Initial import of Cobalt 2.8885 2016-07-27
diff --git a/src/base/allocator/README b/src/base/allocator/README new file mode 100644 index 0000000..ec8a707 --- /dev/null +++ b/src/base/allocator/README
@@ -0,0 +1,59 @@ +Notes about the Chrome memory allocator. + +Background +---------- +We use this library as a generic way to fork into any of several allocators. +Currently we can, at runtime, switch between: + the default windows allocator + the windows low-fragmentation-heap + tcmalloc + jemalloc (the heap used most notably within Mozilla Firefox) + +The mechanism for hooking LIBCMT in windows is rather tricky. The core +problem is that by default, the windows library does not declare malloc and +free as weak symbols. Because of this, they cannot be overriden. To work +around this, we start with the LIBCMT.LIB, and manually remove all allocator +related functions from it using the visual studio library tool. Once removed, +we can now link against the library and provide custom versions of the +allocator related functionality. + + +Source code +----------- +This directory contains just the allocator (i.e. shim) layer that switches +between the different underlying memory allocation implementations. + +The tcmalloc and jemalloc libraries originate outside of Chromium +and exist in ../../third_party/tcmalloc and ../../third_party/jemalloc +(currently, the actual locations are defined in the allocator.gyp file). +The third party sources use a vendor-branch SCM pattern to track +Chromium-specific changes independently from upstream changes. + +The general intent is to push local changes upstream so that over +time we no longer need any forked files. + + +Adding a new allocator +---------------------- +Adding a new allocator requires definition of the following five functions: + + extern "C" { + bool init(); + void* malloc(size_t s); + void* realloc(void* p, size_t s); + void free(void* s); + size_t msize(void* p); + } + +All other allocation related functions (new/delete/calloc/etc) have been +implemented generically to work across all allocators. + + +Usage +----- +You can use the different allocators by setting the environment variable +CHROME_ALLOCATOR to: + "tcmalloc" - TC Malloc (default) + "jemalloc" - JE Malloc + "winheap" - Windows default heap + "winlfh" - Windows Low-Fragmentation heap
diff --git a/src/base/allocator/allocator.gyp b/src/base/allocator/allocator.gyp new file mode 100644 index 0000000..f5bda73 --- /dev/null +++ b/src/base/allocator/allocator.gyp
@@ -0,0 +1,671 @@ +# 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. + +{ + 'variables': { + 'jemalloc_dir': '../../third_party/jemalloc/chromium', + 'tcmalloc_dir': '../../third_party/tcmalloc/chromium', + 'use_vtable_verify%': 0, + }, + 'targets': [ + # Only executables and not libraries should depend on the + # allocator target; only the application (the final executable) + # knows what allocator makes sense. + { + 'target_name': 'allocator', + 'type': 'static_library', + # Make sure the allocation library is optimized to + # the hilt in official builds. + 'variables': { + 'optimize': 'max', + }, + 'include_dirs': [ + '.', + '<(tcmalloc_dir)/src/base', + '<(tcmalloc_dir)/src', + '../..', + ], + 'direct_dependent_settings': { + 'configurations': { + 'Common_Base': { + 'msvs_settings': { + 'VCLinkerTool': { + 'IgnoreDefaultLibraryNames': ['libcmtd.lib', 'libcmt.lib'], + 'AdditionalDependencies': [ + '<(SHARED_INTERMEDIATE_DIR)/allocator/libcmt.lib' + ], + }, + }, + }, + }, + 'conditions': [ + ['OS=="win"', { + 'defines': [ + 'PERFTOOLS_DLL_DECL=', + ], + }], + ], + }, + 'sources': [ + # Generated for our configuration from tcmalloc's build + # and checked in. + '<(tcmalloc_dir)/src/config.h', + '<(tcmalloc_dir)/src/config_linux.h', + '<(tcmalloc_dir)/src/config_win.h', + + # all tcmalloc native and forked files + '<(tcmalloc_dir)/src/addressmap-inl.h', + '<(tcmalloc_dir)/src/base/abort.cc', + '<(tcmalloc_dir)/src/base/abort.h', + '<(tcmalloc_dir)/src/base/arm_instruction_set_select.h', + '<(tcmalloc_dir)/src/base/atomicops-internals-linuxppc.h', + '<(tcmalloc_dir)/src/base/atomicops-internals-arm-generic.h', + '<(tcmalloc_dir)/src/base/atomicops-internals-arm-v6plus.h', + '<(tcmalloc_dir)/src/base/atomicops-internals-macosx.h', + '<(tcmalloc_dir)/src/base/atomicops-internals-windows.h', + '<(tcmalloc_dir)/src/base/atomicops-internals-x86.cc', + '<(tcmalloc_dir)/src/base/atomicops-internals-x86.h', + '<(tcmalloc_dir)/src/base/atomicops.h', + '<(tcmalloc_dir)/src/base/basictypes.h', + '<(tcmalloc_dir)/src/base/commandlineflags.h', + '<(tcmalloc_dir)/src/base/cycleclock.h', + # We don't list dynamic_annotations.c since its copy is already + # present in the dynamic_annotations target. + '<(tcmalloc_dir)/src/base/dynamic_annotations.h', + '<(tcmalloc_dir)/src/base/elf_mem_image.cc', + '<(tcmalloc_dir)/src/base/elf_mem_image.h', + '<(tcmalloc_dir)/src/base/elfcore.h', + '<(tcmalloc_dir)/src/base/googleinit.h', + '<(tcmalloc_dir)/src/base/linux_syscall_support.h', + '<(tcmalloc_dir)/src/base/linuxthreads.cc', + '<(tcmalloc_dir)/src/base/linuxthreads.h', + '<(tcmalloc_dir)/src/base/logging.cc', + '<(tcmalloc_dir)/src/base/logging.h', + '<(tcmalloc_dir)/src/base/low_level_alloc.cc', + '<(tcmalloc_dir)/src/base/low_level_alloc.h', + '<(tcmalloc_dir)/src/base/simple_mutex.h', + '<(tcmalloc_dir)/src/base/spinlock.cc', + '<(tcmalloc_dir)/src/base/spinlock.h', + '<(tcmalloc_dir)/src/base/spinlock_internal.cc', + '<(tcmalloc_dir)/src/base/spinlock_internal.h', + '<(tcmalloc_dir)/src/base/spinlock_linux-inl.h', + '<(tcmalloc_dir)/src/base/spinlock_posix-inl.h', + '<(tcmalloc_dir)/src/base/spinlock_win32-inl.h', + '<(tcmalloc_dir)/src/base/stl_allocator.h', + '<(tcmalloc_dir)/src/base/synchronization_profiling.h', + '<(tcmalloc_dir)/src/base/sysinfo.cc', + '<(tcmalloc_dir)/src/base/sysinfo.h', + '<(tcmalloc_dir)/src/base/thread_annotations.h', + '<(tcmalloc_dir)/src/base/thread_lister.c', + '<(tcmalloc_dir)/src/base/thread_lister.h', + '<(tcmalloc_dir)/src/base/vdso_support.cc', + '<(tcmalloc_dir)/src/base/vdso_support.h', + '<(tcmalloc_dir)/src/central_freelist.cc', + '<(tcmalloc_dir)/src/central_freelist.h', + '<(tcmalloc_dir)/src/common.cc', + '<(tcmalloc_dir)/src/common.h', + '<(tcmalloc_dir)/src/debugallocation.cc', + '<(tcmalloc_dir)/src/deep-heap-profile.cc', + '<(tcmalloc_dir)/src/deep-heap-profile.h', + '<(tcmalloc_dir)/src/free_list.cc', + '<(tcmalloc_dir)/src/free_list.h', + '<(tcmalloc_dir)/src/getpc.h', + '<(tcmalloc_dir)/src/gperftools/heap-checker.h', + '<(tcmalloc_dir)/src/gperftools/heap-profiler.h', + '<(tcmalloc_dir)/src/gperftools/malloc_extension.h', + '<(tcmalloc_dir)/src/gperftools/malloc_extension_c.h', + '<(tcmalloc_dir)/src/gperftools/malloc_hook.h', + '<(tcmalloc_dir)/src/gperftools/malloc_hook_c.h', + '<(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/heap-profile-table.cc', + '<(tcmalloc_dir)/src/heap-profile-table.h', + '<(tcmalloc_dir)/src/heap-profiler.cc', + '<(tcmalloc_dir)/src/internal_logging.cc', + '<(tcmalloc_dir)/src/internal_logging.h', + '<(tcmalloc_dir)/src/libc_override.h', + '<(tcmalloc_dir)/src/libc_override_gcc_and_weak.h', + '<(tcmalloc_dir)/src/libc_override_glibc.h', + '<(tcmalloc_dir)/src/libc_override_osx.h', + '<(tcmalloc_dir)/src/libc_override_redefine.h', + '<(tcmalloc_dir)/src/linked_list.h', + '<(tcmalloc_dir)/src/malloc_extension.cc', + '<(tcmalloc_dir)/src/malloc_hook-inl.h', + '<(tcmalloc_dir)/src/malloc_hook.cc', + '<(tcmalloc_dir)/src/malloc_hook_mmap_freebsd.h', + '<(tcmalloc_dir)/src/malloc_hook_mmap_linux.h', + '<(tcmalloc_dir)/src/maybe_threads.cc', + '<(tcmalloc_dir)/src/maybe_threads.h', + '<(tcmalloc_dir)/src/memfs_malloc.cc', + '<(tcmalloc_dir)/src/memory_region_map.cc', + '<(tcmalloc_dir)/src/memory_region_map.h', + '<(tcmalloc_dir)/src/packed-cache-inl.h', + '<(tcmalloc_dir)/src/page_heap.cc', + '<(tcmalloc_dir)/src/page_heap.h', + '<(tcmalloc_dir)/src/page_heap_allocator.h', + '<(tcmalloc_dir)/src/pagemap.h', + '<(tcmalloc_dir)/src/profile-handler.cc', + '<(tcmalloc_dir)/src/profile-handler.h', + '<(tcmalloc_dir)/src/profiledata.cc', + '<(tcmalloc_dir)/src/profiledata.h', + '<(tcmalloc_dir)/src/profiler.cc', + '<(tcmalloc_dir)/src/raw_printer.cc', + '<(tcmalloc_dir)/src/raw_printer.h', + '<(tcmalloc_dir)/src/sampler.cc', + '<(tcmalloc_dir)/src/sampler.h', + '<(tcmalloc_dir)/src/span.cc', + '<(tcmalloc_dir)/src/span.h', + '<(tcmalloc_dir)/src/stack_trace_table.cc', + '<(tcmalloc_dir)/src/stack_trace_table.h', + '<(tcmalloc_dir)/src/stacktrace.cc', + '<(tcmalloc_dir)/src/stacktrace_arm-inl.h', + '<(tcmalloc_dir)/src/stacktrace_config.h', + '<(tcmalloc_dir)/src/stacktrace_generic-inl.h', + '<(tcmalloc_dir)/src/stacktrace_libunwind-inl.h', + '<(tcmalloc_dir)/src/stacktrace_powerpc-inl.h', + '<(tcmalloc_dir)/src/stacktrace_win32-inl.h', + '<(tcmalloc_dir)/src/stacktrace_with_context.cc', + '<(tcmalloc_dir)/src/stacktrace_x86-inl.h', + '<(tcmalloc_dir)/src/static_vars.cc', + '<(tcmalloc_dir)/src/static_vars.h', + '<(tcmalloc_dir)/src/symbolize.cc', + '<(tcmalloc_dir)/src/symbolize.h', + '<(tcmalloc_dir)/src/system-alloc.cc', + '<(tcmalloc_dir)/src/system-alloc.h', + '<(tcmalloc_dir)/src/tcmalloc.cc', + '<(tcmalloc_dir)/src/tcmalloc_guard.h', + '<(tcmalloc_dir)/src/thread_cache.cc', + '<(tcmalloc_dir)/src/thread_cache.h', + '<(tcmalloc_dir)/src/windows/config.h', + '<(tcmalloc_dir)/src/windows/get_mangled_names.cc', + '<(tcmalloc_dir)/src/windows/gperftools/tcmalloc.h', + '<(tcmalloc_dir)/src/windows/ia32_modrm_map.cc', + '<(tcmalloc_dir)/src/windows/ia32_opcode_map.cc', + '<(tcmalloc_dir)/src/windows/mingw.h', + '<(tcmalloc_dir)/src/windows/mini_disassembler.cc', + '<(tcmalloc_dir)/src/windows/mini_disassembler.h', + '<(tcmalloc_dir)/src/windows/mini_disassembler_types.h', + '<(tcmalloc_dir)/src/windows/override_functions.cc', + '<(tcmalloc_dir)/src/windows/patch_functions.cc', + '<(tcmalloc_dir)/src/windows/port.cc', + '<(tcmalloc_dir)/src/windows/port.h', + '<(tcmalloc_dir)/src/windows/preamble_patcher.cc', + '<(tcmalloc_dir)/src/windows/preamble_patcher.h', + '<(tcmalloc_dir)/src/windows/preamble_patcher_with_stub.cc', + + # jemalloc files + '<(jemalloc_dir)/jemalloc.c', + '<(jemalloc_dir)/jemalloc.h', + '<(jemalloc_dir)/ql.h', + '<(jemalloc_dir)/qr.h', + '<(jemalloc_dir)/rb.h', + + 'allocator_shim.cc', + 'allocator_shim.h', + 'debugallocation_shim.cc', + 'generic_allocators.cc', + 'win_allocator.cc', + ], + # sources! means that these are not compiled directly. + 'sources!': [ + # Included by allocator_shim.cc for maximal inlining. + 'generic_allocators.cc', + 'win_allocator.cc', + + # Included by debugallocation_shim.cc. + '<(tcmalloc_dir)/src/debugallocation.cc', + '<(tcmalloc_dir)/src/tcmalloc.cc', + + # We simply don't use these, but list them above so that IDE + # users can view the full available source for reference, etc. + '<(tcmalloc_dir)/src/addressmap-inl.h', + '<(tcmalloc_dir)/src/base/atomicops-internals-linuxppc.h', + '<(tcmalloc_dir)/src/base/atomicops-internals-macosx.h', + '<(tcmalloc_dir)/src/base/atomicops-internals-x86-msvc.h', + '<(tcmalloc_dir)/src/base/atomicops-internals-x86.cc', + '<(tcmalloc_dir)/src/base/atomicops-internals-x86.h', + '<(tcmalloc_dir)/src/base/atomicops.h', + '<(tcmalloc_dir)/src/base/basictypes.h', + '<(tcmalloc_dir)/src/base/commandlineflags.h', + '<(tcmalloc_dir)/src/base/cycleclock.h', + '<(tcmalloc_dir)/src/base/elf_mem_image.h', + '<(tcmalloc_dir)/src/base/elfcore.h', + '<(tcmalloc_dir)/src/base/googleinit.h', + '<(tcmalloc_dir)/src/base/linux_syscall_support.h', + '<(tcmalloc_dir)/src/base/simple_mutex.h', + '<(tcmalloc_dir)/src/base/spinlock_linux-inl.h', + '<(tcmalloc_dir)/src/base/spinlock_posix-inl.h', + '<(tcmalloc_dir)/src/base/spinlock_win32-inl.h', + '<(tcmalloc_dir)/src/base/stl_allocator.h', + '<(tcmalloc_dir)/src/base/thread_annotations.h', + '<(tcmalloc_dir)/src/getpc.h', + '<(tcmalloc_dir)/src/gperftools/heap-checker.h', + '<(tcmalloc_dir)/src/gperftools/heap-profiler.h', + '<(tcmalloc_dir)/src/gperftools/malloc_extension.h', + '<(tcmalloc_dir)/src/gperftools/malloc_extension_c.h', + '<(tcmalloc_dir)/src/gperftools/malloc_hook.h', + '<(tcmalloc_dir)/src/gperftools/malloc_hook_c.h', + '<(tcmalloc_dir)/src/gperftools/profiler.h', + '<(tcmalloc_dir)/src/gperftools/stacktrace.h', + '<(tcmalloc_dir)/src/gperftools/tcmalloc.h', + '<(tcmalloc_dir)/src/libc_override.h', + '<(tcmalloc_dir)/src/libc_override_gcc_and_weak.h', + '<(tcmalloc_dir)/src/libc_override_glibc.h', + '<(tcmalloc_dir)/src/libc_override_osx.h', + '<(tcmalloc_dir)/src/libc_override_redefine.h', + '<(tcmalloc_dir)/src/malloc_hook_mmap_freebsd.h', + '<(tcmalloc_dir)/src/malloc_hook_mmap_linux.h', + '<(tcmalloc_dir)/src/memfs_malloc.cc', + '<(tcmalloc_dir)/src/packed-cache-inl.h', + '<(tcmalloc_dir)/src/page_heap_allocator.h', + '<(tcmalloc_dir)/src/pagemap.h', + '<(tcmalloc_dir)/src/stacktrace_arm-inl.h', + '<(tcmalloc_dir)/src/stacktrace_config.h', + '<(tcmalloc_dir)/src/stacktrace_generic-inl.h', + '<(tcmalloc_dir)/src/stacktrace_libunwind-inl.h', + '<(tcmalloc_dir)/src/stacktrace_powerpc-inl.h', + '<(tcmalloc_dir)/src/stacktrace_win32-inl.h', + '<(tcmalloc_dir)/src/stacktrace_with_context.cc', + '<(tcmalloc_dir)/src/stacktrace_x86-inl.h', + '<(tcmalloc_dir)/src/tcmalloc_guard.h', + '<(tcmalloc_dir)/src/windows/config.h', + '<(tcmalloc_dir)/src/windows/gperftools/tcmalloc.h', + '<(tcmalloc_dir)/src/windows/get_mangled_names.cc', + '<(tcmalloc_dir)/src/windows/ia32_modrm_map.cc', + '<(tcmalloc_dir)/src/windows/ia32_opcode_map.cc', + '<(tcmalloc_dir)/src/windows/mingw.h', + '<(tcmalloc_dir)/src/windows/mini_disassembler.cc', + '<(tcmalloc_dir)/src/windows/mini_disassembler.h', + '<(tcmalloc_dir)/src/windows/mini_disassembler_types.h', + '<(tcmalloc_dir)/src/windows/override_functions.cc', + '<(tcmalloc_dir)/src/windows/patch_functions.cc', + '<(tcmalloc_dir)/src/windows/preamble_patcher.cc', + '<(tcmalloc_dir)/src/windows/preamble_patcher.h', + '<(tcmalloc_dir)/src/windows/preamble_patcher_with_stub.cc', + ], + 'dependencies': [ + '../third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + ], + 'msvs_settings': { + # TODO(sgk): merge this with build/common.gypi settings + 'VCLibrarianTool': { + 'AdditionalOptions': ['/ignore:4006,4221'], + 'AdditionalLibraryDirectories': + ['<(DEPTH)/third_party/platformsdk_win7/files/Lib'], + }, + 'VCLinkerTool': { + 'AdditionalOptions': ['/ignore:4006'], + }, + }, + 'configurations': { + 'Debug_Base': { + 'msvs_settings': { + 'VCCLCompilerTool': { + 'RuntimeLibrary': '0', + }, + }, + 'variables': { + # Provide a way to force disable debugallocation in Debug builds, + # e.g. for profiling (it's more rare to profile Debug builds, + # but people sometimes need to do that). + 'disable_debugallocation%': 1, + }, + 'conditions': [ + ['disable_debugallocation==0', { + 'defines': [ + # Use debugallocation for Debug builds to catch problems early + # and cleanly, http://crbug.com/30715 . + 'TCMALLOC_FOR_DEBUGALLOCATION', + ], + }], + ], + }, + }, + 'conditions': [ + ['OS=="linux" and clang_type_profiler==1', { + 'dependencies': [ + 'type_profiler_tcmalloc', + ], + # It is undoing dependencies and cflags_cc for type_profiler which + # build/common.gypi injects into all targets. + 'dependencies!': [ + 'type_profiler', + ], + 'cflags_cc!': [ + '-fintercept-allocation-functions', + ], + }], + ['OS=="win"', { + 'defines': [ + 'PERFTOOLS_DLL_DECL=', + ], + 'defines!': [ + # tcmalloc source files unconditionally define this, remove it from + # the list of defines that common.gypi defines globally. + 'NOMINMAX', + ], + 'dependencies': [ + 'libcmt', + ], + 'include_dirs': [ + '<(jemalloc_dir)', + '<(tcmalloc_dir)/src/windows', + ], + 'sources!': [ + '<(tcmalloc_dir)/src/base/elf_mem_image.cc', + '<(tcmalloc_dir)/src/base/elf_mem_image.h', + '<(tcmalloc_dir)/src/base/linuxthreads.cc', + '<(tcmalloc_dir)/src/base/linuxthreads.h', + '<(tcmalloc_dir)/src/base/vdso_support.cc', + '<(tcmalloc_dir)/src/base/vdso_support.h', + '<(tcmalloc_dir)/src/maybe_threads.cc', + '<(tcmalloc_dir)/src/maybe_threads.h', + '<(tcmalloc_dir)/src/symbolize.h', + '<(tcmalloc_dir)/src/system-alloc.cc', + '<(tcmalloc_dir)/src/system-alloc.h', + + # included by allocator_shim.cc + 'debugallocation_shim.cc', + + # heap-profiler/checker/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', + '<(tcmalloc_dir)/src/profile-handler.h', + '<(tcmalloc_dir)/src/profiler.cc', + ], + }], + ['OS=="linux" or OS=="freebsd" or OS=="solaris"', { + 'sources!': [ + '<(tcmalloc_dir)/src/system-alloc.h', + '<(tcmalloc_dir)/src/windows/port.cc', + '<(tcmalloc_dir)/src/windows/port.h', + + # TODO(willchan): Support allocator shim later on. + 'allocator_shim.cc', + + # TODO(willchan): support jemalloc on other platforms + # jemalloc files + '<(jemalloc_dir)/jemalloc.c', + '<(jemalloc_dir)/jemalloc.h', + '<(jemalloc_dir)/ql.h', + '<(jemalloc_dir)/qr.h', + '<(jemalloc_dir)/rb.h', + + ], + # We enable all warnings by default, but upstream disables a few. + # Keep "-Wno-*" flags in sync with upstream by comparing against: + # http://code.google.com/p/google-perftools/source/browse/trunk/Makefile.am + 'cflags': [ + '-Wno-sign-compare', + '-Wno-unused-result', + ], + 'cflags!': [ + '-fvisibility=hidden', + ], + 'link_settings': { + 'ldflags': [ + # Don't let linker rip this symbol out, otherwise the heap&cpu + # profilers will not initialize properly on startup. + '-Wl,-uIsHeapProfilerRunning,-uProfilerStart', + # Do the same for heap leak checker. + '-Wl,-u_Z21InitialMallocHook_NewPKvj,-u_Z22InitialMallocHook_MMapPKvS0_jiiix,-u_Z22InitialMallocHook_SbrkPKvi', + '-Wl,-u_Z21InitialMallocHook_NewPKvm,-u_Z22InitialMallocHook_MMapPKvS0_miiil,-u_Z22InitialMallocHook_SbrkPKvl', + '-Wl,-u_ZN15HeapLeakChecker12IgnoreObjectEPKv,-u_ZN15HeapLeakChecker14UnIgnoreObjectEPKv', + ]}, + }], + [ 'use_vtable_verify==1', { + 'cflags': [ + '-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', + ], + }], + [ 'clang==1', { + 'cflags': [ + '-Wno-non-literal-null-conversion', + ], + }], + ['order_profiling != 0', { + 'target_conditions' : [ + ['_toolset=="target"', { + 'cflags!': [ '-finstrument-functions' ], + }], + ], + }], + ], + }, + { + # This library is linked in to src/base.gypi:base and allocator_unittests + # It can't depend on either and nothing else should depend on it - all + # other code should use the interfaced provided by base. + 'target_name': 'allocator_extension_thunks', + 'type': 'static_library', + 'sources': [ + 'allocator_extension_thunks.cc', + 'allocator_extension_thunks.h', + ], + 'toolsets': ['host', 'target'], + 'include_dirs': [ + '../../' + ], + 'conditions': [ + ['OS=="linux" and clang_type_profiler==1', { + # It is undoing dependencies and cflags_cc for type_profiler which + # build/common.gypi injects into all targets. + 'dependencies!': [ + 'type_profiler', + ], + 'cflags_cc!': [ + '-fintercept-allocation-functions', + ], + }], + ], + }, + ], + 'conditions': [ + ['OS=="win"', { + 'targets': [ + { + 'target_name': 'libcmt', + 'type': 'none', + 'actions': [ + { + 'action_name': 'libcmt', + 'inputs': [ + 'prep_libc.py', + ], + 'outputs': [ + '<(SHARED_INTERMEDIATE_DIR)/allocator/libcmt.lib', + ], + 'action': [ + 'python', + 'prep_libc.py', + '$(VCInstallDir)lib', + '<(SHARED_INTERMEDIATE_DIR)/allocator', + ], + }, + ], + }, + { + 'target_name': 'allocator_unittests', + 'type': 'executable', + 'dependencies': [ + 'allocator', + 'allocator_extension_thunks', + '../../testing/gtest.gyp:gtest', + ], + 'include_dirs': [ + '.', + '<(tcmalloc_dir)/src/base', + '<(tcmalloc_dir)/src', + '../..', + ], + 'sources': [ + 'allocator_unittests.cc', + '../profiler/alternate_timer.cc', + '../profiler/alternate_timer.h', + ], + }, + { + 'target_name': 'allocator_extension_thunks_win64', + 'type': 'static_library', + 'sources': [ + 'allocator_extension_thunks.cc', + 'allocator_extension_thunks.h', + ], + 'toolsets': ['host', 'target'], + 'include_dirs': [ + '../../' + ], + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + }, + { + 'target_name': 'tcmalloc_unittest', + 'type': 'executable', + 'sources': [ + 'tcmalloc_unittest.cc', + ], + 'include_dirs': [ + '../..', + # For constants of TCMalloc. + '<(tcmalloc_dir)/src', + ], + 'dependencies': [ + '../../testing/gtest.gyp:gtest', + '../base.gyp:base', + 'allocator', + ], + }, + ], + }], + ['OS=="linux" and clang_type_profiler==1', { + # Some targets in this section undo dependencies and cflags_cc for + # type_profiler which build/common.gypi injects into all targets. + 'targets': [ + { + 'target_name': 'type_profiler', + 'type': 'static_library', + 'dependencies!': [ + 'type_profiler', + ], + 'cflags_cc!': [ + '-fintercept-allocation-functions', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'type_profiler.cc', + 'type_profiler.h', + 'type_profiler_control.h', + ], + 'toolsets': ['host', 'target'], + }, + { + 'target_name': 'type_profiler_tcmalloc', + 'type': 'static_library', + 'dependencies!': [ + 'type_profiler', + ], + 'cflags_cc!': [ + '-fintercept-allocation-functions', + ], + 'include_dirs': [ + '<(tcmalloc_dir)/src', + '../..', + ], + 'sources': [ + 'type_profiler_tcmalloc.cc', + 'type_profiler_tcmalloc.h', + '<(tcmalloc_dir)/src/gperftools/type_profiler_map.h', + '<(tcmalloc_dir)/src/type_profiler_map.cc', + ], + }, + { + 'target_name': 'type_profiler_unittests', + 'type': 'executable', + 'dependencies': [ + '../../testing/gtest.gyp:gtest', + '../base.gyp:base', + 'allocator', + 'type_profiler_tcmalloc', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'type_profiler_control.cc', + 'type_profiler_control.h', + 'type_profiler_unittests.cc', + ], + }, + { + 'target_name': 'type_profiler_map_unittests', + 'type': 'executable', + 'dependencies': [ + '../../testing/gtest.gyp:gtest', + '../base.gyp:base', + 'allocator', + ], + 'dependencies!': [ + 'type_profiler', + ], + 'cflags_cc!': [ + '-fintercept-allocation-functions', + ], + 'include_dirs': [ + '<(tcmalloc_dir)/src', + '../..', + ], + 'sources': [ + 'type_profiler_map_unittests.cc', + '<(tcmalloc_dir)/src/gperftools/type_profiler_map.h', + '<(tcmalloc_dir)/src/type_profiler_map.cc', + ], + }, + ], + }], + ], +}
diff --git a/src/base/allocator/allocator_extension.cc b/src/base/allocator/allocator_extension.cc new file mode 100644 index 0000000..83e460a --- /dev/null +++ b/src/base/allocator/allocator_extension.cc
@@ -0,0 +1,56 @@ +// 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/allocator/allocator_extension.h" + +#include "base/logging.h" + +namespace base { +namespace allocator { + +bool GetAllocatorWasteSize(size_t* size) { + thunks::GetAllocatorWasteSizeFunction get_allocator_waste_size_function = + thunks::GetGetAllocatorWasteSizeFunction(); + return get_allocator_waste_size_function != NULL && + get_allocator_waste_size_function(size); +} + +void GetStats(char* buffer, int buffer_length) { + DCHECK_GT(buffer_length, 0); + thunks::GetStatsFunction get_stats_function = thunks::GetGetStatsFunction(); + if (get_stats_function) + get_stats_function(buffer, buffer_length); + else + buffer[0] = '\0'; +} + +void ReleaseFreeMemory() { + thunks::ReleaseFreeMemoryFunction release_free_memory_function = + thunks::GetReleaseFreeMemoryFunction(); + if (release_free_memory_function) + release_free_memory_function(); +} + +void SetGetAllocatorWasteSizeFunction( + thunks::GetAllocatorWasteSizeFunction get_allocator_waste_size_function) { + DCHECK_EQ(thunks::GetGetAllocatorWasteSizeFunction(), + reinterpret_cast<thunks::GetAllocatorWasteSizeFunction>(NULL)); + thunks::SetGetAllocatorWasteSizeFunction(get_allocator_waste_size_function); +} + +void SetGetStatsFunction(thunks::GetStatsFunction get_stats_function) { + DCHECK_EQ(thunks::GetGetStatsFunction(), + reinterpret_cast<thunks::GetStatsFunction>(NULL)); + thunks::SetGetStatsFunction(get_stats_function); +} + +void SetReleaseFreeMemoryFunction( + thunks::ReleaseFreeMemoryFunction release_free_memory_function) { + DCHECK_EQ(thunks::GetReleaseFreeMemoryFunction(), + reinterpret_cast<thunks::ReleaseFreeMemoryFunction>(NULL)); + thunks::SetReleaseFreeMemoryFunction(release_free_memory_function); +} + +} // namespace allocator +} // namespace base
diff --git a/src/base/allocator/allocator_extension.h b/src/base/allocator/allocator_extension.h new file mode 100644 index 0000000..de3119f --- /dev/null +++ b/src/base/allocator/allocator_extension.h
@@ -0,0 +1,59 @@ +// 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_ALLOCATOR_ALLOCATOR_EXTENSION_H +#define BASE_ALLOCATOR_ALLOCATOR_EXTENSION_H + +#include <stddef.h> // for size_t + +#include "base/allocator/allocator_extension_thunks.h" +#include "base/base_export.h" +#include "build/build_config.h" + +namespace base { +namespace allocator { + +// Request the allocator to report value of its waste memory size. +// Waste size corresponds to memory that has been allocated from the OS but +// not passed up to the application. It e.g. includes memory retained by free +// lists, internal data, chunks padding, etc. +// +// |size| pointer to the returned value, must be not NULL. +// Returns true if the value has been returned, false otherwise. +BASE_EXPORT bool GetAllocatorWasteSize(size_t* size); + +// Request that the allocator print a human-readable description of the current +// state of the allocator into a null-terminated string in the memory segment +// buffer[0,buffer_length-1]. +// +// |buffer| must point to a valid piece of memory +// |buffer_length| must be > 0. +BASE_EXPORT void GetStats(char* buffer, int buffer_length); + +// Request that the allocator release any free memory it knows about to the +// system. +BASE_EXPORT void ReleaseFreeMemory(); + + +// These settings allow specifying a callback used to implement the allocator +// extension functions. These are optional, but if set they must only be set +// once. These will typically called in an allocator-specific initialization +// routine. +// +// No threading promises are made. The caller is responsible for making sure +// these pointers are set before any other threads attempt to call the above +// functions. +BASE_EXPORT void SetGetAllocatorWasteSizeFunction( + thunks::GetAllocatorWasteSizeFunction get_allocator_waste_size_function); + +BASE_EXPORT void SetGetStatsFunction( + thunks::GetStatsFunction get_stats_function); + +BASE_EXPORT void SetReleaseFreeMemoryFunction( + thunks::ReleaseFreeMemoryFunction release_free_memory_function); + +} // namespace allocator +} // namespace base + +#endif
diff --git a/src/base/allocator/allocator_extension_thunks.cc b/src/base/allocator/allocator_extension_thunks.cc new file mode 100644 index 0000000..e4024fb --- /dev/null +++ b/src/base/allocator/allocator_extension_thunks.cc
@@ -0,0 +1,52 @@ +// 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/allocator/allocator_extension_thunks.h" + +#include <cstddef> // for NULL + +namespace base { +namespace allocator { +namespace thunks { + +// This slightly odd translation unit exists because of the peculularity of how +// allocator_unittests work on windows. That target has to perform +// tcmalloc-specific initialization on windows, but it cannot depend on base +// otherwise. This target sits in the middle - base and allocator_unittests +// can depend on it. This file can't depend on anything else in base, including +// logging. + +static GetAllocatorWasteSizeFunction g_get_allocator_waste_size_function = NULL; +static GetStatsFunction g_get_stats_function = NULL; +static ReleaseFreeMemoryFunction g_release_free_memory_function = NULL; + +void SetGetAllocatorWasteSizeFunction( + GetAllocatorWasteSizeFunction get_allocator_waste_size_function) { + g_get_allocator_waste_size_function = get_allocator_waste_size_function; +} + +GetAllocatorWasteSizeFunction GetGetAllocatorWasteSizeFunction() { + return g_get_allocator_waste_size_function; +} + +void SetGetStatsFunction(GetStatsFunction get_stats_function) { + g_get_stats_function = get_stats_function; +} + +GetStatsFunction GetGetStatsFunction() { + return g_get_stats_function; +} + +void SetReleaseFreeMemoryFunction( + ReleaseFreeMemoryFunction release_free_memory_function) { + g_release_free_memory_function = release_free_memory_function; +} + +ReleaseFreeMemoryFunction GetReleaseFreeMemoryFunction() { + return g_release_free_memory_function; +} + +} // namespace thunks +} // namespace allocator +} // namespace base
diff --git a/src/base/allocator/allocator_extension_thunks.h b/src/base/allocator/allocator_extension_thunks.h new file mode 100644 index 0000000..1e97a84 --- /dev/null +++ b/src/base/allocator/allocator_extension_thunks.h
@@ -0,0 +1,36 @@ +// 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_ALLOCATOR_ALLOCATOR_THUNKS_EXTENSION_H +#define BASE_ALLOCATOR_ALLOCATOR_THUNKS_EXTENSION_H + +#include <stddef.h> // for size_t + +namespace base { +namespace allocator { +namespace thunks { + +// WARNING: You probably don't want to use this file unless you are routing a +// new allocator extension from a specific allocator implementation to base. +// See allocator_extension.h to see the interface that base exports. + +typedef bool (*GetAllocatorWasteSizeFunction)(size_t* size); +void SetGetAllocatorWasteSizeFunction( + GetAllocatorWasteSizeFunction get_allocator_waste_size_function); +GetAllocatorWasteSizeFunction GetGetAllocatorWasteSizeFunction(); + +typedef void (*GetStatsFunction)(char* buffer, int buffer_length); +void SetGetStatsFunction(GetStatsFunction get_stats_function); +GetStatsFunction GetGetStatsFunction(); + +typedef void (*ReleaseFreeMemoryFunction)(); +void SetReleaseFreeMemoryFunction( + ReleaseFreeMemoryFunction release_free_memory_function); +ReleaseFreeMemoryFunction GetReleaseFreeMemoryFunction(); + +} // namespace thunks +} // namespace allocator +} // namespace base + +#endif
diff --git a/src/base/allocator/allocator_shim.cc b/src/base/allocator/allocator_shim.cc new file mode 100644 index 0000000..6e41327 --- /dev/null +++ b/src/base/allocator/allocator_shim.cc
@@ -0,0 +1,431 @@ +// 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/allocator/allocator_shim.h" + +#include <config.h> +#include "base/allocator/allocator_extension_thunks.h" +#include "base/profiler/alternate_timer.h" +#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 + +// TODO(mbelshe): Ensure that all calls to tcmalloc have the proper call depth +// from the "user code" so that debugging tools (HeapChecker) can work. + +// __THROW is defined in glibc systems. It means, counter-intuitively, +// "This function will never throw an exception." It's an optional +// optimization tool, but we may need to use it to match glibc prototypes. +#ifndef __THROW // I guess we're not on a glibc system +# define __THROW // __THROW is just an optimization, so ok to make it "" +#endif + +// new_mode behaves similarly to MSVC's _set_new_mode. +// If flag is 0 (default), calls to malloc will behave normally. +// If flag is 1, calls to malloc will behave like calls to new, +// and the std_new_handler will be invoked on failure. +// Can be set by calling _set_new_mode(). +static int new_mode = 0; + +typedef enum { + TCMALLOC, // TCMalloc is the default allocator. + JEMALLOC, // JEMalloc. + WINHEAP, // Windows Heap (standard Windows allocator). + WINLFH, // Windows LFH Heap. +} Allocator; + +// This is the default allocator. This value can be changed at startup by +// specifying environment variables shown below it. +// See SetupSubprocessAllocator() to specify a default secondary (subprocess) +// allocator. +// TODO(jar): Switch to using TCMALLOC for the renderer as well. +static Allocator allocator = TCMALLOC; + +// The names of the environment variables that can optionally control the +// selection of the allocator. The primary may be used to control overall +// allocator selection, and the secondary can be used to specify an allocator +// to use in sub-processes. +static const char primary_name[] = "CHROME_ALLOCATOR"; +static const char secondary_name[] = "CHROME_ALLOCATOR_2"; + +// We include tcmalloc and the win_allocator to get as much inlining as +// possible. +#include "debugallocation_shim.cc" +#include "win_allocator.cc" + +// Forward declarations from jemalloc. +extern "C" { +void* je_malloc(size_t s); +void* je_realloc(void* p, size_t s); +void je_free(void* s); +size_t je_msize(void* p); +bool je_malloc_init_hard(); +void* je_memalign(size_t a, size_t s); +} + +extern "C" { + +// Call the new handler, if one has been set. +// Returns true on successfully calling the handler, false otherwise. +inline bool call_new_handler(bool nothrow) { + // Get the current new handler. NB: this function is not + // thread-safe. We make a feeble stab at making it so here, but + // this lock only protects against tcmalloc interfering with + // itself, not with other libraries calling set_new_handler. + std::new_handler nh; + { + SpinLockHolder h(&set_new_handler_lock); + nh = std::set_new_handler(0); + (void) std::set_new_handler(nh); + } +#if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || (defined(_HAS_EXCEPTIONS) && !_HAS_EXCEPTIONS) + if (!nh) + return false; + // Since exceptions are disabled, we don't really know if new_handler + // failed. Assume it will abort if it fails. + (*nh)(); + return false; // break out of the retry loop. +#else + // If no new_handler is established, the allocation failed. + if (!nh) { + if (nothrow) + return 0; + throw std::bad_alloc(); + } + // Otherwise, try the new_handler. If it returns, retry the + // allocation. If it throws std::bad_alloc, fail the allocation. + // if it throws something else, don't interfere. + try { + (*nh)(); + } catch (const std::bad_alloc&) { + if (!nothrow) + throw; + return true; + } +#endif // (defined(__GNUC__) && !defined(__EXCEPTIONS)) || (defined(_HAS_EXCEPTIONS) && !_HAS_EXCEPTIONS) +} + +void* malloc(size_t size) __THROW { + void* ptr; + for (;;) { +#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING + switch (allocator) { + case JEMALLOC: + ptr = je_malloc(size); + break; + case WINHEAP: + case WINLFH: + ptr = win_heap_malloc(size); + break; + case TCMALLOC: + default: + ptr = do_malloc(size); + break; + } +#else + // TCMalloc case. + ptr = do_malloc(size); +#endif + if (ptr) + return ptr; + + if (!new_mode || !call_new_handler(true)) + break; + } + return ptr; +} + +void free(void* p) __THROW { +#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING + switch (allocator) { + case JEMALLOC: + je_free(p); + return; + case WINHEAP: + case WINLFH: + win_heap_free(p); + return; + } +#endif + // TCMalloc case. + do_free(p); +} + +void* realloc(void* ptr, size_t size) __THROW { + // Webkit is brittle for allocators that return NULL for malloc(0). The + // realloc(0, 0) code path does not guarantee a non-NULL return, so be sure + // to call malloc for this case. + if (!ptr) + return malloc(size); + + void* new_ptr; + for (;;) { +#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING + switch (allocator) { + case JEMALLOC: + new_ptr = je_realloc(ptr, size); + break; + case WINHEAP: + case WINLFH: + new_ptr = win_heap_realloc(ptr, size); + break; + case TCMALLOC: + default: + 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 + // NULL. + if (new_ptr || !size) + return new_ptr; + if (!new_mode || !call_new_handler(true)) + break; + } + return new_ptr; +} + +// TODO(mbelshe): Implement this for other allocators. +void malloc_stats(void) __THROW { +#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING + switch (allocator) { + case JEMALLOC: + // No stats. + return; + case WINHEAP: + case WINLFH: + // No 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); + case WINHEAP: + case WINLFH: + return win_heap_msize(p); + } +#endif + return MallocExtension::instance()->GetAllocatedSize(p); +} + +// This is included to resolve references from libcmt. +extern "C" intptr_t _get_heap_handle() { + return 0; +} + +static bool get_allocator_waste_size_thunk(size_t* size) { +#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING + switch (allocator) { + case JEMALLOC: + case WINHEAP: + case WINLFH: + // 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) && + ext->GetNumericProperty("generic.current_allocated_bytes", + &allocated_bytes) && + ext->GetNumericProperty("tcmalloc.pageheap_unmapped_bytes", + &unmapped_bytes)) { + *size = heap_size - allocated_bytes - unmapped_bytes; + return true; + } + return false; +} + +static void get_stats_thunk(char* buffer, int buffer_length) { + MallocExtension::instance()->GetStats(buffer, buffer_length); +} + +static void release_free_memory_thunk() { + MallocExtension::instance()->ReleaseFreeMemory(); +} + +// The CRT heap initialization stub. +extern "C" int _heap_init() { +#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING + const char* environment_value = GetenvBeforeMain(primary_name); + if (environment_value) { + if (!stricmp(environment_value, "jemalloc")) + allocator = JEMALLOC; + else if (!stricmp(environment_value, "winheap")) + allocator = WINHEAP; + else if (!stricmp(environment_value, "winlfh")) + allocator = WINLFH; + else if (!stricmp(environment_value, "tcmalloc")) + allocator = TCMALLOC; + } + + switch (allocator) { + case JEMALLOC: + return je_malloc_init_hard() ? 0 : 1; + case WINHEAP: + return win_heap_init(false) ? 1 : 0; + case WINLFH: + return win_heap_init(true) ? 1 : 0; + case TCMALLOC: + default: + // 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 + // you can't do anything useful anyway. + new TCMallocGuard(); + + // Provide optional hook for monitoring allocation quantities on a per-thread + // basis. Only set the hook if the environment indicates this needs to be + // enabled. + const char* profiling = + GetenvBeforeMain(tracked_objects::kAlternateProfilerTime); + if (profiling && *profiling == '1') { + tracked_objects::SetAlternateTimeSource( + tcmalloc::ThreadCache::GetBytesAllocatedOnCurrentThread, + tracked_objects::TIME_SOURCE_TYPE_TCMALLOC); + } + + base::allocator::thunks::SetGetAllocatorWasteSizeFunction( + get_allocator_waste_size_thunk); + base::allocator::thunks::SetGetStatsFunction(get_stats_thunk); + base::allocator::thunks::SetReleaseFreeMemoryFunction( + release_free_memory_thunk); + + return 1; +} + +// The CRT heap cleanup stub. +extern "C" void _heap_term() {} + +// We set this to 1 because part of the CRT uses a check of _crtheap != 0 +// to test whether the CRT has been initialized. Once we've ripped out +// the allocators from libcmt, we need to provide this definition so that +// the rest of the CRT is still usable. +extern "C" void* _crtheap = reinterpret_cast<void*>(1); + +// Provide support for aligned memory through Windows only _aligned_malloc(). +void* _aligned_malloc(size_t size, size_t alignment) { + // _aligned_malloc guarantees parameter validation, so do so here. These + // checks are somewhat stricter than _aligned_malloc() since we're effectively + // using memalign() under the hood. + DCHECK_GT(size, 0U); + DCHECK_EQ(alignment & (alignment - 1), 0U); + DCHECK_EQ(alignment % sizeof(void*), 0U); + + void* ptr; + for (;;) { +#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING + switch (allocator) { + case JEMALLOC: + ptr = je_memalign(alignment, size); + break; + case WINHEAP: + case WINLFH: + ptr = win_heap_memalign(alignment, size); + break; + case TCMALLOC: + default: + 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); + return ptr; + } + + if (!new_mode || !call_new_handler(true)) + break; + } + return ptr; +} + +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); + return; + case WINHEAP: + case WINLFH: + win_heap_memalign_free(p); + return; + } +#endif + // TCMalloc case. + do_free(p); +} + +#endif // WIN32 + +#include "generic_allocators.cc" + +} // extern C + +namespace base { +namespace allocator { + +void SetupSubprocessAllocator() { +#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING + size_t primary_length = 0; + getenv_s(&primary_length, NULL, 0, primary_name); + + size_t secondary_length = 0; + char buffer[20]; + getenv_s(&secondary_length, buffer, sizeof(buffer), secondary_name); + DCHECK_GT(sizeof(buffer), secondary_length); + buffer[sizeof(buffer) - 1] = '\0'; + + if (secondary_length || !primary_length) { + const char* secondary_value = secondary_length ? buffer : "TCMALLOC"; + // Force renderer (or other subprocesses) to use secondary_value. + int ret_val = _putenv_s(primary_name, secondary_value); + DCHECK_EQ(0, ret_val); + } +#endif // ENABLE_DYNAMIC_ALLOCATOR_SWITCHING +} + +void* TCMallocDoMallocForTest(size_t size) { + return do_malloc(size); +} + +void TCMallocDoFreeForTest(void* ptr) { + do_free(ptr); +} + +size_t ExcludeSpaceForMarkForTest(size_t size) { + return ExcludeSpaceForMark(size); +} + +} // namespace allocator. +} // namespace base.
diff --git a/src/base/allocator/allocator_shim.h b/src/base/allocator/allocator_shim.h new file mode 100644 index 0000000..e10fa8d --- /dev/null +++ b/src/base/allocator/allocator_shim.h
@@ -0,0 +1,25 @@ +// 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_ALLOCATOR_ALLOCATOR_SHIM_H_ +#define BASE_ALLOCATOR_ALLOCATOR_SHIM_H_ + +namespace base { +namespace allocator { + +// Resets the environment variable CHROME_ALLOCATOR to specify the choice to +// be used by subprocesses. Priority is given to the current value of +// CHROME_ALLOCATOR_2 (if specified), then CHROME_ALLOCATOR (if specified), and +// then a default value (typically set to TCMALLOC). +void SetupSubprocessAllocator(); + +// Expose some of tcmalloc functions for test. +void* TCMallocDoMallocForTest(size_t size); +void TCMallocDoFreeForTest(void* ptr); +size_t ExcludeSpaceForMarkForTest(size_t size); + +} // namespace allocator. +} // namespace base. + +#endif // BASE_ALLOCATOR_ALLOCATOR_SHIM_H_
diff --git a/src/base/allocator/allocator_unittests.cc b/src/base/allocator/allocator_unittests.cc new file mode 100644 index 0000000..3c455ba --- /dev/null +++ b/src/base/allocator/allocator_unittests.cc
@@ -0,0 +1,555 @@ +// 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 <stdio.h> +#include <stdlib.h> +#include <algorithm> // for min() +#include "base/atomicops.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Number of bits in a size_t. +static const int kSizeBits = 8 * sizeof(size_t); +// The maximum size of a size_t. +static const size_t kMaxSize = ~static_cast<size_t>(0); +// Maximum positive size of a size_t if it were signed. +static const size_t kMaxSignedSize = ((size_t(1) << (kSizeBits-1)) - 1); + +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) +// An allocation size which is not too big to be reasonable. +static const size_t kNotTooBig = 100000; +// An allocation size which is just too big. +static const size_t kTooBig = ~static_cast<size_t>(0); +#endif + +namespace { + +using std::min; + +// Fill a buffer of the specified size with a predetermined pattern +static void Fill(unsigned char* buffer, int n) { + for (int i = 0; i < n; i++) { + buffer[i] = (i & 0xff); + } +} + +// Check that the specified buffer has the predetermined pattern +// generated by Fill() +static bool Valid(unsigned char* buffer, int n) { + for (int i = 0; i < n; i++) { + if (buffer[i] != (i & 0xff)) { + return false; + } + } + return true; +} + +// Check that a buffer is completely zeroed. +static bool IsZeroed(unsigned char* buffer, int n) { + for (int i = 0; i < n; i++) { + if (buffer[i] != 0) { + return false; + } + } + return true; +} + +// Check alignment +static void CheckAlignment(void* p, int align) { + EXPECT_EQ(0, reinterpret_cast<uintptr_t>(p) & (align-1)); +} + +// Return the next interesting size/delta to check. Returns -1 if no more. +static int NextSize(int size) { + if (size < 100) + return size+1; + + if (size < 100000) { + // Find next power of two + int power = 1; + while (power < size) + power <<= 1; + + // Yield (power-1, power, power+1) + if (size < power-1) + return power-1; + + if (size == power-1) + return power; + + assert(size == power); + return power+1; + } else { + return -1; + } +} + +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) +#if defined(GG_ULONGLONG) +// GG_ULONGLONG is already defined in base/port.h +#undef GG_ULONGLONG +#endif + +// namespace resolution for typedefs +using base::subtle::AtomicWord; +using base::subtle::Atomic32; +#endif + +#define GG_ULONGLONG(x) static_cast<uint64>(x) + +template <class AtomicType> +static void TestAtomicIncrement() { + // For now, we just test single threaded execution + + // use a guard value to make sure the NoBarrier_AtomicIncrement doesn't go + // outside the expected address bounds. This is in particular to + // test that some future change to the asm code doesn't cause the + // 32-bit NoBarrier_AtomicIncrement to do the wrong thing on 64-bit machines. + struct { + AtomicType prev_word; + AtomicType count; + AtomicType next_word; + } s; + + AtomicType prev_word_value, next_word_value; + memset(&prev_word_value, 0xFF, sizeof(AtomicType)); + memset(&next_word_value, 0xEE, sizeof(AtomicType)); + + s.prev_word = prev_word_value; + s.count = 0; + s.next_word = next_word_value; + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 1), 1); + EXPECT_EQ(s.count, 1); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 2), 3); + EXPECT_EQ(s.count, 3); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 3), 6); + EXPECT_EQ(s.count, 6); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -3), 3); + EXPECT_EQ(s.count, 3); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -2), 1); + EXPECT_EQ(s.count, 1); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -1), 0); + EXPECT_EQ(s.count, 0); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -1), -1); + EXPECT_EQ(s.count, -1); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -4), -5); + EXPECT_EQ(s.count, -5); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 5), 0); + EXPECT_EQ(s.count, 0); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); +} + + +#define NUM_BITS(T) (sizeof(T) * 8) + + +template <class AtomicType> +static void TestCompareAndSwap() { + AtomicType value = 0; + AtomicType prev = base::subtle::NoBarrier_CompareAndSwap(&value, 0, 1); + EXPECT_EQ(1, value); + EXPECT_EQ(0, prev); + + // Use test value that has non-zero bits in both halves, more for testing + // 64-bit implementation on 32-bit platforms. + const AtomicType k_test_val = (GG_ULONGLONG(1) << + (NUM_BITS(AtomicType) - 2)) + 11; + value = k_test_val; + prev = base::subtle::NoBarrier_CompareAndSwap(&value, 0, 5); + EXPECT_EQ(k_test_val, value); + EXPECT_EQ(k_test_val, prev); + + value = k_test_val; + prev = base::subtle::NoBarrier_CompareAndSwap(&value, k_test_val, 5); + EXPECT_EQ(5, value); + EXPECT_EQ(k_test_val, prev); +} + + +template <class AtomicType> +static void TestAtomicExchange() { + AtomicType value = 0; + AtomicType new_value = base::subtle::NoBarrier_AtomicExchange(&value, 1); + EXPECT_EQ(1, value); + EXPECT_EQ(0, new_value); + + // Use test value that has non-zero bits in both halves, more for testing + // 64-bit implementation on 32-bit platforms. + const AtomicType k_test_val = (GG_ULONGLONG(1) << + (NUM_BITS(AtomicType) - 2)) + 11; + value = k_test_val; + new_value = base::subtle::NoBarrier_AtomicExchange(&value, k_test_val); + EXPECT_EQ(k_test_val, value); + EXPECT_EQ(k_test_val, new_value); + + value = k_test_val; + new_value = base::subtle::NoBarrier_AtomicExchange(&value, 5); + EXPECT_EQ(5, value); + EXPECT_EQ(k_test_val, new_value); +} + + +template <class AtomicType> +static void TestAtomicIncrementBounds() { + // Test increment at the half-width boundary of the atomic type. + // It is primarily for testing at the 32-bit boundary for 64-bit atomic type. + AtomicType test_val = GG_ULONGLONG(1) << (NUM_BITS(AtomicType) / 2); + AtomicType value = test_val - 1; + AtomicType new_value = base::subtle::NoBarrier_AtomicIncrement(&value, 1); + EXPECT_EQ(test_val, value); + EXPECT_EQ(value, new_value); + + base::subtle::NoBarrier_AtomicIncrement(&value, -1); + EXPECT_EQ(test_val - 1, value); +} + +// This is a simple sanity check that values are correct. Not testing +// atomicity +template <class AtomicType> +static void TestStore() { + const AtomicType kVal1 = static_cast<AtomicType>(0xa5a5a5a5a5a5a5a5LL); + const AtomicType kVal2 = static_cast<AtomicType>(-1); + + AtomicType value; + + base::subtle::NoBarrier_Store(&value, kVal1); + EXPECT_EQ(kVal1, value); + base::subtle::NoBarrier_Store(&value, kVal2); + EXPECT_EQ(kVal2, value); + + base::subtle::Acquire_Store(&value, kVal1); + EXPECT_EQ(kVal1, value); + base::subtle::Acquire_Store(&value, kVal2); + EXPECT_EQ(kVal2, value); + + base::subtle::Release_Store(&value, kVal1); + EXPECT_EQ(kVal1, value); + base::subtle::Release_Store(&value, kVal2); + EXPECT_EQ(kVal2, value); +} + +// This is a simple sanity check that values are correct. Not testing +// atomicity +template <class AtomicType> +static void TestLoad() { + const AtomicType kVal1 = static_cast<AtomicType>(0xa5a5a5a5a5a5a5a5LL); + const AtomicType kVal2 = static_cast<AtomicType>(-1); + + AtomicType value; + + value = kVal1; + EXPECT_EQ(kVal1, base::subtle::NoBarrier_Load(&value)); + value = kVal2; + EXPECT_EQ(kVal2, base::subtle::NoBarrier_Load(&value)); + + value = kVal1; + EXPECT_EQ(kVal1, base::subtle::Acquire_Load(&value)); + value = kVal2; + EXPECT_EQ(kVal2, base::subtle::Acquire_Load(&value)); + + value = kVal1; + EXPECT_EQ(kVal1, base::subtle::Release_Load(&value)); + value = kVal2; + EXPECT_EQ(kVal2, base::subtle::Release_Load(&value)); +} + +template <class AtomicType> +static void TestAtomicOps() { + TestCompareAndSwap<AtomicType>(); + TestAtomicExchange<AtomicType>(); + TestAtomicIncrementBounds<AtomicType>(); + TestStore<AtomicType>(); + TestLoad<AtomicType>(); +} + +static void TestCalloc(size_t n, size_t s, bool ok) { + char* p = reinterpret_cast<char*>(calloc(n, s)); + if (!ok) { + EXPECT_EQ(NULL, p) << "calloc(n, s) should not succeed"; + } else { + // TODO(__LB_SHELL__): This behavior is not defined by c++ standard. + // However it is possible that chromium expects it. We need to make sure + // these functions work the same way as expected by chromium when we override + // memory functions. + EXPECT_NE(reinterpret_cast<void*>(NULL), p) << + "calloc(n, s) should succeed"; + for (int i = 0; i < n*s; i++) { + EXPECT_EQ('\0', p[i]); + } + free(p); + } +} + +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) +// Exceptions are disabled on lbshell and Starboard. + +// A global test counter for number of times the NewHandler is called. +static int news_handled = 0; +static void TestNewHandler() { + ++news_handled; + throw std::bad_alloc(); +} + +// Because we compile without exceptions, we expect these will not throw. +static void TestOneNewWithoutExceptions(void* (*func)(size_t), + bool should_throw) { + // success test + try { + void* ptr = (*func)(kNotTooBig); + EXPECT_NE(reinterpret_cast<void*>(NULL), ptr) << + "allocation should not have failed."; + } catch(...) { + EXPECT_EQ(0, 1) << "allocation threw unexpected exception."; + } + + // failure test + try { + void* rv = (*func)(kTooBig); + EXPECT_EQ(NULL, rv); + EXPECT_FALSE(should_throw) << "allocation should have thrown."; + } catch(...) { + EXPECT_TRUE(should_throw) << "allocation threw unexpected exception."; + } +} + +static void TestNothrowNew(void* (*func)(size_t)) { + news_handled = 0; + + // test without new_handler: + std::new_handler saved_handler = std::set_new_handler(0); + TestOneNewWithoutExceptions(func, false); + + // test with new_handler: + std::set_new_handler(TestNewHandler); + TestOneNewWithoutExceptions(func, true); + EXPECT_EQ(news_handled, 1) << "nothrow new_handler was not called."; + std::set_new_handler(saved_handler); +} +#endif +} // namespace + +//----------------------------------------------------------------------------- + +TEST(Atomics, AtomicIncrementWord) { + TestAtomicIncrement<AtomicWord>(); +} + +TEST(Atomics, AtomicIncrement32) { + TestAtomicIncrement<Atomic32>(); +} + +TEST(Atomics, AtomicOpsWord) { + TestAtomicIncrement<AtomicWord>(); +} + +TEST(Atomics, AtomicOps32) { + TestAtomicIncrement<Atomic32>(); +} + +TEST(Allocators, Malloc) { + // Try allocating data with a bunch of alignments and sizes + for (int size = 1; size < 1048576; size *= 2) { + unsigned char* ptr = reinterpret_cast<unsigned char*>(malloc(size)); + CheckAlignment(ptr, 2); // Should be 2 byte aligned + Fill(ptr, size); + EXPECT_TRUE(Valid(ptr, size)); + free(ptr); + } +} + +TEST(Allocators, Calloc) { + TestCalloc(0, 0, true); + TestCalloc(0, 1, true); + TestCalloc(1, 1, true); + TestCalloc(1<<10, 0, true); + TestCalloc(1<<20, 0, true); + TestCalloc(0, 1<<10, true); + TestCalloc(0, 1<<20, true); + TestCalloc(1<<20, 2, true); + TestCalloc(2, 1<<20, true); + TestCalloc(1000, 1000, true); + + TestCalloc(kMaxSize, 2, false); + TestCalloc(2, kMaxSize, false); + TestCalloc(kMaxSize, kMaxSize, false); + + TestCalloc(kMaxSignedSize, 3, false); + TestCalloc(3, kMaxSignedSize, false); + TestCalloc(kMaxSignedSize, kMaxSignedSize, false); +} + +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) +// Exceptions are disabled on lbshell. +TEST(Allocators, New) { + TestNothrowNew(&::operator new); + TestNothrowNew(&::operator new[]); +} +#endif + +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) +// The test assumes the memory space to be very empty +// and doesn't work on lb_shell platforms. + +// This makes sure that reallocing a small number of bytes in either +// direction doesn't cause us to allocate new memory. +TEST(Allocators, Realloc1) { + int start_sizes[] = { 100, 1000, 10000, 100000 }; + int deltas[] = { 1, -2, 4, -8, 16, -32, 64, -128 }; + + for (int s = 0; s < sizeof(start_sizes)/sizeof(*start_sizes); ++s) { + void* p = malloc(start_sizes[s]); + ASSERT_TRUE(p); + // The larger the start-size, the larger the non-reallocing delta. + for (int d = 0; d < s*2; ++d) { + void* new_p = realloc(p, start_sizes[s] + deltas[d]); + ASSERT_EQ(p, new_p); // realloc should not allocate new memory + } + // Test again, but this time reallocing smaller first. + for (int d = 0; d < s*2; ++d) { + void* new_p = realloc(p, start_sizes[s] - deltas[d]); + ASSERT_EQ(p, new_p); // realloc should not allocate new memory + } + free(p); + } +} +#endif + +TEST(Allocators, Realloc2) { + for (int src_size = 0; src_size >= 0; src_size = NextSize(src_size)) { + for (int dst_size = 0; dst_size >= 0; dst_size = NextSize(dst_size)) { + unsigned char* src = reinterpret_cast<unsigned char*>(malloc(src_size)); + Fill(src, src_size); + unsigned char* dst = + reinterpret_cast<unsigned char*>(realloc(src, dst_size)); + EXPECT_TRUE(Valid(dst, min(src_size, dst_size))); + Fill(dst, dst_size); + EXPECT_TRUE(Valid(dst, dst_size)); + if (dst != NULL) free(dst); + } + } + + // Now make sure realloc works correctly even when we overflow the + // packed cache, so some entries are evicted from the cache. + // The cache has 2^12 entries, keyed by page number. + const int kNumEntries = 1 << 14; + int** p = reinterpret_cast<int**>(malloc(sizeof(*p) * kNumEntries)); + int sum = 0; + for (int i = 0; i < kNumEntries; i++) { + // no page size is likely to be bigger than 8192? + p[i] = reinterpret_cast<int*>(malloc(8192)); + p[i][1000] = i; // use memory deep in the heart of p + } + for (int i = 0; i < kNumEntries; i++) { + p[i] = reinterpret_cast<int*>(realloc(p[i], 9000)); + } + for (int i = 0; i < kNumEntries; i++) { + sum += p[i][1000]; + free(p[i]); + } + EXPECT_EQ(kNumEntries/2 * (kNumEntries - 1), sum); // assume kNE is even + free(p); +} + +// TODO(__LB_SHELL__): This behavior is not defined by c++ standard. +// However it is possible that chromium expects it. We need to make sure +// these functions work the same way as expected by chromium when we override +// memory functions. +TEST(Allocators, ReallocZero) { + // Test that realloc to zero does not return NULL. + for (int size = 0; size >= 0; size = NextSize(size)) { + char* ptr = reinterpret_cast<char*>(malloc(size)); + EXPECT_NE(static_cast<char*>(NULL), ptr); + ptr = reinterpret_cast<char*>(realloc(ptr, 0)); + EXPECT_NE(static_cast<char*>(NULL), ptr); + if (ptr) + free(ptr); + } +} + +#ifdef WIN32 +// Test recalloc +TEST(Allocators, Recalloc) { + for (int src_size = 0; src_size >= 0; src_size = NextSize(src_size)) { + for (int dst_size = 0; dst_size >= 0; dst_size = NextSize(dst_size)) { + unsigned char* src = + reinterpret_cast<unsigned char*>(_recalloc(NULL, 1, src_size)); + EXPECT_TRUE(IsZeroed(src, src_size)); + Fill(src, src_size); + unsigned char* dst = + reinterpret_cast<unsigned char*>(_recalloc(src, 1, dst_size)); + EXPECT_TRUE(Valid(dst, min(src_size, dst_size))); + Fill(dst, dst_size); + EXPECT_TRUE(Valid(dst, dst_size)); + if (dst != NULL) + free(dst); + } + } +} + +// Test windows specific _aligned_malloc() and _aligned_free() methods. +TEST(Allocators, AlignedMalloc) { + // Try allocating data with a bunch of alignments and sizes + static const int kTestAlignments[] = {8, 16, 256, 4096, 8192, 16384}; + for (int size = 1; size > 0; size = NextSize(size)) { + for (int i = 0; i < ARRAYSIZE(kTestAlignments); ++i) { + unsigned char* ptr = static_cast<unsigned char*>( + _aligned_malloc(size, kTestAlignments[i])); + CheckAlignment(ptr, kTestAlignments[i]); + Fill(ptr, size); + EXPECT_TRUE(Valid(ptr, size)); + + // Make a second allocation of the same size and alignment to prevent + // allocators from passing this test by accident. Per jar, tcmalloc + // provides allocations for new (never before seen) sizes out of a thread + // local heap of a given "size class." Each time the test requests a new + // size, it will usually get the first element of a span, which is a + // 4K aligned allocation. + unsigned char* ptr2 = static_cast<unsigned char*>( + _aligned_malloc(size, kTestAlignments[i])); + CheckAlignment(ptr2, kTestAlignments[i]); + Fill(ptr2, size); + EXPECT_TRUE(Valid(ptr2, size)); + + // Should never happen, but sanity check just in case. + ASSERT_NE(ptr, ptr2); + _aligned_free(ptr); + _aligned_free(ptr2); + } + } +} + +#endif + +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) +// main defined in run_all_unittests +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} +#endif
diff --git a/src/base/allocator/debugallocation_shim.cc b/src/base/allocator/debugallocation_shim.cc new file mode 100644 index 0000000..d1cf52a --- /dev/null +++ b/src/base/allocator/debugallocation_shim.cc
@@ -0,0 +1,9 @@ +// 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. + +#if defined(TCMALLOC_FOR_DEBUGALLOCATION) +#include "third_party/tcmalloc/chromium/src/debugallocation.cc" +#else +#include "third_party/tcmalloc/chromium/src/tcmalloc.cc" +#endif
diff --git a/src/base/allocator/generic_allocators.cc b/src/base/allocator/generic_allocators.cc new file mode 100644 index 0000000..d4cf19e --- /dev/null +++ b/src/base/allocator/generic_allocators.cc
@@ -0,0 +1,168 @@ +// Copyright (c) 2009 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. + +// When possible, we implement allocator functions on top of the basic +// low-level functions malloc() and free(). This way, including a new +// allocator is as simple as providing just a small interface. +// +// As such, this file should not contain any allocator-specific code. + +// Implement a C++ style allocation, which always calls the new_handler +// on failure. +inline void* generic_cpp_alloc(size_t size, bool nothrow) { + void* ptr; + for (;;) { + ptr = malloc(size); + if (ptr) + return ptr; + if (!call_new_handler(nothrow)) + break; + } + return ptr; +} + +extern "C++" { + +void* __cdecl operator new(size_t size) { + return generic_cpp_alloc(size, false); +} + +void operator delete(void* p) __THROW { + free(p); +} + +void* operator new[](size_t size) { + return generic_cpp_alloc(size, false); +} + +void operator delete[](void* p) __THROW { + free(p); +} + +void* operator new(size_t size, const std::nothrow_t& nt) __THROW { + return generic_cpp_alloc(size, true); +} + +void* operator new[](size_t size, const std::nothrow_t& nt) __THROW { + return generic_cpp_alloc(size, true); +} + +// This function behaves similarly to MSVC's _set_new_mode. +// If flag is 0 (default), calls to malloc will behave normally. +// If flag is 1, calls to malloc will behave like calls to new, +// and the std_new_handler will be invoked on failure. +// Returns the previous mode. +int _set_new_mode(int flag) __THROW { + int old_mode = new_mode; + new_mode = flag; + return old_mode; +} + +} // extern "C++" + +extern "C" { + +void* calloc(size_t n, size_t elem_size) __THROW { + // Overflow check + const size_t size = n * elem_size; + if (elem_size != 0 && size / elem_size != n) return NULL; + + void* result = malloc(size); + if (result != NULL) { + memset(result, 0, size); + } + return result; +} + +void cfree(void* p) __THROW { + free(p); +} + +#ifdef WIN32 + +void* _recalloc(void* p, size_t n, size_t elem_size) { + if (!p) + return calloc(n, elem_size); + + // This API is a bit odd. + // Note: recalloc only guarantees zeroed memory when p is NULL. + // Generally, calls to malloc() have padding. So a request + // to malloc N bytes actually malloc's N+x bytes. Later, if + // that buffer is passed to recalloc, we don't know what N + // was anymore. We only know what N+x is. As such, there is + // no way to know what to zero out. + const size_t size = n * elem_size; + if (elem_size != 0 && size / elem_size != n) return NULL; + return realloc(p, size); +} + +void* _calloc_impl(size_t n, size_t size) { + return calloc(n, size); +} + +#ifndef NDEBUG +#undef malloc +#undef free +#undef calloc + +static int error_handler(int reportType) { + switch (reportType) { + case 0: // _CRT_WARN + __debugbreak(); + return 0; + + case 1: // _CRT_ERROR + __debugbreak(); + return 0; + + case 2: // _CRT_ASSERT + __debugbreak(); + return 0; + } + char* p = NULL; + *p = '\0'; + return 0; +} + +int _CrtDbgReport(int reportType, + const char*, + int, const char*, + const char*, + ...) { + return error_handler(reportType); +} + +int _CrtDbgReportW(int reportType, + const wchar_t*, + int, const wchar_t*, + const wchar_t*, + ...) { + return error_handler(reportType); +} + +int _CrtSetReportMode(int, int) { + return 0; +} + +void* _malloc_dbg(size_t size, int , const char*, int) { + return malloc(size); +} + +void* _realloc_dbg(void* ptr, size_t size, int, const char*, int) { + return realloc(ptr, size); +} + +void _free_dbg(void* ptr, int) { + free(ptr); +} + +void* _calloc_dbg(size_t n, size_t size, int, const char*, int) { + return calloc(n, size); +} +#endif // NDEBUG + +#endif // WIN32 + +} // extern C +
diff --git a/src/base/allocator/prep_libc.py b/src/base/allocator/prep_libc.py new file mode 100755 index 0000000..be7030f --- /dev/null +++ b/src/base/allocator/prep_libc.py
@@ -0,0 +1,57 @@ +#!/usr/bin/env python + +# 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. +# +# This script takes libcmt.lib for VS2005/08/10 and removes the allocation +# related functions from it. +# +# Usage: prep_libc.py <VCInstallDir> <OutputDir> +# +# VCInstallDir is the path where VC is installed, something like: +# C:\Program Files\Microsoft Visual Studio 8\VC\ +# +# OutputDir is the directory where the modified libcmt file should be stored. + +import os +import shutil +import subprocess +import sys + +def run(command, filter=None): + """Run |command|, removing any lines that match |filter|. The filter is + to remove the echoing of input filename that 'lib' does.""" + popen = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out, _ = popen.communicate() + for line in out.splitlines(): + if filter and line.strip() != filter: + print line + return popen.returncode + +def main(): + vs_install_dir = sys.argv[1] + outdir = sys.argv[2] + output_lib = os.path.join(outdir, 'libcmt.lib') + shutil.copyfile(os.path.join(vs_install_dir, 'libcmt.lib'), output_lib) + shutil.copyfile(os.path.join(vs_install_dir, 'libcmt.pdb'), + os.path.join(outdir, 'libcmt.pdb')) + vspaths = [ + 'build\\intel\\mt_obj\\', + 'f:\\dd\\vctools\\crt_bld\\SELF_X86\\crt\\src\\build\\INTEL\\mt_obj\\', + 'F:\\dd\\vctools\\crt_bld\\SELF_X86\\crt\\src\\build\\INTEL\\mt_obj\\nativec\\\\', + 'F:\\dd\\vctools\\crt_bld\\SELF_X86\\crt\\src\\build\\INTEL\\mt_obj\\nativecpp\\\\', + ] + objfiles = ['malloc', 'free', 'realloc', 'new', 'delete', 'new2', 'delete2', + 'align', 'msize', 'heapinit', 'expand', 'heapchk', 'heapwalk', + 'heapmin', 'sbheap', 'calloc', 'recalloc', 'calloc_impl', + 'new_mode', 'newopnt'] + for obj in objfiles: + for vspath in vspaths: + cmd = ('lib /nologo /ignore:4006,4014,4221 /remove:%s%s.obj %s' % + (vspath, obj, output_lib)) + run(cmd, obj + '.obj') + +if __name__ == "__main__": + sys.exit(main())
diff --git a/src/base/allocator/tcmalloc_unittest.cc b/src/base/allocator/tcmalloc_unittest.cc new file mode 100644 index 0000000..053a9d5 --- /dev/null +++ b/src/base/allocator/tcmalloc_unittest.cc
@@ -0,0 +1,81 @@ +// 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 <stdio.h> +#include "base/allocator/allocator_shim.h" + +#include "testing/gtest/include/gtest/gtest.h" + +// TCMalloc header files +#include "common.h" // For TCMalloc constants like page size, etc. + +using base::allocator::TCMallocDoMallocForTest; +using base::allocator::TCMallocDoFreeForTest; +using base::allocator::ExcludeSpaceForMarkForTest; + +TEST(TCMallocFreeCheck, BadPointerInFirstPageOfTheLargeObject) { + char* p = reinterpret_cast<char*>( + TCMallocDoMallocForTest(ExcludeSpaceForMarkForTest(kMaxSize + 1))); + for (int offset = 1; offset < kPageSize ; offset <<= 1) { + ASSERT_DEATH(TCMallocDoFreeForTest(p + offset), + "Pointer is not pointing to the start of a span"); + } +} + +TEST(TCMallocFreeCheck, BadPageAlignedPointerInsideLargeObject) { + char* p = reinterpret_cast<char*>( + TCMallocDoMallocForTest(ExcludeSpaceForMarkForTest(kMaxSize + 1))); + + for (int offset = kPageSize; offset < kMaxSize; offset += kPageSize) { + // Only the first and last page of a span are in heap map. So for others + // tcmalloc will give a general error of invalid pointer. + ASSERT_DEATH(TCMallocDoFreeForTest(p + offset), + "Attempt to free invalid pointer"); + } + ASSERT_DEATH(TCMallocDoFreeForTest(p + kMaxSize), + "Pointer is not pointing to the start of a span"); +} + +TEST(TCMallocFreeCheck, DoubleFreeLargeObject) { + char* p = reinterpret_cast<char*>( + TCMallocDoMallocForTest(ExcludeSpaceForMarkForTest(kMaxSize + 1))); + ASSERT_DEATH(TCMallocDoFreeForTest(p); TCMallocDoFreeForTest(p), + "Object was not in-use"); +} + + +#ifdef NDEBUG +TEST(TCMallocFreeCheck, DoubleFreeSmallObject) { + for (size_t size = 1; + size <= ExcludeSpaceForMarkForTest(kMaxSize); + size <<= 1) { + char* p = reinterpret_cast<char*>(TCMallocDoMallocForTest(size)); + ASSERT_DEATH(TCMallocDoFreeForTest(p); TCMallocDoFreeForTest(p), + "Circular loop in list detected"); + } +} +#else +TEST(TCMallocFreeCheck, DoubleFreeSmallObject) { + size_t size = 1; + + // When the object is small, tcmalloc validation can not distinguish normal + // memory corruption or double free, because there's not enough space in + // freed objects to keep the mark. + for (; size <= ExcludeSpaceForMarkForTest(kMinClassSize); size <<= 1) { + char* p = reinterpret_cast<char*>(TCMallocDoMallocForTest(size)); + ASSERT_DEATH(TCMallocDoFreeForTest(p); TCMallocDoFreeForTest(p), + "Memory corrupted"); + } + + for (; size <= ExcludeSpaceForMarkForTest(kMaxSize); size <<= 1) { + char* p = reinterpret_cast<char*>(TCMallocDoMallocForTest(size)); + ASSERT_DEATH(TCMallocDoFreeForTest(p); TCMallocDoFreeForTest(p), + "Attempt to double free"); + } +} +#endif + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}
diff --git a/src/base/allocator/type_profiler.cc b/src/base/allocator/type_profiler.cc new file mode 100644 index 0000000..635fbcf --- /dev/null +++ b/src/base/allocator/type_profiler.cc
@@ -0,0 +1,63 @@ +// Copyright 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. + +#if defined(TYPE_PROFILING) + +#include "base/allocator/type_profiler.h" + +#include <assert.h> + +namespace { + +void* NopIntercept(void* ptr, size_t size, const std::type_info& type) { + return ptr; +} + +base::type_profiler::InterceptFunction* g_new_intercept = NopIntercept; +base::type_profiler::InterceptFunction* g_delete_intercept = NopIntercept; + +} + +void* __op_new_intercept__(void* ptr, + size_t size, + const std::type_info& type) { + return g_new_intercept(ptr, size, type); +} + +void* __op_delete_intercept__(void* ptr, + size_t size, + const std::type_info& type) { + return g_delete_intercept(ptr, size, type); +} + +namespace base { +namespace type_profiler { + +// static +void InterceptFunctions::SetFunctions(InterceptFunction* new_intercept, + InterceptFunction* delete_intercept) { + // Don't use DCHECK, as this file is injected into targets + // that do not and should not depend on base/base.gyp:base + assert(g_new_intercept == NopIntercept); + assert(g_delete_intercept == NopIntercept); + + g_new_intercept = new_intercept; + g_delete_intercept = delete_intercept; +} + +// static +void InterceptFunctions::ResetFunctions() { + g_new_intercept = NopIntercept; + g_delete_intercept = NopIntercept; +} + +// static +bool InterceptFunctions::IsAvailable() { + return g_new_intercept != NopIntercept || g_delete_intercept != NopIntercept; +} + +} // namespace type_profiler +} // namespace base + +#endif // defined(TYPE_PROFILING)
diff --git a/src/base/allocator/type_profiler.h b/src/base/allocator/type_profiler.h new file mode 100644 index 0000000..86b5711 --- /dev/null +++ b/src/base/allocator/type_profiler.h
@@ -0,0 +1,40 @@ +// Copyright 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_ALLOCATOR_TYPE_PROFILER_H_ +#define BASE_ALLOCATOR_TYPE_PROFILER_H_ + +#if defined(TYPE_PROFILING) + +#include <stddef.h> // for size_t +#include <typeinfo> // for std::typeinfo + +namespace base { +namespace type_profiler { + +typedef void* InterceptFunction(void*, size_t, const std::type_info&); + +class InterceptFunctions { + public: + // It must be called only once in a process while it is in single-thread. + // For now, ContentMainRunnerImpl::Initialize is the only supposed caller + // of this function except for single-threaded unit tests. + static void SetFunctions(InterceptFunction* new_intercept, + InterceptFunction* delete_intercept); + + private: + friend class TypeProfilerTest; + + // These functions are not thread safe. + // They must be used only from single-threaded unit tests. + static void ResetFunctions(); + static bool IsAvailable(); +}; + +} // namespace type_profiler +} // namespace base + +#endif // defined(TYPE_PROFILING) + +#endif // BASE_ALLOCATOR_TYPE_PROFILER_H_
diff --git a/src/base/allocator/type_profiler_control.cc b/src/base/allocator/type_profiler_control.cc new file mode 100644 index 0000000..6be7984 --- /dev/null +++ b/src/base/allocator/type_profiler_control.cc
@@ -0,0 +1,38 @@ +// Copyright 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/allocator/type_profiler_control.h" + +namespace base { +namespace type_profiler { + +namespace { + +#if defined(TYPE_PROFILING) +const bool kTypeProfilingEnabled = true; +#else +const bool kTypeProfilingEnabled = false; +#endif + +bool g_enable_intercept = kTypeProfilingEnabled; + +} // namespace + +// static +void Controller::Stop() { + g_enable_intercept = false; +} + +// static +bool Controller::IsProfiling() { + return kTypeProfilingEnabled && g_enable_intercept; +} + +// static +void Controller::Restart() { + g_enable_intercept = kTypeProfilingEnabled; +} + +} // namespace type_profiler +} // namespace base
diff --git a/src/base/allocator/type_profiler_control.h b/src/base/allocator/type_profiler_control.h new file mode 100644 index 0000000..17cf5b6 --- /dev/null +++ b/src/base/allocator/type_profiler_control.h
@@ -0,0 +1,31 @@ +// Copyright 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_ALLOCATOR_TYPE_PROFILER_CONTROL_H_ +#define BASE_ALLOCATOR_TYPE_PROFILER_CONTROL_H_ + +#include "base/gtest_prod_util.h" + +namespace base { +namespace type_profiler { + +class Controller { + public: + static void Stop(); + static bool IsProfiling(); + + private: + FRIEND_TEST_ALL_PREFIXES(TypeProfilerTest, + TestProfileNewWithoutProfiledDelete); + + // It must be used only from allowed unit tests. The following is only + // allowed for use in unit tests. Profiling should never be restarted in + // regular use. + static void Restart(); +}; + +} // namespace type_profiler +} // namespace base + +#endif // BASE_ALLOCATOR_TYPE_PROFILER_CONTROL_H_
diff --git a/src/base/allocator/type_profiler_map_unittests.cc b/src/base/allocator/type_profiler_map_unittests.cc new file mode 100644 index 0000000..ff74e11 --- /dev/null +++ b/src/base/allocator/type_profiler_map_unittests.cc
@@ -0,0 +1,99 @@ +// Copyright 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. + +// This is a unittest set for type_profiler_map in third_party/tcmalloc. It is +// independent from other tests and executed manually like allocator_unittests +// since type_profiler_map is a singleton (like TCMalloc's heap-profiler), and +// it requires RTTI and different compiling/linking options from others. + +#if defined(TYPE_PROFILING) + +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/tcmalloc/chromium/src/gperftools/type_profiler_map.h" + +namespace base { +namespace type_profiler { + +static const void* const g_const_null = static_cast<const void*>(NULL); + +TEST(TypeProfilerMapTest, NormalOperation) { + // Allocate an object just to get a valid address. + // This 'new' is not profiled by type_profiler. + scoped_ptr<int> dummy(new int(48)); + const std::type_info* type; + + type = LookupType(dummy.get()); + EXPECT_EQ(g_const_null, type); + + InsertType(dummy.get(), 12, typeid(int)); + type = LookupType(dummy.get()); + ASSERT_NE(g_const_null, type); + EXPECT_STREQ(typeid(int).name(), type->name()); + + EraseType(dummy.get()); + type = LookupType(dummy.get()); + EXPECT_EQ(g_const_null, type); +} + +TEST(TypeProfilerMapTest, EraseWithoutInsert) { + scoped_ptr<int> dummy(new int(48)); + const std::type_info* type; + + for (int i = 0; i < 10; ++i) { + EraseType(dummy.get()); + type = LookupType(dummy.get()); + EXPECT_EQ(g_const_null, type); + } +} + +TEST(TypeProfilerMapTest, InsertThenMultipleErase) { + scoped_ptr<int> dummy(new int(48)); + const std::type_info* type; + + InsertType(dummy.get(), 12, typeid(int)); + type = LookupType(dummy.get()); + ASSERT_NE(g_const_null, type); + EXPECT_STREQ(typeid(int).name(), type->name()); + + for (int i = 0; i < 10; ++i) { + EraseType(dummy.get()); + type = LookupType(dummy.get()); + EXPECT_EQ(g_const_null, type); + } +} + +TEST(TypeProfilerMapTest, MultipleInsertWithoutErase) { + scoped_ptr<int> dummy(new int(48)); + const std::type_info* type; + + InsertType(dummy.get(), 12, typeid(int)); + type = LookupType(dummy.get()); + ASSERT_NE(g_const_null, type); + EXPECT_STREQ(typeid(int).name(), type->name()); + + InsertType(dummy.get(), 5, typeid(char)); + type = LookupType(dummy.get()); + ASSERT_NE(g_const_null, type); + EXPECT_STREQ(typeid(char).name(), type->name()); + + InsertType(dummy.get(), 129, typeid(long)); + type = LookupType(dummy.get()); + ASSERT_NE(g_const_null, type); + EXPECT_STREQ(typeid(long).name(), type->name()); + + EraseType(dummy.get()); + type = LookupType(dummy.get()); + EXPECT_EQ(g_const_null, type); +} + +} // namespace type_profiler +} // namespace base + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +#endif // defined(TYPE_PROFILING)
diff --git a/src/base/allocator/type_profiler_tcmalloc.cc b/src/base/allocator/type_profiler_tcmalloc.cc new file mode 100644 index 0000000..e5e10e0 --- /dev/null +++ b/src/base/allocator/type_profiler_tcmalloc.cc
@@ -0,0 +1,37 @@ +// Copyright 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. + +#if defined(TYPE_PROFILING) + +#include "base/allocator/type_profiler_tcmalloc.h" + +#include "base/allocator/type_profiler_control.h" +#include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h" +#include "third_party/tcmalloc/chromium/src/gperftools/type_profiler_map.h" + +namespace base { +namespace type_profiler { + +void* NewInterceptForTCMalloc(void* ptr, + size_t size, + const std::type_info& type) { + if (Controller::IsProfiling()) + InsertType(ptr, size, type); + + return ptr; +} + +void* DeleteInterceptForTCMalloc(void* ptr, + size_t size, + const std::type_info& type) { + if (Controller::IsProfiling()) + EraseType(ptr); + + return ptr; +} + +} // namespace type_profiler +} // namespace base + +#endif // defined(TYPE_PROFILING)
diff --git a/src/base/allocator/type_profiler_tcmalloc.h b/src/base/allocator/type_profiler_tcmalloc.h new file mode 100644 index 0000000..ac55995 --- /dev/null +++ b/src/base/allocator/type_profiler_tcmalloc.h
@@ -0,0 +1,29 @@ +// Copyright 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_ALLOCATOR_TYPE_PROFILER_TCMALLOC_H_ +#define BASE_ALLOCATOR_TYPE_PROFILER_TCMALLOC_H_ + +#if defined(TYPE_PROFILING) + +#include <cstddef> // for size_t +#include <typeinfo> // for std::type_info + +namespace base { +namespace type_profiler { + +void* NewInterceptForTCMalloc(void* ptr, + size_t size, + const std::type_info& type); + +void* DeleteInterceptForTCMalloc(void* ptr, + size_t size, + const std::type_info& type); + +} // namespace type_profiler +} // namespace base + +#endif // defined(TYPE_PROFILING) + +#endif // BASE_ALLOCATOR_TYPE_PROFILER_TCMALLOC_H_
diff --git a/src/base/allocator/type_profiler_unittests.cc b/src/base/allocator/type_profiler_unittests.cc new file mode 100644 index 0000000..16af86d --- /dev/null +++ b/src/base/allocator/type_profiler_unittests.cc
@@ -0,0 +1,189 @@ +// Copyright 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. + +// This is a unittest set for type_profiler. It is independent from other +// tests and executed manually like allocator_unittests since type_profiler_map +// used in type_profiler is a singleton (like TCMalloc's heap-profiler), and +// it requires RTTI and different compiling/linking options from others +// +// It tests that the profiler doesn't fail in suspicous cases. For example, +// 'new' is not profiled, but 'delete' for the created object is profiled. + +#if defined(TYPE_PROFILING) + +#include "base/allocator/type_profiler.h" +#include "base/allocator/type_profiler_control.h" +#include "base/allocator/type_profiler_tcmalloc.h" +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/tcmalloc/chromium/src/gperftools/type_profiler_map.h" + +namespace base { +namespace type_profiler { + +class TypeProfilerTest : public testing::Test { + public: + TypeProfilerTest() {} + + void SetInterceptFunctions() { + InterceptFunctions::SetFunctions(NewInterceptForTCMalloc, + DeleteInterceptForTCMalloc); + } + + void ResetInterceptFunctions() { + InterceptFunctions::ResetFunctions(); + } + + void SetUp() { + SetInterceptFunctions(); + } + + void TearDown() { + ResetInterceptFunctions(); + } + + protected: + static const size_t kDummyArraySize; + static const void* const kConstNull; + + private: + DISALLOW_COPY_AND_ASSIGN(TypeProfilerTest); +}; + +const size_t TypeProfilerTest::kDummyArraySize = 10; +const void* const TypeProfilerTest::kConstNull = static_cast<const void*>(NULL); + +TEST_F(TypeProfilerTest, TestNormalProfiling) { + int* dummy = new int(48); + const std::type_info* type; + + type = LookupType(dummy); + ASSERT_NE(kConstNull, type); + EXPECT_STREQ(typeid(int).name(), type->name()); + delete dummy; + + type = LookupType(dummy); + EXPECT_EQ(kConstNull, type); +} + +TEST_F(TypeProfilerTest, TestNormalArrayProfiling) { + int* dummy = new int[kDummyArraySize]; + const std::type_info* type; + + type = LookupType(dummy); + ASSERT_NE(kConstNull, type); + // For an array, the profiler remembers its base type. + EXPECT_STREQ(typeid(int).name(), type->name()); + delete[] dummy; + + type = LookupType(dummy); + EXPECT_EQ(kConstNull, type); +} + +TEST_F(TypeProfilerTest, TestRepeatedNewAndDelete) { + int *dummy[kDummyArraySize]; + const std::type_info* type; + for (int i = 0; i < kDummyArraySize; ++i) + dummy[i] = new int(i); + + for (int i = 0; i < kDummyArraySize; ++i) { + type = LookupType(dummy[i]); + ASSERT_NE(kConstNull, type); + EXPECT_STREQ(typeid(int).name(), type->name()); + } + + for (int i = 0; i < kDummyArraySize; ++i) { + delete dummy[i]; + type = LookupType(dummy[i]); + ASSERT_EQ(kConstNull, type); + } +} + +TEST_F(TypeProfilerTest, TestMultipleNewWithDroppingDelete) { + static const size_t large_size = 256 * 1024; + + char* dummy_char = new char[large_size / sizeof(*dummy_char)]; + const std::type_info* type; + + type = LookupType(dummy_char); + ASSERT_NE(kConstNull, type); + EXPECT_STREQ(typeid(char).name(), type->name()); + + // Call "::operator delete" directly to drop __op_delete_intercept__. + ::operator delete[](dummy_char); + + type = LookupType(dummy_char); + ASSERT_NE(kConstNull, type); + EXPECT_STREQ(typeid(char).name(), type->name()); + + // Allocates a little different size. + int* dummy_int = new int[large_size / sizeof(*dummy_int) - 1]; + + // We expect that tcmalloc returns the same address for these large (over 32k) + // allocation calls. It usually happens, but maybe probablistic. + ASSERT_EQ(static_cast<void*>(dummy_char), static_cast<void*>(dummy_int)) << + "two new (malloc) calls didn't return the same address; retry it."; + + type = LookupType(dummy_int); + ASSERT_NE(kConstNull, type); + EXPECT_STREQ(typeid(int).name(), type->name()); + + delete[] dummy_int; + + type = LookupType(dummy_int); + EXPECT_EQ(kConstNull, type); +} + +TEST_F(TypeProfilerTest, TestProfileDeleteWithoutProfiledNew) { + // 'dummy' should be new'ed in this test before intercept functions are set. + ResetInterceptFunctions(); + + int* dummy = new int(48); + const std::type_info* type; + + // Set intercept functions again after 'dummy' is new'ed. + SetInterceptFunctions(); + + delete dummy; + + type = LookupType(dummy); + EXPECT_EQ(kConstNull, type); + + ResetInterceptFunctions(); +} + +TEST_F(TypeProfilerTest, TestProfileNewWithoutProfiledDelete) { + int* dummy = new int(48); + const std::type_info* type; + + EXPECT_TRUE(Controller::IsProfiling()); + + // Stop profiling before deleting 'dummy'. + Controller::Stop(); + EXPECT_FALSE(Controller::IsProfiling()); + + delete dummy; + + // NOTE: We accept that a profile entry remains when a profiled object is + // deleted after Controller::Stop(). + type = LookupType(dummy); + ASSERT_NE(kConstNull, type); + EXPECT_STREQ(typeid(int).name(), type->name()); + + Controller::Restart(); + EXPECT_TRUE(Controller::IsProfiling()); + + // Remove manually since 'dummy' is not removed from type_profiler_map. + EraseType(dummy); +} + +} // namespace type_profiler +} // namespace base + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +#endif // defined(TYPE_PROFILING)
diff --git a/src/base/allocator/unittest_utils.cc b/src/base/allocator/unittest_utils.cc new file mode 100644 index 0000000..130ba15 --- /dev/null +++ b/src/base/allocator/unittest_utils.cc
@@ -0,0 +1,18 @@ +// Copyright (c) 2009 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. + +// The unittests need a this in order to link up without pulling in tons +// of other libraries + +#include <config.h> + +inline int snprintf(char* buffer, size_t count, const char* format, ...) { + int result; + va_list args; + va_start(args, format); + result = _vsnprintf(buffer, count, format, args); + va_end(args); + return result; +} +
diff --git a/src/base/allocator/win_allocator.cc b/src/base/allocator/win_allocator.cc new file mode 100644 index 0000000..899b867 --- /dev/null +++ b/src/base/allocator/win_allocator.cc
@@ -0,0 +1,73 @@ +// 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. + +// This is a simple allocator based on the windows heap. + +extern "C" { + +HANDLE win_heap; + +bool win_heap_init(bool use_lfh) { + win_heap = HeapCreate(0, 0, 0); + if (win_heap == NULL) + return false; + + if (use_lfh) { + ULONG enable_lfh = 2; + HeapSetInformation(win_heap, HeapCompatibilityInformation, + &enable_lfh, sizeof(enable_lfh)); + // NOTE: Setting LFH may fail. Vista already has it enabled. + // And under the debugger, it won't use LFH. So we + // ignore any errors. + } + + return true; +} + +void* win_heap_malloc(size_t size) { + return HeapAlloc(win_heap, 0, size); +} + +void win_heap_free(void* size) { + HeapFree(win_heap, 0, size); +} + +void* win_heap_realloc(void* ptr, size_t size) { + if (!ptr) + return win_heap_malloc(size); + if (!size) { + win_heap_free(ptr); + return NULL; + } + return HeapReAlloc(win_heap, 0, ptr, size); +} + +size_t win_heap_msize(void* ptr) { + return HeapSize(win_heap, 0, ptr); +} + +void* win_heap_memalign(size_t alignment, size_t size) { + // Reserve enough space to ensure we can align and set aligned_ptr[-1] to the + // original allocation for use with win_heap_memalign_free() later. + size_t allocation_size = size + (alignment - 1) + sizeof(void*); + + // Check for overflow. Alignment and size are checked in allocator_shim. + DCHECK_LT(size, allocation_size); + DCHECK_LT(alignment, allocation_size); + + void* ptr = win_heap_malloc(allocation_size); + char* aligned_ptr = static_cast<char*>(ptr) + sizeof(void*); + aligned_ptr += + alignment - reinterpret_cast<uintptr_t>(aligned_ptr) & (alignment - 1); + + reinterpret_cast<void**>(aligned_ptr)[-1] = ptr; + return aligned_ptr; +} + +void win_heap_memalign_free(void* ptr) { + if (ptr) + win_heap_free(static_cast<void**>(ptr)[-1]); +} + +} // extern "C"
diff --git a/src/base/android/base_jni_registrar.cc b/src/base/android/base_jni_registrar.cc new file mode 100644 index 0000000..2395d98 --- /dev/null +++ b/src/base/android/base_jni_registrar.cc
@@ -0,0 +1,37 @@ +// 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/android/base_jni_registrar.h" + +#include "base/basictypes.h" +#include "base/android/build_info.h" +#include "base/android/cpu_features.h" +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "base/android/locale_utils.h" +#include "base/android/path_service_android.h" +#include "base/android/path_utils.h" +#include "base/message_pump_android.h" +#include "base/system_monitor/system_monitor_android.h" + +namespace base { +namespace android { + +static RegistrationMethod kBaseRegisteredMethods[] = { + { "BuildInfo", base::android::BuildInfo::RegisterBindings }, + { "CpuFeatures", base::android::RegisterCpuFeatures }, + { "LocaleUtils", base::android::RegisterLocaleUtils }, + { "PathService", base::android::RegisterPathService }, + { "PathUtils", base::android::RegisterPathUtils }, + { "SystemMessageHandler", base::MessagePumpForUI::RegisterBindings }, + { "SystemMonitor", base::RegisterSystemMonitor }, +}; + +bool RegisterJni(JNIEnv* env) { + return RegisterNativeMethods(env, kBaseRegisteredMethods, + arraysize(kBaseRegisteredMethods)); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/base_jni_registrar.h b/src/base/android/base_jni_registrar.h new file mode 100644 index 0000000..fdaf5f2 --- /dev/null +++ b/src/base/android/base_jni_registrar.h
@@ -0,0 +1,21 @@ +// 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_ANDROID_BASE_JNI_REGISTRAR_H_ +#define BASE_ANDROID_BASE_JNI_REGISTRAR_H_ + +#include <jni.h> + +#include "base/base_export.h" + +namespace base { +namespace android { + +// Register all JNI bindings necessary for base. +BASE_EXPORT bool RegisterJni(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_BASE_JNI_REGISTRAR_H_
diff --git a/src/base/android/build_info.cc b/src/base/android/build_info.cc new file mode 100644 index 0000000..cdde6a9 --- /dev/null +++ b/src/base/android/build_info.cc
@@ -0,0 +1,78 @@ +// 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/android/build_info.h" + +#include <string> + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "jni/BuildInfo_jni.h" + +namespace { + +// The caller takes ownership of the returned const char*. +const char* StrDupJString(const base::android::JavaRef<jstring>& java_string) { + std::string str = ConvertJavaStringToUTF8(java_string); + return strdup(str.c_str()); +} + +} // namespace + +namespace base { +namespace android { + +struct BuildInfoSingletonTraits { + static BuildInfo* New() { + return new BuildInfo(AttachCurrentThread()); + } + + static void Delete(BuildInfo* x) { + // We're leaking this type, see kRegisterAtExit. + NOTREACHED(); + } + + static const bool kRegisterAtExit = false; + static const bool kAllowedToAccessOnNonjoinableThread = true; +}; + +BuildInfo::BuildInfo(JNIEnv* env) + : device_(StrDupJString(Java_BuildInfo_getDevice(env))), + model_(StrDupJString(Java_BuildInfo_getDeviceModel(env))), + brand_(StrDupJString(Java_BuildInfo_getBrand(env))), + android_build_id_(StrDupJString(Java_BuildInfo_getAndroidBuildId(env))), + android_build_fp_(StrDupJString( + Java_BuildInfo_getAndroidBuildFingerprint(env))), + package_version_code_(StrDupJString(Java_BuildInfo_getPackageVersionCode( + env, GetApplicationContext()))), + package_version_name_(StrDupJString(Java_BuildInfo_getPackageVersionName( + env, GetApplicationContext()))), + package_label_(StrDupJString(Java_BuildInfo_getPackageLabel( + env, GetApplicationContext()))), + package_name_(StrDupJString(Java_BuildInfo_getPackageName( + env, GetApplicationContext()))), + sdk_int_(Java_BuildInfo_getSdkInt(env)), + java_exception_info_(NULL) { +} + +// static +BuildInfo* BuildInfo::GetInstance() { + return Singleton<BuildInfo, BuildInfoSingletonTraits >::get(); +} + +void BuildInfo::set_java_exception_info(const std::string& info) { + DCHECK(!java_exception_info_) << "info should be set only once."; + java_exception_info_ = strndup(info.c_str(), 1024); +} + +// static +bool BuildInfo::RegisterBindings(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/build_info.h b/src/base/android/build_info.h new file mode 100644 index 0000000..6273d3e --- /dev/null +++ b/src/base/android/build_info.h
@@ -0,0 +1,114 @@ +// 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_ANDROID_BUILD_INFO_H_ +#define BASE_ANDROID_BUILD_INFO_H_ + +#include <jni.h> + +#include <string> + +#include "base/memory/singleton.h" + +namespace base { +namespace android { + +// BuildInfo is a singleton class that stores android build and device +// information. It will be called from Android specific code and gets used +// primarily in crash reporting. + +// It is also used to store the last java exception seen during JNI. +// TODO(nileshagrawal): Find a better place to store this info. +class BuildInfo { + public: + + ~BuildInfo() {} + + // Static factory method for getting the singleton BuildInfo instance. + // Note that ownership is not conferred on the caller and the BuildInfo in + // question isn't actually freed until shutdown. This is ok because there + // should only be one instance of BuildInfo ever created. + static BuildInfo* GetInstance(); + + // Const char* is used instead of std::strings because these values must be + // available even if the process is in a crash state. Sadly + // std::string.c_str() doesn't guarantee that memory won't be allocated when + // it is called. + const char* device() const { + return device_; + } + + const char* model() const { + return model_; + } + + const char* brand() const { + return brand_; + } + + const char* android_build_id() const { + return android_build_id_; + } + + const char* android_build_fp() const { + return android_build_fp_; + } + + const char* package_version_code() const { + return package_version_code_; + } + + const char* package_version_name() const { + return package_version_name_; + } + + const char* package_label() const { + return package_label_; + } + + const char* package_name() const { + return package_name_; + } + + int sdk_int() const { + return sdk_int_; + } + + const char* java_exception_info() const { + return java_exception_info_; + } + + void set_java_exception_info(const std::string& info); + + static bool RegisterBindings(JNIEnv* env); + + private: + friend struct BuildInfoSingletonTraits; + + explicit BuildInfo(JNIEnv* env); + + // Const char* is used instead of std::strings because these values must be + // available even if the process is in a crash state. Sadly + // std::string.c_str() doesn't guarantee that memory won't be allocated when + // it is called. + const char* const device_; + const char* const model_; + const char* const brand_; + const char* const android_build_id_; + const char* const android_build_fp_; + const char* const package_version_code_; + const char* const package_version_name_; + const char* const package_label_; + const char* const package_name_; + const int sdk_int_; + // This is set via set_java_exception_info, not at constructor time. + const char* java_exception_info_; + + DISALLOW_COPY_AND_ASSIGN(BuildInfo); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_BUILD_INFO_H_
diff --git a/src/base/android/cpu_features.cc b/src/base/android/cpu_features.cc new file mode 100644 index 0000000..6a18695 --- /dev/null +++ b/src/base/android/cpu_features.cc
@@ -0,0 +1,26 @@ +// 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 <cpu-features.h> + +#include "base/android/jni_android.h" +#include "jni/CpuFeatures_jni.h" + +namespace base { +namespace android { + +jint GetCoreCount(JNIEnv*, jclass) { + return android_getCpuCount(); +} + +jlong GetCpuFeatures(JNIEnv*, jclass) { + return android_getCpuFeatures(); +} + +bool RegisterCpuFeatures(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/cpu_features.h b/src/base/android/cpu_features.h new file mode 100644 index 0000000..0a27822 --- /dev/null +++ b/src/base/android/cpu_features.h
@@ -0,0 +1,18 @@ +// 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_ANDROID_CPU_FEATURES_H_ +#define BASE_ANDROID_CPU_FEATURES_H_ + +#include <jni.h> + +namespace base { +namespace android { + +bool RegisterCpuFeatures(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_CPU_FEATURES_H_
diff --git a/src/base/android/java/src/org/chromium/base/AccessedByNative.java b/src/base/android/java/src/org/chromium/base/AccessedByNative.java new file mode 100644 index 0000000..8248cc6 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/AccessedByNative.java
@@ -0,0 +1,20 @@ +// 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. + +package org.chromium.base; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @AccessedByNative is used to ensure proguard will keep this field, since it's + * only accessed by native. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface AccessedByNative { + public String value() default ""; +}
diff --git a/src/base/android/java/src/org/chromium/base/ActivityStatus.java b/src/base/android/java/src/org/chromium/base/ActivityStatus.java new file mode 100644 index 0000000..e728870 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/ActivityStatus.java
@@ -0,0 +1,99 @@ +// 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. + +package org.chromium.base; + +import android.app.Activity; +import android.os.Looper; + +import java.util.ArrayList; + +/** + * Provides information about the parent activity's status. + */ +public class ActivityStatus { + + // Constants matching activity states reported to StateListener.onStateChange + public static final int CREATED = 1; + public static final int STARTED = 2; + public static final int RESUMED = 3; + public static final int PAUSED = 4; + public static final int STOPPED = 5; + public static final int DESTROYED = 6; + + // Current main activity, or null if none. + private static Activity sActivity; + + // Current main activity's state. This can be set even if sActivity is null, to simplify unit + // testing. + private static int sActivityState; + + private static final ArrayList<StateListener> sStateListeners = new ArrayList<StateListener>(); + + // Use this interface to listen to all state changes. + public interface StateListener { + /** + * Called when the activity's state changes. + * @param newState New activity state. + */ + public void onActivityStateChange(int newState); + } + + private ActivityStatus() {} + + /** + * Must be called by the main activity when it changes state. + * @param activity Current activity. + * @param newState New state value. + */ + public static void onStateChange(Activity activity, int newState) { + if (newState == CREATED) { + sActivity = activity; + } + sActivityState = newState; + for (StateListener listener : sStateListeners) { + listener.onActivityStateChange(newState); + } + if (newState == DESTROYED) { + sActivity = null; + } + } + + /** + * Indicates that the parent activity is currently paused. + */ + public static boolean isPaused() { + return sActivityState == PAUSED; + } + + /** + * Returns the current main application activity. + */ + public static Activity getActivity() { + return sActivity; + } + + /** + * Returns the current main application activity's state. + */ + public static int getState() { + return sActivityState; + } + + /** + * Registers the given listener to receive activity state changes. + * @param listener Listener to receive state changes. + */ + public static void registerStateListener(StateListener listener) { + sStateListeners.add(listener); + } + + /** + * Unregisters the given listener from receiving activity state changes. + * @param listener Listener that doesn't want to receive state changes. + */ + public static void unregisterStateListener(StateListener listener) { + sStateListeners.remove(listener); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/BuildInfo.java b/src/base/android/java/src/org/chromium/base/BuildInfo.java new file mode 100644 index 0000000..2314051 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/BuildInfo.java
@@ -0,0 +1,114 @@ +// 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. + +package org.chromium.base; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Build; +import android.util.Log; + +import org.chromium.base.CalledByNative; + +/** + * BuildInfo is a utility class providing easy access to {@link PackageInfo} + * information. This is primarly of use for accessesing package information + * from native code. + */ +public class BuildInfo { + private static final String TAG = "BuildInfo"; + private static final int MAX_FINGERPRINT_LENGTH = 128; + + /** + * BuildInfo is a static utility class and therefore shouldn't be + * instantiated. + */ + private BuildInfo() { + } + + @CalledByNative + public static String getDevice() { + return Build.DEVICE; + } + + @CalledByNative + public static String getBrand() { + return Build.BRAND; + } + + @CalledByNative + public static String getAndroidBuildId() { + return Build.ID; + } + + /** + * @return The build fingerprint for the current Android install. The value is truncated to a + * 128 characters as this is used for crash and UMA reporting, which should avoid huge + * strings. + */ + @CalledByNative + public static String getAndroidBuildFingerprint() { + return Build.FINGERPRINT.substring( + 0, Math.min(Build.FINGERPRINT.length(), MAX_FINGERPRINT_LENGTH)); + } + + @CalledByNative + public static String getDeviceModel() { + return Build.MODEL; + } + + @CalledByNative + public static String getPackageVersionCode(Context context) { + String msg = "versionCode not available."; + try { + PackageManager pm = context.getPackageManager(); + PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0); + msg = "" + pi.versionCode; + } catch (NameNotFoundException e) { + Log.d(TAG, msg); + } + return msg; + + } + + @CalledByNative + public static String getPackageVersionName(Context context) { + String msg = "versionName not available"; + try { + PackageManager pm = context.getPackageManager(); + PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0); + msg = pi.versionName; + } catch (NameNotFoundException e) { + Log.d(TAG, msg); + } + return msg; + } + + @CalledByNative + public static String getPackageLabel(Context context) { + try { + PackageManager packageManager = context.getPackageManager(); + ApplicationInfo appInfo = packageManager.getApplicationInfo(context.getPackageName(), + PackageManager.GET_META_DATA); + CharSequence label = packageManager.getApplicationLabel(appInfo); + return label != null ? label.toString() : ""; + } catch (NameNotFoundException e) { + return ""; + } + } + + @CalledByNative + public static String getPackageName(Context context) { + String packageName = context != null ? context.getPackageName() : null; + return packageName != null ? packageName : ""; + } + + @CalledByNative + public static int getSdkInt() { + return Build.VERSION.SDK_INT; + } +}
diff --git a/src/base/android/java/src/org/chromium/base/CalledByNative.java b/src/base/android/java/src/org/chromium/base/CalledByNative.java new file mode 100644 index 0000000..8d3dcad --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/CalledByNative.java
@@ -0,0 +1,23 @@ +// 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. + +package org.chromium.base; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @CalledByNative is used by the JNI generator to create the necessary JNI + * bindings and expose this method to native code. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface CalledByNative { + /* + * If present, tells which inner class the method belongs to. + */ + public String value() default ""; +}
diff --git a/src/base/android/java/src/org/chromium/base/CalledByNativeUnchecked.java b/src/base/android/java/src/org/chromium/base/CalledByNativeUnchecked.java new file mode 100644 index 0000000..cc264a2 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/CalledByNativeUnchecked.java
@@ -0,0 +1,27 @@ +// 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. + +package org.chromium.base; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @CalledByNativeUnchecked is used to generate JNI bindings that do not check for exceptions. + * It only makes sense to use this annotation on methods that declare a throws... spec. + * However, note that the exception received native side maybe an 'unchecked' (RuntimeExpception) + * such as NullPointerException, so the native code should differentiate these cases. + * Usage of this should be very rare; where possible handle exceptions in the Java side and use a + * return value to indicate success / failure. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface CalledByNativeUnchecked { + /* + * If present, tells which inner class the method belongs to. + */ + public String value() default ""; +}
diff --git a/src/base/android/java/src/org/chromium/base/ChromiumActivity.java b/src/base/android/java/src/org/chromium/base/ChromiumActivity.java new file mode 100644 index 0000000..65f5ce9 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/ChromiumActivity.java
@@ -0,0 +1,49 @@ +// 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. + +package org.chromium.base; + +import android.app.Activity; +import android.os.Bundle; + +// All Chromium main activities should extend this class. This allows various sub-systems to +// properly react to activity state changes. +public class ChromiumActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstance) { + super.onCreate(savedInstance); + ActivityStatus.onStateChange(this, ActivityStatus.CREATED); + } + + @Override + protected void onStart() { + super.onStart(); + ActivityStatus.onStateChange(this, ActivityStatus.STARTED); + } + + @Override + protected void onResume() { + super.onResume(); + ActivityStatus.onStateChange(this, ActivityStatus.RESUMED); + } + + @Override + protected void onPause() { + ActivityStatus.onStateChange(this, ActivityStatus.PAUSED); + super.onPause(); + } + + @Override + protected void onStop() { + ActivityStatus.onStateChange(this, ActivityStatus.STOPPED); + super.onStop(); + } + + @Override + protected void onDestroy() { + ActivityStatus.onStateChange(this, ActivityStatus.DESTROYED); + super.onDestroy(); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/CpuFeatures.java b/src/base/android/java/src/org/chromium/base/CpuFeatures.java new file mode 100644 index 0000000..f298fb1 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/CpuFeatures.java
@@ -0,0 +1,40 @@ +// 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. + +package org.chromium.base; + +// The only purpose of this class is to allow sending CPU properties +// from the browser process to sandboxed renderer processes. This is +// needed because sandboxed processes cannot, on ARM, query the kernel +// about the CPU's properties by parsing /proc, so this operation must +// be performed in the browser process, and the result passed to +// renderer ones. +// +// For more context, see http://crbug.com/164154 +// +// Technically, this is a wrapper around the native NDK cpufeatures +// library. The exact CPU features bits are never used in Java so +// there is no point in duplicating their definitions here. +// +@JNINamespace("base::android") +public abstract class CpuFeatures { + /** + * Return the number of CPU Cores on the device. + */ + public static int getCount() { + return nativeGetCoreCount(); + } + + /** + * Return the CPU feature mask. + * This is a 64-bit integer that corresponds to the CPU's features. + * The value comes directly from android_getCpuFeatures(). + */ + public static long getMask() { + return nativeGetCpuFeatures(); + } + + private static native int nativeGetCoreCount(); + private static native long nativeGetCpuFeatures(); +}
diff --git a/src/base/android/java/src/org/chromium/base/JNINamespace.java b/src/base/android/java/src/org/chromium/base/JNINamespace.java new file mode 100644 index 0000000..cfffc91 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/JNINamespace.java
@@ -0,0 +1,20 @@ +// 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. + +package org.chromium.base; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @JNINamespace is used by the JNI generator to create the necessary JNI + * bindings and expose this method to native code using the specified namespace. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface JNINamespace { + public String value(); +}
diff --git a/src/base/android/java/src/org/chromium/base/LocaleUtils.java b/src/base/android/java/src/org/chromium/base/LocaleUtils.java new file mode 100644 index 0000000..6bca1f7 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/LocaleUtils.java
@@ -0,0 +1,26 @@ +// 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. + +package org.chromium.base; + +import java.util.Locale; + +/** + * This class provides the locale related methods for the native library. + */ +class LocaleUtils { + + private LocaleUtils() { /* cannot be instantiated */ } + + /** + * @return the default locale. + */ + @CalledByNative + public static String getDefaultLocale() { + Locale locale = Locale.getDefault(); + String language = locale.getLanguage(); + String country = locale.getCountry(); + return country.isEmpty() ? language : language + "-" + country; + } +}
diff --git a/src/base/android/java/src/org/chromium/base/NativeClassQualifiedName.java b/src/base/android/java/src/org/chromium/base/NativeClassQualifiedName.java new file mode 100644 index 0000000..309169b --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/NativeClassQualifiedName.java
@@ -0,0 +1,25 @@ +// 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. + +package org.chromium.base; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @NativeClassQualifiedName is used by the JNI generator to create the necessary JNI + * bindings to call into the specified native class name. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface NativeClassQualifiedName { + /* + * Tells which native class the method is going to be bound to. + * The first parameter of the annotated method must be an int nativePtr pointing to + * an instance of this class. + */ + public String value(); +}
diff --git a/src/base/android/java/src/org/chromium/base/PathService.java b/src/base/android/java/src/org/chromium/base/PathService.java new file mode 100644 index 0000000..dfda736 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/PathService.java
@@ -0,0 +1,24 @@ +// 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. + +package org.chromium.base; + +/** + * This class provides java side access to the native PathService. + */ +@JNINamespace("base::android") +public abstract class PathService { + + // Must match the value of DIR_MODULE in base/base_paths.h! + public static final int DIR_MODULE = 3; + + // Prevent instantiation. + private PathService() {} + + public static void override(int what, String path) { + nativeOverride(what, path); + } + + private static native void nativeOverride(int what, String path); +}
diff --git a/src/base/android/java/src/org/chromium/base/PathUtils.java b/src/base/android/java/src/org/chromium/base/PathUtils.java new file mode 100644 index 0000000..c3503a4 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/PathUtils.java
@@ -0,0 +1,85 @@ +// 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. + +package org.chromium.base; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.Environment; + +/** + * This class provides the path related methods for the native library. + */ +public abstract class PathUtils { + + private static String sDataDirectorySuffix; + + // Prevent instantiation. + private PathUtils() {} + + /** + * Sets the suffix that should be used for the directory where private data is to be stored + * by the application. + * @param suffix The private data directory suffix. + * @see Context#getDir(String, int) + */ + public static void setPrivateDataDirectorySuffix(String suffix) { + sDataDirectorySuffix = suffix; + } + + /** + * @return the private directory that is used to store application data. + */ + @CalledByNative + public static String getDataDirectory(Context appContext) { + if (sDataDirectorySuffix == null) { + throw new IllegalStateException( + "setDataDirectorySuffix must be called before getDataDirectory"); + } + return appContext.getDir(sDataDirectorySuffix, Context.MODE_PRIVATE).getPath(); + } + + /** + * @return the cache directory. + */ + @SuppressWarnings("unused") + @CalledByNative + private static String getCacheDirectory(Context appContext) { + return appContext.getCacheDir().getPath(); + } + + /** + * @return the public downloads directory. + */ + @SuppressWarnings("unused") + @CalledByNative + private static String getDownloadsDirectory(Context appContext) { + return Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS).getPath(); + } + + /** + * @return the path to native libraries. + */ + @SuppressWarnings("unused") + @CalledByNative + private static String getNativeLibraryDirectory(Context appContext) { + ApplicationInfo ai = appContext.getApplicationInfo(); + if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 || + (ai.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + return ai.nativeLibraryDir; + } + + return "/system/lib/"; + } + + /** + * @return the external storage directory. + */ + @SuppressWarnings("unused") + @CalledByNative + public static String getExternalStorageDirectory() { + return Environment.getExternalStorageDirectory().getAbsolutePath(); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/PowerStatusReceiver.java b/src/base/android/java/src/org/chromium/base/PowerStatusReceiver.java new file mode 100644 index 0000000..89594b8 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/PowerStatusReceiver.java
@@ -0,0 +1,23 @@ +// 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. + +package org.chromium.base; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + + +/** + * A BroadcastReceiver that listens to changes in power status and notifies + * SystemMonitor. + * It's instantiated by the framework via the application intent-filter + * declared in its manifest. + */ +public class PowerStatusReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + SystemMonitor.onBatteryChargingChanged(intent); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/SystemMessageHandler.java b/src/base/android/java/src/org/chromium/base/SystemMessageHandler.java new file mode 100644 index 0000000..f7bb19f --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/SystemMessageHandler.java
@@ -0,0 +1,93 @@ +// 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. + +package org.chromium.base; + +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; + +import java.util.concurrent.atomic.AtomicBoolean; + +class SystemMessageHandler extends Handler { + + private static final int TIMER_MESSAGE = 1; + private static final int DELAYED_TIMER_MESSAGE = 2; + + // Native class pointer set by the constructor of the SharedClient native class. + private int mMessagePumpDelegateNative = 0; + + // Used to ensure we have at most one TIMER_MESSAGE pending at once. + private AtomicBoolean mTimerFired = new AtomicBoolean(true); + + // Used to insert TIMER_MESSAGE on the front of the system message queue during startup only. + // This is a wee hack, to give a priority boost to native tasks during startup as they tend to + // be on the critical path. (After startup, handling the UI with minimum latency is more + // important). + private boolean mStartupComplete = false; + private final long mStartupCompleteTime = System.currentTimeMillis() + 2000; + private final boolean startupComplete() { + if (!mStartupComplete && System.currentTimeMillis() > mStartupCompleteTime) { + mStartupComplete = true; + } + return mStartupComplete; + } + + private SystemMessageHandler(int messagePumpDelegateNative) { + mMessagePumpDelegateNative = messagePumpDelegateNative; + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == TIMER_MESSAGE) { + mTimerFired.set(true); + } + while (nativeDoRunLoopOnce(mMessagePumpDelegateNative)) { + if (startupComplete()) { + setTimer(); + break; + } + } + } + + @CalledByNative + private void setTimer() { + if (!mTimerFired.getAndSet(false)) { + // mTimerFired was already false. + return; + } + if (startupComplete()) { + sendEmptyMessage(TIMER_MESSAGE); + } else { + sendMessageAtFrontOfQueue(obtainMessage(TIMER_MESSAGE)); + } + } + + // If millis <=0, it'll send a TIMER_MESSAGE instead of + // a DELAYED_TIMER_MESSAGE. + @SuppressWarnings("unused") + @CalledByNative + private void setDelayedTimer(long millis) { + if (millis <= 0) { + setTimer(); + } else { + removeMessages(DELAYED_TIMER_MESSAGE); + sendEmptyMessageDelayed(DELAYED_TIMER_MESSAGE, millis); + } + } + + @SuppressWarnings("unused") + @CalledByNative + private void removeTimer() { + removeMessages(TIMER_MESSAGE); + removeMessages(DELAYED_TIMER_MESSAGE); + } + + @CalledByNative + private static SystemMessageHandler create(int messagePumpDelegateNative) { + return new SystemMessageHandler(messagePumpDelegateNative); + } + + private native boolean nativeDoRunLoopOnce(int messagePumpDelegateNative); +}
diff --git a/src/base/android/java/src/org/chromium/base/SystemMonitor.java b/src/base/android/java/src/org/chromium/base/SystemMonitor.java new file mode 100644 index 0000000..30f61a6 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/SystemMonitor.java
@@ -0,0 +1,90 @@ +// 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. + +package org.chromium.base; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.Handler; +import android.os.Looper; + + +/** + * Integrates native SystemMonitor with the java side. + */ +@JNINamespace("base::android") +public class SystemMonitor implements ActivityStatus.StateListener { + private static final long SUSPEND_DELAY_MS = 1 * 60 * 1000; // 1 minute. + private static SystemMonitor sInstance; + + private boolean mIsBatteryPower; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + // Asynchronous task used to fire the "paused" event to the native side 1 minute after the main + // activity transitioned to the "paused" state. This event is not sent immediately because it + // would be too aggressive. An Android activity can be in the "paused" state quite often. This + // can happen when a dialog window shows up for instance. + private static final Runnable sSuspendTask = new Runnable() { + @Override + public void run() { + nativeOnMainActivitySuspended(); + } + }; + + public static void createForTests(Context context) { + // Applications will create this once the JNI side has been fully wired up both sides. For + // tests, we just need native -> java, that is, we don't need to notify java -> native on + // creation. + sInstance = new SystemMonitor(); + } + + public static void create(Context context) { + if (sInstance == null) { + sInstance = new SystemMonitor(); + ActivityStatus.registerStateListener(sInstance); + IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + Intent batteryStatusIntent = context.registerReceiver(null, ifilter); + onBatteryChargingChanged(batteryStatusIntent); + } + } + + private SystemMonitor() { + } + + public static void onBatteryChargingChanged(Intent intent) { + if (sInstance == null) { + // We may be called by the framework intent-filter before being fully initialized. This + // is not a problem, since our constructor will check for the state later on. + return; + } + int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + // If we're not plugged, assume we're running on battery power. + sInstance.mIsBatteryPower = chargePlug != BatteryManager.BATTERY_PLUGGED_USB && + chargePlug != BatteryManager.BATTERY_PLUGGED_AC; + nativeOnBatteryChargingChanged(); + } + + @Override + public void onActivityStateChange(int newState) { + if (newState == ActivityStatus.RESUMED) { + // Remove the callback from the message loop in case it hasn't been executed yet. + mHandler.removeCallbacks(sSuspendTask); + nativeOnMainActivityResumed(); + } else if (newState == ActivityStatus.PAUSED) { + mHandler.postDelayed(sSuspendTask, SUSPEND_DELAY_MS); + } + } + + @CalledByNative + private static boolean isBatteryPower() { + return sInstance.mIsBatteryPower; + } + + private static native void nativeOnBatteryChargingChanged(); + private static native void nativeOnMainActivitySuspended(); + private static native void nativeOnMainActivityResumed(); +}
diff --git a/src/base/android/java/src/org/chromium/base/ThreadUtils.java b/src/base/android/java/src/org/chromium/base/ThreadUtils.java new file mode 100644 index 0000000..70232c4 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/ThreadUtils.java
@@ -0,0 +1,152 @@ +// 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. + +package org.chromium.base; + +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +/** + * Helper methods to deal with threading related tasks. + */ +public class ThreadUtils { + + /** + * Run the supplied Runnable on the main thread. The method will block until + * the Runnable completes. + * + * @param r The Runnable to run. + */ + public static void runOnUiThreadBlocking(final Runnable r) { + if (runningOnUiThread()) { + r.run(); + } else { + FutureTask<Void> task = new FutureTask<Void>(r, null); + postOnUiThread(task); + try { + task.get(); + } catch (Exception e) { + throw new RuntimeException("Exception occured while waiting for runnable", e); + } + } + } + + /** + * Run the supplied Callable on the main thread, wrapping any exceptions in + * a RuntimeException. The method will block until the Callable completes. + * + * @param c The Callable to run + * @return The result of the callable + */ + public static <T> T runOnUiThreadBlockingNoException(Callable<T> c) { + try { + return runOnUiThreadBlocking(c); + } catch (ExecutionException e) { + throw new RuntimeException("Error occured waiting for callable", e); + } + } + + /** + * Run the supplied Callable on the main thread, The method will block until + * the Callable completes. + * + * @param c The Callable to run + * @return The result of the callable + * @throws ExecutionException c's exception + */ + public static <T> T runOnUiThreadBlocking(Callable<T> c) throws ExecutionException { + FutureTask<T> task = new FutureTask<T>(c); + runOnUiThread(task); + try { + return task.get(); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted waiting for callable", e); + } + } + + /** + * Run the supplied FutureTask on the main thread. The method will block + * only if the current thread is the main thread. + * + * @param task The FutureTask to run + * @return The queried task (to aid inline construction) + */ + public static <T> FutureTask<T> runOnUiThread(FutureTask<T> task) { + if (runningOnUiThread()) { + task.run(); + } else { + postOnUiThread(task); + } + return task; + } + + /** + * Run the supplied Callable on the main thread. The method will block + * only if the current thread is the main thread. + * + * @param c The Callable to run + * @return A FutureTask wrapping the callable to retrieve results + */ + public static <T> FutureTask<T> runOnUiThread(Callable<T> c) { + return runOnUiThread(new FutureTask<T>(c)); + } + + /** + * Run the supplied Runnable on the main thread. The method will block + * only if the current thread is the main thread. + * + * @param r The Runnable to run + */ + public static void runOnUiThread(Runnable r) { + if (runningOnUiThread()) { + r.run(); + } else { + LazyHolder.sUiThreadHandler.post(r); + } + } + + /** + * Post the supplied FutureTask to run on the main thread. The method will + * not block, even if called on the UI thread. + * + * @param task The FutureTask to run + * @return The queried task (to aid inline construction) + */ + public static <T> FutureTask<T> postOnUiThread(FutureTask<T> task) { + LazyHolder.sUiThreadHandler.post(task); + return task; + } + + /** + * Post the supplied Runnable to run on the main thread. The method will + * not block, even if called on the UI thread. + * + * @param task The Runnable to run + */ + public static void postOnUiThread(Runnable r) { + LazyHolder.sUiThreadHandler.post(r); + } + + /** + * Asserts that the current thread is running on the main thread. + */ + public static void assertOnUiThread() { + assert runningOnUiThread(); + } + + /** + * @return true iff the current thread is the main (UI) thread. + */ + public static boolean runningOnUiThread() { + return Looper.getMainLooper() == Looper.myLooper(); + } + + private static class LazyHolder { + private static Handler sUiThreadHandler = new Handler(Looper.getMainLooper()); + } +}
diff --git a/src/base/android/java/src/org/chromium/base/WeakContext.java b/src/base/android/java/src/org/chromium/base/WeakContext.java new file mode 100644 index 0000000..d660cc9 --- /dev/null +++ b/src/base/android/java/src/org/chromium/base/WeakContext.java
@@ -0,0 +1,45 @@ +// 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. + +package org.chromium.base; + +import android.content.Context; + +import java.lang.ref.WeakReference; +import java.util.concurrent.Callable; + +// Holds a WeakReference to Context to allow it to be GC'd. +// Also provides utility functions to getSystemService from the UI or any +// other thread (may return null, if the Context has been nullified). +public class WeakContext { + private static WeakReference<Context> sWeakContext; + + public static void initializeWeakContext(final Context context) { + sWeakContext = new WeakReference<Context>(context); + } + + public static Context getContext() { + return sWeakContext.get(); + } + + // Returns a system service. May be called from any thread. + // If necessary, it will send a message to the main thread to acquire the + // service, and block waiting for it to complete. + // May return null if context is no longer available. + public static Object getSystemService(final String name) { + final Context context = sWeakContext.get(); + if (context == null) { + return null; + } + if (ThreadUtils.runningOnUiThread()) { + return context.getSystemService(name); + } + return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Object>() { + @Override + public Object call() { + return context.getSystemService(name); + } + }); + } +}
diff --git a/src/base/android/jni_android.cc b/src/base/android/jni_android.cc new file mode 100644 index 0000000..fdc2170 --- /dev/null +++ b/src/base/android/jni_android.cc
@@ -0,0 +1,327 @@ +// 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/android/jni_android.h" + +#include <map> + +#include "base/android/build_info.h" +#include "base/android/jni_string.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/platform_thread.h" + +namespace { +using base::android::GetClass; +using base::android::MethodID; +using base::android::ScopedJavaLocalRef; + +struct MethodIdentifier { + const char* class_name; + const char* method; + const char* jni_signature; + + bool operator<(const MethodIdentifier& other) const { + int r = strcmp(class_name, other.class_name); + if (r < 0) { + return true; + } else if (r > 0) { + return false; + } + + r = strcmp(method, other.method); + if (r < 0) { + return true; + } else if (r > 0) { + return false; + } + + return strcmp(jni_signature, other.jni_signature) < 0; + } +}; + +typedef std::map<MethodIdentifier, jmethodID> MethodIDMap; + +const base::subtle::AtomicWord kUnlocked = 0; +const base::subtle::AtomicWord kLocked = 1; +base::subtle::AtomicWord g_method_id_map_lock = kUnlocked; +JavaVM* g_jvm = NULL; +// Leak the global app context, as it is used from a non-joinable worker thread +// that may still be running at shutdown. There is no harm in doing this. +base::LazyInstance<base::android::ScopedJavaGlobalRef<jobject> >::Leaky + g_application_context = LAZY_INSTANCE_INITIALIZER; +base::LazyInstance<MethodIDMap> g_method_id_map = LAZY_INSTANCE_INITIALIZER; + +std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) { + ScopedJavaLocalRef<jclass> throwable_clazz = + GetClass(env, "java/lang/Throwable"); + jmethodID throwable_printstacktrace = + MethodID::Get<MethodID::TYPE_INSTANCE>( + env, throwable_clazz.obj(), "printStackTrace", + "(Ljava/io/PrintStream;)V"); + + // Create an instance of ByteArrayOutputStream. + ScopedJavaLocalRef<jclass> bytearray_output_stream_clazz = + GetClass(env, "java/io/ByteArrayOutputStream"); + jmethodID bytearray_output_stream_constructor = + MethodID::Get<MethodID::TYPE_INSTANCE>( + env, bytearray_output_stream_clazz.obj(), "<init>", "()V"); + jmethodID bytearray_output_stream_tostring = + MethodID::Get<MethodID::TYPE_INSTANCE>( + env, bytearray_output_stream_clazz.obj(), "toString", + "()Ljava/lang/String;"); + ScopedJavaLocalRef<jobject> bytearray_output_stream(env, + env->NewObject(bytearray_output_stream_clazz.obj(), + bytearray_output_stream_constructor)); + + // Create an instance of PrintStream. + ScopedJavaLocalRef<jclass> printstream_clazz = + GetClass(env, "java/io/PrintStream"); + jmethodID printstream_constructor = + MethodID::Get<MethodID::TYPE_INSTANCE>( + env, printstream_clazz.obj(), "<init>", + "(Ljava/io/OutputStream;)V"); + ScopedJavaLocalRef<jobject> printstream(env, + env->NewObject(printstream_clazz.obj(), printstream_constructor, + bytearray_output_stream.obj())); + + // Call Throwable.printStackTrace(PrintStream) + env->CallVoidMethod(java_throwable, throwable_printstacktrace, + printstream.obj()); + + // Call ByteArrayOutputStream.toString() + ScopedJavaLocalRef<jstring> exception_string( + env, static_cast<jstring>( + env->CallObjectMethod(bytearray_output_stream.obj(), + bytearray_output_stream_tostring))); + + return ConvertJavaStringToUTF8(exception_string); +} + +} // namespace + +namespace base { +namespace android { + +JNIEnv* AttachCurrentThread() { + DCHECK(g_jvm); + JNIEnv* env = NULL; + jint ret = g_jvm->AttachCurrentThread(&env, NULL); + DCHECK_EQ(JNI_OK, ret); + return env; +} + +void DetachFromVM() { + // Ignore the return value, if the thread is not attached, DetachCurrentThread + // will fail. But it is ok as the native thread may never be attached. + if (g_jvm) + g_jvm->DetachCurrentThread(); +} + +void InitVM(JavaVM* vm) { + DCHECK(!g_jvm); + g_jvm = vm; +} + +void InitApplicationContext(const JavaRef<jobject>& context) { + DCHECK(g_application_context.Get().is_null()); + g_application_context.Get().Reset(context); +} + +const jobject GetApplicationContext() { + DCHECK(!g_application_context.Get().is_null()); + return g_application_context.Get().obj(); +} + +ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env, const char* class_name) { + return ScopedJavaLocalRef<jclass>(env, GetUnscopedClass(env, class_name)); +} + +jclass GetUnscopedClass(JNIEnv* env, const char* class_name) { + jclass clazz = env->FindClass(class_name); + CHECK(!ClearException(env) && clazz) << "Failed to find class " << class_name; + return clazz; +} + +bool HasClass(JNIEnv* env, const char* class_name) { + ScopedJavaLocalRef<jclass> clazz(env, env->FindClass(class_name)); + if (!clazz.obj()) { + ClearException(env); + return false; + } + bool error = ClearException(env); + DCHECK(!error); + return true; +} + +template<MethodID::Type type> +jmethodID MethodID::Get(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature) { + jmethodID id = type == TYPE_STATIC ? + env->GetStaticMethodID(clazz, method_name, jni_signature) : + env->GetMethodID(clazz, method_name, jni_signature); + CHECK(base::android::ClearException(env) || id) << + "Failed to find " << + (type == TYPE_STATIC ? "static " : "") << + "method " << method_name << " " << jni_signature; + return id; +} + +// If |atomic_method_id| set, it'll return immediately. Otherwise, it'll call +// into ::Get() above. If there's a race, it's ok since the values are the same +// (and the duplicated effort will happen only once). +template<MethodID::Type type> +jmethodID MethodID::LazyGet(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature, + base::subtle::AtomicWord* atomic_method_id) { + COMPILE_ASSERT(sizeof(subtle::AtomicWord) >= sizeof(jmethodID), + AtomicWord_SmallerThan_jMethodID); + subtle::AtomicWord value = base::subtle::Acquire_Load(atomic_method_id); + if (value) + return reinterpret_cast<jmethodID>(value); + jmethodID id = MethodID::Get<type>(env, clazz, method_name, jni_signature); + base::subtle::Release_Store( + atomic_method_id, reinterpret_cast<subtle::AtomicWord>(id)); + return id; +} + +// Various template instantiations. +template jmethodID MethodID::Get<MethodID::TYPE_STATIC>( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature); + +template jmethodID MethodID::Get<MethodID::TYPE_INSTANCE>( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature); + +template jmethodID MethodID::LazyGet<MethodID::TYPE_STATIC>( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature, base::subtle::AtomicWord* atomic_method_id); + +template jmethodID MethodID::LazyGet<MethodID::TYPE_INSTANCE>( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature, base::subtle::AtomicWord* atomic_method_id); + +jfieldID GetFieldID(JNIEnv* env, + const JavaRef<jclass>& clazz, + const char* field_name, + const char* jni_signature) { + jfieldID field_id = env->GetFieldID(clazz.obj(), field_name, jni_signature); + CHECK(!ClearException(env) && field_id) << "Failed to find field " << + field_name << " " << jni_signature; + return field_id; +} + +bool HasField(JNIEnv* env, + const JavaRef<jclass>& clazz, + const char* field_name, + const char* jni_signature) { + jfieldID field_id = env->GetFieldID(clazz.obj(), field_name, jni_signature); + if (!field_id) { + ClearException(env); + return false; + } + bool error = ClearException(env); + DCHECK(!error); + return true; +} + +jfieldID GetStaticFieldID(JNIEnv* env, + const JavaRef<jclass>& clazz, + const char* field_name, + const char* jni_signature) { + jfieldID field_id = + env->GetStaticFieldID(clazz.obj(), field_name, jni_signature); + CHECK(!ClearException(env) && field_id) << "Failed to find static field " << + field_name << " " << jni_signature; + return field_id; +} + +jmethodID GetMethodIDFromClassName(JNIEnv* env, + const char* class_name, + const char* method, + const char* jni_signature) { + MethodIdentifier key; + key.class_name = class_name; + key.method = method; + key.jni_signature = jni_signature; + + MethodIDMap* map = g_method_id_map.Pointer(); + bool found = false; + + while (base::subtle::Acquire_CompareAndSwap(&g_method_id_map_lock, + kUnlocked, + kLocked) != kUnlocked) { + base::PlatformThread::YieldCurrentThread(); + } + MethodIDMap::const_iterator iter = map->find(key); + if (iter != map->end()) { + found = true; + } + base::subtle::Release_Store(&g_method_id_map_lock, kUnlocked); + + // Addition to the map does not invalidate this iterator. + if (found) { + return iter->second; + } + + ScopedJavaLocalRef<jclass> clazz(env, env->FindClass(class_name)); + jmethodID id = MethodID::Get<MethodID::TYPE_INSTANCE>( + env, clazz.obj(), method, jni_signature); + + while (base::subtle::Acquire_CompareAndSwap(&g_method_id_map_lock, + kUnlocked, + kLocked) != kUnlocked) { + base::PlatformThread::YieldCurrentThread(); + } + // Another thread may have populated the map already. + std::pair<MethodIDMap::const_iterator, bool> result = + map->insert(std::make_pair(key, id)); + DCHECK_EQ(id, result.first->second); + base::subtle::Release_Store(&g_method_id_map_lock, kUnlocked); + + return id; +} + +bool HasException(JNIEnv* env) { + return env->ExceptionCheck() != JNI_FALSE; +} + +bool ClearException(JNIEnv* env) { + if (!HasException(env)) + return false; + env->ExceptionDescribe(); + env->ExceptionClear(); + return true; +} + +void CheckException(JNIEnv* env) { + if (!HasException(env)) return; + + // Exception has been found, might as well tell breakpad about it. + jthrowable java_throwable = env->ExceptionOccurred(); + if (!java_throwable) { + // Do nothing but return false. + CHECK(false); + } + + // Clear the pending exception, since a local reference is now held. + env->ExceptionDescribe(); + env->ExceptionClear(); + + // Set the exception_string in BuildInfo so that breakpad can read it. + // RVO should avoid any extra copies of the exception string. + base::android::BuildInfo::GetInstance()->set_java_exception_info( + GetJavaExceptionInfo(env, java_throwable)); + + // Now, feel good about it and die. + CHECK(false); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/jni_android.h b/src/base/android/jni_android.h new file mode 100644 index 0000000..16b85af --- /dev/null +++ b/src/base/android/jni_android.h
@@ -0,0 +1,137 @@ +// 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_ANDROID_JNI_ANDROID_H_ +#define BASE_ANDROID_JNI_ANDROID_H_ + +#include <jni.h> +#include <sys/types.h> + +#include "base/android/scoped_java_ref.h" +#include "base/atomicops.h" +#include "base/base_export.h" +#include "base/compiler_specific.h" + +namespace base { +namespace android { + +// Used to mark symbols to be exported in a shared library's symbol table. +#define JNI_EXPORT __attribute__ ((visibility("default"))) + +// Contains the registration method information for initializing JNI bindings. +struct RegistrationMethod { + const char* name; + bool (*func)(JNIEnv* env); +}; + +// Attach the current thread to the VM (if necessary) and return the JNIEnv*. +BASE_EXPORT JNIEnv* AttachCurrentThread(); + +// Detach the current thread from VM if it is attached. +BASE_EXPORT void DetachFromVM(); + +// Initializes the global JVM. It is not necessarily called before +// InitApplicationContext(). +BASE_EXPORT void InitVM(JavaVM* vm); + +// Initializes the global application context object. The |context| can be any +// valid reference to the application context. Internally holds a global ref to +// the context. InitVM and InitApplicationContext maybe called in either order. +BASE_EXPORT void InitApplicationContext(const JavaRef<jobject>& context); + +// Gets a global ref to the application context set with +// InitApplicationContext(). Ownership is retained by the function - the caller +// must NOT release it. +const BASE_EXPORT jobject GetApplicationContext(); + +// Finds the class named |class_name| and returns it. +// Use this method instead of invoking directly the JNI FindClass method (to +// prevent leaking local references). +// This method triggers a fatal assertion if the class could not be found. +// Use HasClass if you need to check whether the class exists. +BASE_EXPORT ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env, + const char* class_name); + +// Similar to the above, but the caller is responsible to manage the jclass +// lifetime. +BASE_EXPORT jclass GetUnscopedClass(JNIEnv* env, + const char* class_name) WARN_UNUSED_RESULT; + +// Returns true iff the class |class_name| could be found. +BASE_EXPORT bool HasClass(JNIEnv* env, const char* class_name); + +// This class is a wrapper for JNIEnv Get(Static)MethodID. +class BASE_EXPORT MethodID { + public: + enum Type { + TYPE_STATIC, + TYPE_INSTANCE, + }; + + // Returns the method ID for the method with the specified name and signature. + // This method triggers a fatal assertion if the method could not be found. + template<Type type> + static jmethodID Get(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature); + + // The caller is responsible to zero-initialize |atomic_method_id|. + // It's fine to simultaneously call this on multiple threads referencing the + // same |atomic_method_id|. + template<Type type> + static jmethodID LazyGet(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature, + base::subtle::AtomicWord* atomic_method_id); +}; + +// Gets the method ID from the class name. Clears the pending Java exception +// and returns NULL if the method is not found. Caches results. Note that +// MethodID::Get() above avoids a class lookup, but does not cache results. +// Strings passed to this function are held in the cache and MUST remain valid +// beyond the duration of all future calls to this function, across all +// threads. In practice, this means that the function should only be used with +// string constants. +BASE_EXPORT jmethodID GetMethodIDFromClassName(JNIEnv* env, + const char* class_name, + const char* method, + const char* jni_signature); + +// Gets the field ID for a class field. +// This method triggers a fatal assertion if the field could not be found. +BASE_EXPORT jfieldID GetFieldID(JNIEnv* env, + const JavaRef<jclass>& clazz, + const char* field_name, + const char* jni_signature); + +// Returns true if |clazz| as a field with the given name and signature. +// TODO(jcivelli): Determine whether we explicitly have to pass the environment. +BASE_EXPORT bool HasField(JNIEnv* env, + const JavaRef<jclass>& clazz, + const char* field_name, + const char* jni_signature); + +// Gets the field ID for a static class field. +// This method triggers a fatal assertion if the field could not be found. +BASE_EXPORT jfieldID GetStaticFieldID(JNIEnv* env, + const JavaRef<jclass>& clazz, + const char* field_name, + const char* jni_signature); + +// Returns true if an exception is pending in the provided JNIEnv*. +BASE_EXPORT bool HasException(JNIEnv* env); + +// If an exception is pending in the provided JNIEnv*, this function clears it +// and returns true. +BASE_EXPORT bool ClearException(JNIEnv* env); + +// This function will call CHECK() macro if there's any pending exception. +BASE_EXPORT void CheckException(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_ANDROID_H_
diff --git a/src/base/android/jni_android_unittest.cc b/src/base/android/jni_android_unittest.cc new file mode 100644 index 0000000..920b395 --- /dev/null +++ b/src/base/android/jni_android_unittest.cc
@@ -0,0 +1,141 @@ +// 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/android/jni_android.h" + +#include "base/at_exit.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +namespace { + +const char kJavaLangObject[] = "java/lang/Object"; +const char kGetClass[] = "getClass"; +const char kToString[] = "toString"; +const char kReturningJavaLangClass[] = "()Ljava/lang/Class;"; +const char kReturningJavaLangString[] = "()Ljava/lang/String;"; + +const char* g_last_method; +const char* g_last_jni_signature; +jmethodID g_last_method_id; + +const JNINativeInterface* g_previous_functions; + +jmethodID GetMethodIDWrapper(JNIEnv* env, jclass clazz, const char* method, + const char* jni_signature) { + g_last_method = method; + g_last_jni_signature = jni_signature; + g_last_method_id = g_previous_functions->GetMethodID(env, clazz, method, + jni_signature); + return g_last_method_id; +} + +} // namespace + +class JNIAndroidTest : public testing::Test { + protected: + virtual void SetUp() { + JNIEnv* env = AttachCurrentThread(); + g_previous_functions = env->functions; + hooked_functions = *g_previous_functions; + env->functions = &hooked_functions; + hooked_functions.GetMethodID = &GetMethodIDWrapper; + } + + virtual void TearDown() { + JNIEnv* env = AttachCurrentThread(); + env->functions = g_previous_functions; + Reset(); + } + + void Reset() { + g_last_method = 0; + g_last_jni_signature = 0; + g_last_method_id = NULL; + } + // Needed to cleanup the cached method map in the implementation between + // runs (e.g. if using --gtest_repeat) + base::ShadowingAtExitManager exit_manager; + // From JellyBean release, the instance of this struct provided in JNIEnv is + // read-only, so we deep copy it to allow individual functions to be hooked. + JNINativeInterface hooked_functions; +}; + +TEST_F(JNIAndroidTest, GetMethodIDFromClassNameCaching) { + JNIEnv* env = AttachCurrentThread(); + + Reset(); + jmethodID id1 = GetMethodIDFromClassName(env, kJavaLangObject, kGetClass, + kReturningJavaLangClass); + EXPECT_STREQ(kGetClass, g_last_method); + EXPECT_STREQ(kReturningJavaLangClass, g_last_jni_signature); + EXPECT_EQ(g_last_method_id, id1); + + Reset(); + jmethodID id2 = GetMethodIDFromClassName(env, kJavaLangObject, kGetClass, + kReturningJavaLangClass); + EXPECT_STREQ(0, g_last_method); + EXPECT_STREQ(0, g_last_jni_signature); + EXPECT_EQ(NULL, g_last_method_id); + EXPECT_EQ(id1, id2); + + Reset(); + jmethodID id3 = GetMethodIDFromClassName(env, kJavaLangObject, kToString, + kReturningJavaLangString); + EXPECT_STREQ(kToString, g_last_method); + EXPECT_STREQ(kReturningJavaLangString, g_last_jni_signature); + EXPECT_EQ(g_last_method_id, id3); +} + +namespace { + +base::subtle::AtomicWord g_atomic_id = 0; +int LazyMethodIDCall(JNIEnv* env, jclass clazz, int p) { + jmethodID id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, clazz, + "abs", + "(I)I", + &g_atomic_id); + + return env->CallStaticIntMethod(clazz, id, p); +} + +int MethodIDCall(JNIEnv* env, jclass clazz, jmethodID id, int p) { + return env->CallStaticIntMethod(clazz, id, p); +} + +} // namespace + +TEST(JNIAndroidMicrobenchmark, MethodId) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jclass> clazz(GetClass(env, "java/lang/Math")); + base::Time start_lazy = base::Time::Now(); + int o = 0; + for (int i = 0; i < 1024; ++i) + o += LazyMethodIDCall(env, clazz.obj(), i); + base::Time end_lazy = base::Time::Now(); + + jmethodID id = reinterpret_cast<jmethodID>(g_atomic_id); + base::Time start = base::Time::Now(); + for (int i = 0; i < 1024; ++i) + o += MethodIDCall(env, clazz.obj(), id, i); + base::Time end = base::Time::Now(); + + // On a Galaxy Nexus, results were in the range of: + // JNI LazyMethodIDCall (us) 1984 + // JNI MethodIDCall (us) 1861 + LOG(ERROR) << "JNI LazyMethodIDCall (us) " << + base::TimeDelta(end_lazy - start_lazy).InMicroseconds(); + LOG(ERROR) << "JNI MethodIDCall (us) " << + base::TimeDelta(end - start).InMicroseconds(); + LOG(ERROR) << "JNI " << o; +} + + +} // namespace android +} // namespace base
diff --git a/src/base/android/jni_array.cc b/src/base/android/jni_array.cc new file mode 100644 index 0000000..fe2aadb --- /dev/null +++ b/src/base/android/jni_array.cc
@@ -0,0 +1,153 @@ +// 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/android/jni_array.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/logging.h" + +namespace base { +namespace android { + +ScopedJavaLocalRef<jbyteArray> ToJavaByteArray( + JNIEnv* env, const uint8* bytes, size_t len) { + jbyteArray byte_array = env->NewByteArray(len); + CheckException(env); + DCHECK(byte_array); + + jbyte* elements = env->GetByteArrayElements(byte_array, NULL); + memcpy(elements, bytes, len); + env->ReleaseByteArrayElements(byte_array, elements, 0); + CheckException(env); + + return ScopedJavaLocalRef<jbyteArray>(env, byte_array); +} + +ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfByteArray( + JNIEnv* env, const std::vector<std::string>& v) { + ScopedJavaLocalRef<jclass> byte_array_clazz = GetClass(env, "[B"); + jobjectArray joa = env->NewObjectArray(v.size(), + byte_array_clazz.obj(), NULL); + CheckException(env); + + for (size_t i = 0; i < v.size(); ++i) { + ScopedJavaLocalRef<jbyteArray> byte_array = ToJavaByteArray(env, + reinterpret_cast<const uint8*>(v[i].data()), v[i].length()); + env->SetObjectArrayElement(joa, i, byte_array.obj()); + } + return ScopedJavaLocalRef<jobjectArray>(env, joa); +} + +ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings( + JNIEnv* env, const std::vector<std::string>& v) { + ScopedJavaLocalRef<jclass> string_clazz = GetClass(env, "java/lang/String"); + jobjectArray joa = env->NewObjectArray(v.size(), string_clazz.obj(), NULL); + CheckException(env); + + for (size_t i = 0; i < v.size(); ++i) { + ScopedJavaLocalRef<jstring> item = ConvertUTF8ToJavaString(env, v[i]); + env->SetObjectArrayElement(joa, i, item.obj()); + } + return ScopedJavaLocalRef<jobjectArray>(env, joa); +} + +ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings( + JNIEnv* env, const std::vector<string16>& v) { + ScopedJavaLocalRef<jclass> string_clazz = GetClass(env, "java/lang/String"); + jobjectArray joa = env->NewObjectArray(v.size(), string_clazz.obj(), NULL); + CheckException(env); + + for (size_t i = 0; i < v.size(); ++i) { + ScopedJavaLocalRef<jstring> item = ConvertUTF16ToJavaString(env, v[i]); + env->SetObjectArrayElement(joa, i, item.obj()); + } + return ScopedJavaLocalRef<jobjectArray>(env, joa); +} + +void AppendJavaStringArrayToStringVector(JNIEnv* env, + jobjectArray array, + std::vector<string16>* out) { + DCHECK(out); + if (!array) + return; + jsize len = env->GetArrayLength(array); + size_t back = out->size(); + out->resize(back + len); + for (jsize i = 0; i < len; ++i) { + ScopedJavaLocalRef<jstring> str(env, + static_cast<jstring>(env->GetObjectArrayElement(array, i))); + ConvertJavaStringToUTF16(env, str.obj(), &((*out)[back + i])); + } +} + +void AppendJavaStringArrayToStringVector(JNIEnv* env, + jobjectArray array, + std::vector<std::string>* out) { + DCHECK(out); + if (!array) + return; + jsize len = env->GetArrayLength(array); + size_t back = out->size(); + out->resize(back + len); + for (jsize i = 0; i < len; ++i) { + ScopedJavaLocalRef<jstring> str(env, + static_cast<jstring>(env->GetObjectArrayElement(array, i))); + ConvertJavaStringToUTF8(env, str.obj(), &((*out)[back + i])); + } +} + +void AppendJavaByteArrayToByteVector(JNIEnv* env, + jbyteArray byte_array, + std::vector<uint8>* out) { + DCHECK(out); + if (!byte_array) + return; + jsize len = env->GetArrayLength(byte_array); + jbyte* bytes = env->GetByteArrayElements(byte_array, NULL); + out->insert(out->end(), bytes, bytes + len); + env->ReleaseByteArrayElements(byte_array, bytes, JNI_ABORT); +} + +void JavaByteArrayToByteVector(JNIEnv* env, + jbyteArray byte_array, + std::vector<uint8>* out) { + DCHECK(out); + out->clear(); + AppendJavaByteArrayToByteVector(env, byte_array, out); +} + +void JavaIntArrayToIntVector(JNIEnv* env, + jintArray array, + std::vector<int>* out) { + DCHECK(out); + out->clear(); + jsize len = env->GetArrayLength(array); + jint* ints = env->GetIntArrayElements(array, NULL); + for (jsize i = 0; i < len; ++i) { + out->push_back(static_cast<int>(ints[i])); + } + env->ReleaseIntArrayElements(array, ints, JNI_ABORT); +} + +void JavaArrayOfByteArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector<std::string>* out) { + DCHECK(out); + out->clear(); + jsize len = env->GetArrayLength(array); + out->resize(len); + for (jsize i = 0; i < len; ++i) { + jbyteArray bytes_array = static_cast<jbyteArray>( + env->GetObjectArrayElement(array, i)); + jsize bytes_len = env->GetArrayLength(bytes_array); + jbyte* bytes = env->GetByteArrayElements(bytes_array, NULL); + (*out)[i].assign(reinterpret_cast<const char*>(bytes), bytes_len); + env->ReleaseByteArrayElements(bytes_array, bytes, JNI_ABORT); + } +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/jni_array.h b/src/base/android/jni_array.h new file mode 100644 index 0000000..fbc131e --- /dev/null +++ b/src/base/android/jni_array.h
@@ -0,0 +1,73 @@ +// 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_ANDROID_JNI_ARRAY_H_ +#define BASE_ANDROID_JNI_ARRAY_H_ + +#include <jni.h> +#include <string> +#include <vector> + +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/string16.h" + +namespace base { +namespace android { + +// Returns a new Java byte array converted from the given bytes array. +BASE_EXPORT ScopedJavaLocalRef<jbyteArray> ToJavaByteArray( + JNIEnv* env, const uint8* bytes, size_t len); + +// Returns a array of Java byte array converted from |v|. +BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfByteArray( + JNIEnv* env, const std::vector<std::string>& v); + +BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings( + JNIEnv* env, const std::vector<std::string>& v); + +BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings( + JNIEnv* env, const std::vector<string16>& v); + +// Converts a Java string array to a native array. +BASE_EXPORT void AppendJavaStringArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector<string16>* out); + +BASE_EXPORT void AppendJavaStringArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector<std::string>* out); + +// Appends the Java bytes in |bytes_array| onto the end of |out|. +BASE_EXPORT void AppendJavaByteArrayToByteVector( + JNIEnv* env, + jbyteArray byte_array, + std::vector<uint8>* out); + +// Replaces the content of |out| with the Java bytes in |bytes_array|. +BASE_EXPORT void JavaByteArrayToByteVector( + JNIEnv* env, + jbyteArray byte_array, + std::vector<uint8>* out); + +// Replaces the content of |out| with the Java ints in |int_array|. +BASE_EXPORT void JavaIntArrayToIntVector( + JNIEnv* env, + jintArray int_array, + std::vector<int>* out); + +// Assuming |array| is an byte[][] (array of byte arrays), replaces the +// content of |out| with the corresponding vector of strings. No UTF-8 +// conversion is performed. +void JavaArrayOfByteArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector<std::string>* out); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_ARRAY_H_
diff --git a/src/base/android/jni_array_unittest.cc b/src/base/android/jni_array_unittest.cc new file mode 100644 index 0000000..a4e3025 --- /dev/null +++ b/src/base/android/jni_array_unittest.cc
@@ -0,0 +1,67 @@ +// 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/android/jni_array.h" + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +TEST(JniArray, BasicConversions) { + const uint8 kBytes[] = { 0, 1, 2, 3 }; + const size_t kLen = arraysize(kBytes); + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jbyteArray> bytes = ToJavaByteArray(env, kBytes, kLen); + ASSERT_TRUE(bytes.obj()); + + std::vector<uint8> vec(5); + JavaByteArrayToByteVector(env, bytes.obj(), &vec); + EXPECT_EQ(4U, vec.size()); + EXPECT_EQ(std::vector<uint8>(kBytes, kBytes + kLen), vec); + + AppendJavaByteArrayToByteVector(env, bytes.obj(), &vec); + EXPECT_EQ(8U, vec.size()); +} + +TEST(JniArray, JavaArrayOfByteArrayToStringVector) { + const int kMaxItems = 50; + JNIEnv* env = AttachCurrentThread(); + + // Create a byte[][] object. + ScopedJavaLocalRef<jclass> byte_array_clazz(env, env->FindClass("[B")); + ASSERT_TRUE(byte_array_clazz.obj()); + + ScopedJavaLocalRef<jobjectArray> array( + env, env->NewObjectArray(kMaxItems, byte_array_clazz.obj(), NULL)); + ASSERT_TRUE(array.obj()); + + // Create kMaxItems byte buffers. + char text[16]; + for (int i = 0; i < kMaxItems; ++i) { + snprintf(text, sizeof text, "%d", i); + ScopedJavaLocalRef<jbyteArray> byte_array = ToJavaByteArray( + env, reinterpret_cast<uint8*>(text), + static_cast<size_t>(strlen(text))); + ASSERT_TRUE(byte_array.obj()); + + env->SetObjectArrayElement(array.obj(), i, byte_array.obj()); + ASSERT_FALSE(HasException(env)); + } + + // Convert to std::vector<std::string>, check the content. + std::vector<std::string> vec; + JavaArrayOfByteArrayToStringVector(env, array.obj(), &vec); + + EXPECT_EQ(static_cast<size_t>(kMaxItems), vec.size()); + for (int i = 0; i < kMaxItems; ++i) { + snprintf(text, sizeof text, "%d", i); + EXPECT_STREQ(text, vec[i].c_str()); + } +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/jni_generator/SampleForTests.java b/src/base/android/jni_generator/SampleForTests.java new file mode 100644 index 0000000..341f0ea --- /dev/null +++ b/src/base/android/jni_generator/SampleForTests.java
@@ -0,0 +1,171 @@ +// 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. + +package org.chromium.example.jni_generator; + +import android.graphics.Rect; + +// This class serves as a reference test for the bindings generator, and as example documentation +// for how to use the jni generator. +// The C++ counter-part is sample_for_tests.cc. +// jni_generator.gyp has a jni_generator_tests target that will: +// * Generate a header file for the JNI bindings based on this file. +// * Compile sample_for_tests.cc using the generated header file. +// * link a native executable to prove the generated header + cc file are self-contained. +// All comments are informational only, and are ignored by the jni generator. +// +// This JNINamespace annotation indicates that all native methods should be +// generated inside this namespace, including the native class that this +// object binds to. +@JNINamespace("base::android") +class SampleForTests { + // Classes can store their C++ pointer counter part as an int that is normally initialized by + // calling out a nativeInit() function. + int nativePtr; + + // You can define methods and attributes on the java class just like any other. + // Methods without the @CalledByNative annotation won't be exposed to JNI. + public SampleForTests() { + } + + public void startExample() { + // Calls native code and holds a pointer to the C++ class. + nativePtr = nativeInit("myParam"); + } + + public void doStuff() { + // This will call CPPClass::Method() using nativePtr as a pointer to the object. This must be + // done to: + // * avoid leaks. + // * using finalizers are not allowed to destroy the cpp class. + nativeMethod(nativePtr); + } + + public void finishExample() { + // We're done, so let's destroy nativePtr object. + nativeDestroy(nativePtr); + } + + // ----------------------------------------------------------------------------------------------- + // The following methods demonstrate exporting Java methods for invocation from C++ code. + // Java functions are mapping into C global functions by prefixing the method name with + // "Java_<Class>_" + // This is triggered by the @CalledByNative annotation; the methods may be named as you wish. + + // Exported to C++ as: + // Java_Example_javaMethod(JNIEnv* env, jobject obj, jint foo, jint bar) + // Typically the C++ code would have obtained the jobject via the Init() call described above. + @CalledByNative + public int javaMethod(int foo, + int bar) { + return 0; + } + + // Exported to C++ as Java_Example_staticJavaMethod(JNIEnv* env) + // Note no jobject argument, as it is static. + @CalledByNative + public static boolean staticJavaMethod() { + return true; + } + + // No prefix, so this method is package private. It will still be exported. + @CalledByNative + void packagePrivateJavaMethod() {} + + // Note the "Unchecked" suffix. By default, @CalledByNative will always generate bindings that + // call CheckException(). With "@CalledByNativeUnchecked", the client C++ code is responsible to + // call ClearException() and act as appropriate. + // See more details at the "@CalledByNativeUnchecked" annotation. + @CalledByNativeUnchecked + void methodThatThrowsException() throws Exception {} + + // The generator is not confused by inline comments: + // @CalledByNative void thisShouldNotAppearInTheOutput(); + // @CalledByNativeUnchecked public static void neitherShouldThis(int foo); + + /** + * The generator is not confused by block comments: + * @CalledByNative void thisShouldNotAppearInTheOutputEither(); + * @CalledByNativeUnchecked public static void andDefinitelyNotThis(int foo); + */ + + // String constants that look like comments don't confuse the generator: + private String arrgh = "*/*"; + + //------------------------------------------------------------------------------------------------ + // Java fields which are accessed from C++ code only must be annotated with @AccessedByNative to + // prevent them being eliminated when unreferenced code is stripped. + @AccessedByNative + private int javaField; + + //------------------------------------------------------------------------------------------------ + // The following methods demonstrate declaring methods to call into C++ from Java. + // The generator detects the "native" and "static" keywords, the type and name of the first + // parameter, and the "native" prefix to the function name to determine the C++ function + // signatures. Besides these constraints the methods can be freely named. + + // This declares a C++ function which the application code must implement: + // static jint Init(JNIEnv* env, jobject obj); + // The jobject parameter refers back to this java side object instance. + // The implementation must return the pointer to the C++ object cast to jint. + // The caller of this method should store it, and supply it as a the nativeCPPClass param to + // subsequent native method calls (see the methods below that take an "int native..." as first + // param). + private native int nativeInit(); + + // This defines a function binding to the associated C++ class member function. The name is + // derived from |nativeDestroy| and |nativeCPPClass| to arrive at CPPClass::Destroy() (i.e. native + // prefixes stripped). + // The |nativeCPPClass| is automatically cast to type CPPClass* in order to obtain the object on + // which to invoke the member function. + private native void nativeDestroy(int nativeCPPClass); + + // This declares a C++ function which the application code must implement: + // static jdouble GetDoubleFunction(JNIEnv* env, jobject obj); + // The jobject parameter refers back to this java side object instance. + private native double nativeGetDoubleFunction(); + + // Similar to nativeGetDoubleFunction(), but here the C++ side will receive a jclass rather than + // jobject param, as the function is declared static. + private static native float nativeGetFloatFunction(); + + // This function takes a non-POD datatype. We have a list mapping them to their full classpath in + // jni_generator.py JavaParamToJni. If you require a new datatype, make sure you add to that + // function. + private native void nativeSetNonPODDatatype(Rect rect); + + // This declares a C++ function which the application code must implement: + // static ScopedJavaLocalRef<jobject> GetNonPODDatatype(JNIEnv* env, jobject obj); + // The jobject parameter refers back to this java side object instance. + // Note that it returns a ScopedJavaLocalRef<jobject> so that you don' have to worry about + // deleting the JNI local reference. This is similar with Strings and arrays. + private native Object nativeGetNonPODDatatype(); + + // Similar to nativeDestroy above, this will cast nativeCPPClass into pointer of CPPClass type and + // call its Method member function. + private native int nativeMethod(int nativeCPPClass); + + // Similar to nativeMethod above, but here the C++ fully qualified class name is taken from the + // annotation rather than parameter name, which can thus be chosen freely. + @NativeClassQualifiedName("CPPClass::InnerClass") + private native double nativeMethodOtherP0(int nativePtr); + + // An inner class has some special attributes for annotation. + class InnerClass { + @CalledByNative("InnerClass") + public float JavaInnerMethod() { + } + + @CalledByNative("InnerClass") + public static void javaInnerFunction() { + } + + @NativeCall("InnerClass") + private static native int nativeInnerFunction(); + + @NativeCall("InnerClass") + private static native String nativeInnerMethod(int nativeCPPClass); + + } +}
diff --git a/src/base/android/jni_generator/golden_sample_for_tests_jni.h b/src/base/android/jni_generator/golden_sample_for_tests_jni.h new file mode 100644 index 0000000..08f25a7 --- /dev/null +++ b/src/base/android/jni_generator/golden_sample_for_tests_jni.h
@@ -0,0 +1,293 @@ +// 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator_tests.py +// For +// org/chromium/example/jni_generator/SampleForTests + +#ifndef org_chromium_example_jni_generator_SampleForTests_JNI +#define org_chromium_example_jni_generator_SampleForTests_JNI + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +const char kInnerClassClassPath[] = + "org/chromium/example/jni_generator/SampleForTests$InnerClass"; +const char kSampleForTestsClassPath[] = + "org/chromium/example/jni_generator/SampleForTests"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_InnerClass_clazz = NULL; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_SampleForTests_clazz = NULL; +} // namespace + +namespace base { +namespace android { + +static jint Init(JNIEnv* env, jobject obj); + +static jdouble GetDoubleFunction(JNIEnv* env, jobject obj); + +static jfloat GetFloatFunction(JNIEnv* env, jclass clazz); + +static void SetNonPODDatatype(JNIEnv* env, jobject obj, + jobject rect); + +static jobject GetNonPODDatatype(JNIEnv* env, jobject obj); + +static jint InnerFunction(JNIEnv* env, jclass clazz); + +// Step 2: method stubs. +static void Destroy(JNIEnv* env, jobject obj, + jint nativeCPPClass) { + DCHECK(nativeCPPClass) << "Destroy"; + CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass); + return native->Destroy(env, obj); +} + +static jint Method(JNIEnv* env, jobject obj, + jint nativeCPPClass) { + DCHECK(nativeCPPClass) << "Method"; + CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass); + return native->Method(env, obj); +} + +static jdouble MethodOtherP0(JNIEnv* env, jobject obj, + jint nativePtr) { + DCHECK(nativePtr) << "MethodOtherP0"; + CPPClass::InnerClass* native = + reinterpret_cast<CPPClass::InnerClass*>(nativePtr); + return native->MethodOtherP0(env, obj); +} + +static jstring InnerMethod(JNIEnv* env, jobject obj, + jint nativeCPPClass) { + DCHECK(nativeCPPClass) << "InnerMethod"; + CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass); + return native->InnerMethod(env, obj).Release(); +} + +static base::subtle::AtomicWord g_SampleForTests_javaMethod = 0; +static jint Java_SampleForTests_javaMethod(JNIEnv* env, jobject obj, jint foo, + jint bar) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_SampleForTests_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_SampleForTests_clazz, + "javaMethod", + +"(" +"I" +"I" +")" +"I", + &g_SampleForTests_javaMethod); + + jint ret = + env->CallIntMethod(obj, + method_id, foo, bar); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_SampleForTests_staticJavaMethod = 0; +static jboolean Java_SampleForTests_staticJavaMethod(JNIEnv* env) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_SampleForTests_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, g_SampleForTests_clazz, + "staticJavaMethod", + +"(" +")" +"Z", + &g_SampleForTests_staticJavaMethod); + + jboolean ret = + env->CallStaticBooleanMethod(g_SampleForTests_clazz, + method_id); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_SampleForTests_packagePrivateJavaMethod = 0; +static void Java_SampleForTests_packagePrivateJavaMethod(JNIEnv* env, jobject + obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_SampleForTests_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_SampleForTests_clazz, + "packagePrivateJavaMethod", + +"(" +")" +"V", + &g_SampleForTests_packagePrivateJavaMethod); + + env->CallVoidMethod(obj, + method_id); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_SampleForTests_methodThatThrowsException = 0; +static void Java_SampleForTests_methodThatThrowsException(JNIEnv* env, jobject + obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_SampleForTests_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_SampleForTests_clazz, + "methodThatThrowsException", + +"(" +")" +"V", + &g_SampleForTests_methodThatThrowsException); + + env->CallVoidMethod(obj, + method_id); + +} + +static base::subtle::AtomicWord g_InnerClass_JavaInnerMethod = 0; +static jfloat Java_InnerClass_JavaInnerMethod(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InnerClass_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InnerClass_clazz, + "JavaInnerMethod", + +"(" +")" +"F", + &g_InnerClass_JavaInnerMethod); + + jfloat ret = + env->CallFloatMethod(obj, + method_id); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InnerClass_javaInnerFunction = 0; +static void Java_InnerClass_javaInnerFunction(JNIEnv* env) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InnerClass_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, g_InnerClass_clazz, + "javaInnerFunction", + +"(" +")" +"V", + &g_InnerClass_javaInnerFunction); + + env->CallStaticVoidMethod(g_InnerClass_clazz, + method_id); + base::android::CheckException(env); + +} + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_InnerClass_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetUnscopedClass(env, kInnerClassClassPath))); + g_SampleForTests_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetUnscopedClass(env, kSampleForTestsClassPath))); + static const JNINativeMethod kMethodsInnerClass[] = { + { "nativeInnerFunction", +"(" +")" +"I", reinterpret_cast<void*>(InnerFunction) }, + { "nativeInnerMethod", +"(" +"I" +")" +"Ljava/lang/String;", reinterpret_cast<void*>(InnerMethod) }, + }; + const int kMethodsInnerClassSize = arraysize(kMethodsInnerClass); + + if (env->RegisterNatives(g_InnerClass_clazz, + kMethodsInnerClass, + kMethodsInnerClassSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + static const JNINativeMethod kMethodsSampleForTests[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast<void*>(Init) }, + { "nativeDestroy", +"(" +"I" +")" +"V", reinterpret_cast<void*>(Destroy) }, + { "nativeGetDoubleFunction", +"(" +")" +"D", reinterpret_cast<void*>(GetDoubleFunction) }, + { "nativeGetFloatFunction", +"(" +")" +"F", reinterpret_cast<void*>(GetFloatFunction) }, + { "nativeSetNonPODDatatype", +"(" +"Landroid/graphics/Rect;" +")" +"V", reinterpret_cast<void*>(SetNonPODDatatype) }, + { "nativeGetNonPODDatatype", +"(" +")" +"Ljava/lang/Object;", reinterpret_cast<void*>(GetNonPODDatatype) }, + { "nativeMethod", +"(" +"I" +")" +"I", reinterpret_cast<void*>(Method) }, + { "nativeMethodOtherP0", +"(" +"I" +")" +"D", reinterpret_cast<void*>(MethodOtherP0) }, + }; + const int kMethodsSampleForTestsSize = arraysize(kMethodsSampleForTests); + + if (env->RegisterNatives(g_SampleForTests_clazz, + kMethodsSampleForTests, + kMethodsSampleForTestsSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + return true; +} +} // namespace android +} // namespace base + +#endif // org_chromium_example_jni_generator_SampleForTests_JNI
diff --git a/src/base/android/jni_generator/jni_generator.gyp b/src/base/android/jni_generator/jni_generator.gyp new file mode 100644 index 0000000..dc32d22 --- /dev/null +++ b/src/base/android/jni_generator/jni_generator.gyp
@@ -0,0 +1,55 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'jni_generator_py_tests', + 'type': 'none', + 'actions': [ + { + 'action_name': 'run_jni_generator_py_tests', + 'inputs': [ + 'jni_generator.py', + 'jni_generator_tests.py', + 'SampleForTests.java', + 'golden_sample_for_tests_jni.h', + ], + 'outputs': [ + '', + ], + 'action': [ + 'python', 'jni_generator_tests.py', + ], + }, + ], + }, + { + 'target_name': 'jni_sample_header', + 'type': 'none', + 'sources': [ + 'SampleForTests.java', + ], + 'variables': { + 'jni_gen_dir': 'base', + }, + 'includes': [ '../../../build/jni_generator.gypi' ], + }, + { + 'target_name': 'jni_generator_tests', + 'type': 'executable', + 'dependencies': [ + '../../base.gyp:test_support_base', + 'jni_generator_py_tests', + 'jni_sample_header', + ], + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/base', + ], + 'sources': [ + 'sample_for_tests.cc', + ], + }, + ], +}
diff --git a/src/base/android/jni_generator/jni_generator.py b/src/base/android/jni_generator/jni_generator.py new file mode 100755 index 0000000..18312c6 --- /dev/null +++ b/src/base/android/jni_generator/jni_generator.py
@@ -0,0 +1,1005 @@ +#!/usr/bin/env python +# 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. + +"""Extracts native methods from a Java file and generates the JNI bindings. +If you change this, please run and update the tests.""" + +import collections +import optparse +import os +import re +import string +from string import Template +import subprocess +import sys +import textwrap +import zipfile + + +class ParseError(Exception): + """Exception thrown when we can't parse the input file.""" + + def __init__(self, description, *context_lines): + Exception.__init__(self) + self.description = description + self.context_lines = context_lines + + def __str__(self): + context = '\n'.join(self.context_lines) + return '***\nERROR: %s\n\n%s\n***' % (self.description, context) + + +class Param(object): + """Describes a param for a method, either java or native.""" + + def __init__(self, **kwargs): + self.datatype = kwargs['datatype'] + self.name = kwargs['name'] + + +class NativeMethod(object): + """Describes a C/C++ method that is called by Java code""" + + def __init__(self, **kwargs): + self.static = kwargs['static'] + self.java_class_name = kwargs['java_class_name'] + self.return_type = kwargs['return_type'] + self.name = kwargs['name'] + self.params = kwargs['params'] + if self.params: + assert type(self.params) is list + assert type(self.params[0]) is Param + if (self.params and + self.params[0].datatype == 'int' and + self.params[0].name.startswith('native')): + self.type = 'method' + self.p0_type = self.params[0].name[len('native'):] + if kwargs.get('native_class_name'): + self.p0_type = kwargs['native_class_name'] + else: + self.type = 'function' + self.method_id_var_name = kwargs.get('method_id_var_name', None) + + +class CalledByNative(object): + """Describes a java method exported to c/c++""" + + def __init__(self, **kwargs): + self.system_class = kwargs['system_class'] + self.unchecked = kwargs['unchecked'] + self.static = kwargs['static'] + self.java_class_name = kwargs['java_class_name'] + self.return_type = kwargs['return_type'] + self.name = kwargs['name'] + self.params = kwargs['params'] + self.method_id_var_name = kwargs.get('method_id_var_name', None) + self.is_constructor = kwargs.get('is_constructor', False) + self.env_call = GetEnvCall(self.is_constructor, self.static, + self.return_type) + self.static_cast = GetStaticCastForReturnType(self.return_type) + + +def JavaDataTypeToC(java_type): + """Returns a C datatype for the given java type.""" + java_pod_type_map = { + 'int': 'jint', + 'byte': 'jbyte', + 'boolean': 'jboolean', + 'long': 'jlong', + 'double': 'jdouble', + 'float': 'jfloat', + } + java_type_map = { + 'void': 'void', + 'String': 'jstring', + 'java/lang/String': 'jstring', + 'Class': 'jclass', + 'java/lang/Class': 'jclass', + } + + if java_type in java_pod_type_map: + return java_pod_type_map[java_type] + elif java_type in java_type_map: + return java_type_map[java_type] + elif java_type.endswith('[]'): + if java_type[:-2] in java_pod_type_map: + return java_pod_type_map[java_type[:-2]] + 'Array' + return 'jobjectArray' + else: + return 'jobject' + + +class JniParams(object): + _imports = [] + _fully_qualified_class = '' + _package = '' + _inner_classes = [] + + @staticmethod + def SetFullyQualifiedClass(fully_qualified_class): + JniParams._fully_qualified_class = 'L' + fully_qualified_class + JniParams._package = '/'.join(fully_qualified_class.split('/')[:-1]) + + @staticmethod + def ExtractImportsAndInnerClasses(contents): + contents = contents.replace('\n', '') + re_import = re.compile(r'import.*?(?P<class>\S*?);') + for match in re.finditer(re_import, contents): + JniParams._imports += ['L' + match.group('class').replace('.', '/')] + + re_inner = re.compile(r'(class|interface)\s+?(?P<name>\w+?)\W') + for match in re.finditer(re_inner, contents): + inner = match.group('name') + if not JniParams._fully_qualified_class.endswith(inner): + JniParams._inner_classes += [JniParams._fully_qualified_class + '$' + + inner] + + @staticmethod + def JavaToJni(param): + """Converts a java param into a JNI signature type.""" + pod_param_map = { + 'int': 'I', + 'boolean': 'Z', + 'long': 'J', + 'double': 'D', + 'float': 'F', + 'byte': 'B', + 'void': 'V', + } + object_param_list = [ + 'Ljava/lang/Boolean', + 'Ljava/lang/Integer', + 'Ljava/lang/Long', + 'Ljava/lang/Object', + 'Ljava/lang/String', + 'Ljava/lang/Class', + ] + if param == 'byte[][]': + return '[[B' + prefix = '' + # Array? + if param[-2:] == '[]': + prefix = '[' + param = param[:-2] + # Generic? + if '<' in param: + param = param[:param.index('<')] + if param in pod_param_map: + return prefix + pod_param_map[param] + if '/' in param: + # Coming from javap, use the fully qualified param directly. + return 'L' + param + ';' + for qualified_name in (object_param_list + + [JniParams._fully_qualified_class] + + JniParams._inner_classes): + if (qualified_name.endswith('/' + param) or + qualified_name.endswith('$' + param.replace('.', '$')) or + qualified_name == 'L' + param): + return prefix + qualified_name + ';' + + # Is it from an import? (e.g. referecing Class from import pkg.Class; + # note that referencing an inner class Inner from import pkg.Class.Inner + # is not supported). + for qualified_name in JniParams._imports: + if qualified_name.endswith('/' + param): + # Ensure it's not an inner class. + components = qualified_name.split('/') + if len(components) > 2 and components[-2][0].isupper(): + raise SyntaxError('Inner class (%s) can not be imported ' + 'and used by JNI (%s). Please import the outer ' + 'class and use Outer.Inner instead.' % + (qualified_name, param)) + return prefix + qualified_name + ';' + + # Is it an inner class from an outer class import? (e.g. referencing + # Class.Inner from import pkg.Class). + if '.' in param: + components = param.split('.') + outer = '/'.join(components[:-1]) + inner = components[-1] + for qualified_name in JniParams._imports: + if qualified_name.endswith('/' + outer): + return prefix + qualified_name + '$' + inner + + # Type not found, falling back to same package as this class. + return prefix + 'L' + JniParams._package + '/' + param + ';' + + @staticmethod + def Signature(params, returns, wrap): + """Returns the JNI signature for the given datatypes.""" + items = ['('] + items += [JniParams.JavaToJni(param.datatype) for param in params] + items += [')'] + items += [JniParams.JavaToJni(returns)] + if wrap: + return '\n' + '\n'.join(['"' + item + '"' for item in items]) + else: + return '"' + ''.join(items) + '"' + + @staticmethod + def Parse(params): + """Parses the params into a list of Param objects.""" + if not params: + return [] + ret = [] + for p in [p.strip() for p in params.split(',')]: + items = p.split(' ') + if 'final' in items: + items.remove('final') + param = Param( + datatype=items[0], + name=(items[1] if len(items) > 1 else 'p%s' % len(ret)), + ) + ret += [param] + return ret + + +def ExtractJNINamespace(contents): + re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)') + m = re.findall(re_jni_namespace, contents) + if not m: + return '' + return m[0] + + +def ExtractFullyQualifiedJavaClassName(java_file_name, contents): + re_package = re.compile('.*?package (.*?);') + matches = re.findall(re_package, contents) + if not matches: + raise SyntaxError('Unable to find "package" line in %s' % java_file_name) + return (matches[0].replace('.', '/') + '/' + + os.path.splitext(os.path.basename(java_file_name))[0]) + + +def ExtractNatives(contents): + """Returns a list of dict containing information about a native method.""" + contents = contents.replace('\n', '') + natives = [] + re_native = re.compile(r'(@NativeClassQualifiedName' + '\(\"(?P<native_class_name>.*?)\"\))?\s*' + '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\)))?\s*' + '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*?native ' + '(?P<return>\S*?) ' + '(?P<name>\w+?)\((?P<params>.*?)\);') + for match in re.finditer(re_native, contents): + native = NativeMethod( + static='static' in match.group('qualifiers'), + java_class_name=match.group('java_class_name'), + native_class_name=match.group('native_class_name'), + return_type=match.group('return'), + name=match.group('name').replace('native', ''), + params=JniParams.Parse(match.group('params'))) + natives += [native] + return natives + + +def GetStaticCastForReturnType(return_type): + if return_type in ['String', 'java/lang/String']: + return 'jstring' + elif return_type.endswith('[]'): + return 'jobjectArray' + return None + + +def GetEnvCall(is_constructor, is_static, return_type): + """Maps the types availabe via env->Call__Method.""" + if is_constructor: + return 'NewObject' + env_call_map = {'boolean': 'Boolean', + 'byte': 'Byte', + 'char': 'Char', + 'short': 'Short', + 'int': 'Int', + 'long': 'Long', + 'float': 'Float', + 'void': 'Void', + 'double': 'Double', + 'Object': 'Object', + } + call = env_call_map.get(return_type, 'Object') + if is_static: + call = 'Static' + call + return 'Call' + call + 'Method' + + +def GetMangledParam(datatype): + """Returns a mangled identifier for the datatype.""" + if len(datatype) <= 2: + return datatype.replace('[', 'A') + ret = '' + for i in range(1, len(datatype)): + c = datatype[i] + if c == '[': + ret += 'A' + elif c.isupper() or datatype[i - 1] in ['/', 'L']: + ret += c.upper() + return ret + + +def GetMangledMethodName(name, params, return_type): + """Returns a mangled method name for the given signature. + + The returned name can be used as a C identifier and will be unique for all + valid overloads of the same method. + + Args: + name: string. + params: list of Param. + return_type: string. + + Returns: + A mangled name. + """ + mangled_items = [] + for datatype in [return_type] + [x.datatype for x in params]: + mangled_items += [GetMangledParam(JniParams.JavaToJni(datatype))] + mangled_name = name + '_'.join(mangled_items) + assert re.match(r'[0-9a-zA-Z_]+', mangled_name) + return mangled_name + + +def MangleCalledByNatives(called_by_natives): + """Mangles all the overloads from the call_by_natives list.""" + method_counts = collections.defaultdict( + lambda: collections.defaultdict(lambda: 0)) + for called_by_native in called_by_natives: + java_class_name = called_by_native.java_class_name + name = called_by_native.name + method_counts[java_class_name][name] += 1 + for called_by_native in called_by_natives: + java_class_name = called_by_native.java_class_name + method_name = called_by_native.name + method_id_var_name = method_name + if method_counts[java_class_name][method_name] > 1: + method_id_var_name = GetMangledMethodName(method_name, + called_by_native.params, + called_by_native.return_type) + called_by_native.method_id_var_name = method_id_var_name + return called_by_natives + + +# Regex to match the JNI return types that should be included in a +# ScopedJavaLocalRef. +RE_SCOPED_JNI_RETURN_TYPES = re.compile('jobject|jclass|jstring|.*Array') + +# Regex to match a string like "@CalledByNative public void foo(int bar)". +RE_CALLED_BY_NATIVE = re.compile( + '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?' + '\s+(?P<prefix>[\w ]*?)' + '\s*(?P<return_type>\w+)' + '\s+(?P<name>\w+)' + '\s*\((?P<params>[^\)]*)\)') + + +def ExtractCalledByNatives(contents): + """Parses all methods annotated with @CalledByNative. + + Args: + contents: the contents of the java file. + + Returns: + A list of dict with information about the annotated methods. + TODO(bulach): return a CalledByNative object. + + Raises: + ParseError: if unable to parse. + """ + called_by_natives = [] + for match in re.finditer(RE_CALLED_BY_NATIVE, contents): + called_by_natives += [CalledByNative( + system_class=False, + unchecked='Unchecked' in match.group('Unchecked'), + static='static' in match.group('prefix'), + java_class_name=match.group('annotation') or '', + return_type=match.group('return_type'), + name=match.group('name'), + params=JniParams.Parse(match.group('params')))] + # Check for any @CalledByNative occurrences that weren't matched. + unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n') + for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]): + if '@CalledByNative' in line1: + raise ParseError('could not parse @CalledByNative method signature', + line1, line2) + return MangleCalledByNatives(called_by_natives) + + +class JNIFromJavaP(object): + """Uses 'javap' to parse a .class file and generate the JNI header file.""" + + def __init__(self, contents, namespace): + self.contents = contents + self.namespace = namespace + self.fully_qualified_class = re.match('.*?class (?P<class_name>.*?) ', + contents[1]).group('class_name') + self.fully_qualified_class = self.fully_qualified_class.replace('.', '/') + JniParams.SetFullyQualifiedClass(self.fully_qualified_class) + self.java_class_name = self.fully_qualified_class.split('/')[-1] + if not self.namespace: + self.namespace = 'JNI_' + self.java_class_name + re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)' + '\((?P<params>.*?)\)') + self.called_by_natives = [] + for content in contents[2:]: + match = re.match(re_method, content) + if not match: + continue + self.called_by_natives += [CalledByNative( + system_class=True, + unchecked=False, + static='static' in match.group('prefix'), + java_class_name='', + return_type=match.group('return_type').replace('.', '/'), + name=match.group('name'), + params=JniParams.Parse(match.group('params').replace('.', '/')))] + re_constructor = re.compile('.*? public ' + + self.fully_qualified_class.replace('/', '.') + + '\((?P<params>.*?)\)') + for content in contents[2:]: + match = re.match(re_constructor, content) + if not match: + continue + self.called_by_natives += [CalledByNative( + system_class=True, + unchecked=False, + static=False, + java_class_name='', + return_type=self.fully_qualified_class, + name='Constructor', + params=JniParams.Parse(match.group('params').replace('.', '/')), + is_constructor=True)] + self.called_by_natives = MangleCalledByNatives(self.called_by_natives) + self.inl_header_file_generator = InlHeaderFileGenerator( + self.namespace, self.fully_qualified_class, [], self.called_by_natives) + + def GetContent(self): + return self.inl_header_file_generator.GetContent() + + @staticmethod + def CreateFromClass(class_file, namespace): + class_name = os.path.splitext(os.path.basename(class_file))[0] + p = subprocess.Popen(args=['javap', class_name], + cwd=os.path.dirname(class_file), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, _ = p.communicate() + jni_from_javap = JNIFromJavaP(stdout.split('\n'), namespace) + return jni_from_javap + + +class JNIFromJavaSource(object): + """Uses the given java source file to generate the JNI header file.""" + + def __init__(self, contents, fully_qualified_class): + contents = self._RemoveComments(contents) + JniParams.SetFullyQualifiedClass(fully_qualified_class) + JniParams.ExtractImportsAndInnerClasses(contents) + jni_namespace = ExtractJNINamespace(contents) + natives = ExtractNatives(contents) + called_by_natives = ExtractCalledByNatives(contents) + if len(natives) == 0 and len(called_by_natives) == 0: + raise SyntaxError('Unable to find any JNI methods for %s.' % + fully_qualified_class) + inl_header_file_generator = InlHeaderFileGenerator( + jni_namespace, fully_qualified_class, natives, called_by_natives) + self.content = inl_header_file_generator.GetContent() + + def _RemoveComments(self, contents): + # We need to support both inline and block comments, and we need to handle + # strings that contain '//' or '/*'. Rather than trying to do all that with + # regexps, we just pipe the contents through the C preprocessor. We tell cpp + # the file has already been preprocessed, so it just removes comments and + # doesn't try to parse #include, #pragma etc. + # + # TODO(husky): This is a bit hacky. It would be cleaner to use a real Java + # parser. Maybe we could ditch JNIFromJavaSource and just always use + # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT. + # http://code.google.com/p/chromium/issues/detail?id=138941 + p = subprocess.Popen(args=['cpp', '-fpreprocessed'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, _ = p.communicate(contents) + return stdout + + def GetContent(self): + return self.content + + @staticmethod + def CreateFromFile(java_file_name): + contents = file(java_file_name).read() + fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name, + contents) + return JNIFromJavaSource(contents, fully_qualified_class) + + +class InlHeaderFileGenerator(object): + """Generates an inline header file for JNI integration.""" + + def __init__(self, namespace, fully_qualified_class, natives, + called_by_natives): + self.namespace = namespace + self.fully_qualified_class = fully_qualified_class + self.class_name = self.fully_qualified_class.split('/')[-1] + self.natives = natives + self.called_by_natives = called_by_natives + self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI' + + def GetContent(self): + """Returns the content of the JNI binding file.""" + template = Template("""\ +// 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. + + +// This file is autogenerated by +// ${SCRIPT_NAME} +// For +// ${FULLY_QUALIFIED_CLASS} + +#ifndef ${HEADER_GUARD} +#define ${HEADER_GUARD} + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +$CLASS_PATH_DEFINITIONS +} // namespace + +$OPEN_NAMESPACE +$FORWARD_DECLARATIONS + +// Step 2: method stubs. +$METHOD_STUBS + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { +$REGISTER_NATIVES_IMPL + return true; +} +$CLOSE_NAMESPACE +#endif // ${HEADER_GUARD} +""") + script_components = os.path.abspath(sys.argv[0]).split(os.path.sep) + base_index = script_components.index('base') + script_name = os.sep.join(script_components[base_index:]) + values = { + 'SCRIPT_NAME': script_name, + 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class, + 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(), + 'FORWARD_DECLARATIONS': self.GetForwardDeclarationsString(), + 'METHOD_STUBS': self.GetMethodStubsString(), + 'OPEN_NAMESPACE': self.GetOpenNamespaceString(), + 'REGISTER_NATIVES_IMPL': self.GetRegisterNativesImplString(), + 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(), + 'HEADER_GUARD': self.header_guard, + } + return WrapOutput(template.substitute(values)) + + def GetClassPathDefinitionsString(self): + ret = [] + ret += [self.GetClassPathDefinitions()] + return '\n'.join(ret) + + def GetForwardDeclarationsString(self): + ret = [] + for native in self.natives: + if native.type != 'method': + ret += [self.GetForwardDeclaration(native)] + return '\n'.join(ret) + + def GetMethodStubsString(self): + ret = [] + for native in self.natives: + if native.type == 'method': + ret += [self.GetNativeMethodStub(native)] + for called_by_native in self.called_by_natives: + ret += [self.GetCalledByNativeMethodStub(called_by_native)] + return '\n'.join(ret) + + def GetKMethodsString(self, clazz): + ret = [] + for native in self.natives: + if (native.java_class_name == clazz or + (not native.java_class_name and clazz == self.class_name)): + ret += [self.GetKMethodArrayEntry(native)] + return '\n'.join(ret) + + def GetRegisterNativesImplString(self): + """Returns the implementation for RegisterNatives.""" + template = Template("""\ + static const JNINativeMethod kMethods${JAVA_CLASS}[] = { +${KMETHODS} + }; + const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS}); + + if (env->RegisterNatives(g_${JAVA_CLASS}_clazz, + kMethods${JAVA_CLASS}, + kMethods${JAVA_CLASS}Size) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } +""") + ret = [self.GetFindClasses()] + all_classes = self.GetUniqueClasses(self.natives) + all_classes[self.class_name] = self.fully_qualified_class + for clazz in all_classes: + kmethods = self.GetKMethodsString(clazz) + if kmethods: + values = {'JAVA_CLASS': clazz, + 'KMETHODS': kmethods} + ret += [template.substitute(values)] + if not ret: return '' + return '\n' + '\n'.join(ret) + + def GetOpenNamespaceString(self): + if self.namespace: + all_namespaces = ['namespace %s {' % ns + for ns in self.namespace.split('::')] + return '\n'.join(all_namespaces) + return '' + + def GetCloseNamespaceString(self): + if self.namespace: + all_namespaces = ['} // namespace %s' % ns + for ns in self.namespace.split('::')] + all_namespaces.reverse() + return '\n'.join(all_namespaces) + '\n' + return '' + + def GetJNIFirstParam(self, native): + ret = [] + if native.type == 'method': + ret = ['jobject obj'] + elif native.type == 'function': + if native.static: + ret = ['jclass clazz'] + else: + ret = ['jobject obj'] + return ret + + def GetParamsInDeclaration(self, native): + """Returns the params for the stub declaration. + + Args: + native: the native dictionary describing the method. + + Returns: + A string containing the params. + """ + return ',\n '.join(self.GetJNIFirstParam(native) + + [JavaDataTypeToC(param.datatype) + ' ' + + param.name + for param in native.params]) + + def GetCalledByNativeParamsInDeclaration(self, called_by_native): + return ',\n '.join([JavaDataTypeToC(param.datatype) + ' ' + + param.name + for param in called_by_native.params]) + + def GetForwardDeclaration(self, native): + template = Template(""" +static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS}); +""") + values = {'RETURN': JavaDataTypeToC(native.return_type), + 'NAME': native.name, + 'PARAMS': self.GetParamsInDeclaration(native)} + return template.substitute(values) + + def GetNativeMethodStub(self, native): + """Returns stubs for native methods.""" + template = Template("""\ +static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) { + DCHECK(${PARAM0_NAME}) << "${NAME}"; + ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME}); + return native->${NAME}(env, obj${PARAMS_IN_CALL})${POST_CALL}; +} +""") + params_for_call = ', '.join(p.name for p in native.params[1:]) + if params_for_call: + params_for_call = ', ' + params_for_call + + return_type = JavaDataTypeToC(native.return_type) + if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type): + scoped_return_type = 'ScopedJavaLocalRef<' + return_type + '>' + post_call = '.Release()' + else: + scoped_return_type = return_type + post_call = '' + values = { + 'RETURN': return_type, + 'SCOPED_RETURN': scoped_return_type, + 'NAME': native.name, + 'PARAMS_IN_DECLARATION': self.GetParamsInDeclaration(native), + 'PARAM0_NAME': native.params[0].name, + 'P0_TYPE': native.p0_type, + 'PARAMS_IN_CALL': params_for_call, + 'POST_CALL': post_call + } + return template.substitute(values) + + def GetCalledByNativeMethodStub(self, called_by_native): + """Returns a string.""" + function_signature_template = Template("""\ +static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\ +JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""") + function_header_template = Template("""\ +${FUNCTION_SIGNATURE} {""") + function_header_with_unused_template = Template("""\ +${FUNCTION_SIGNATURE} __attribute__ ((unused)); +${FUNCTION_SIGNATURE} {""") + template = Template(""" +static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0; +${FUNCTION_HEADER} + /* Must call RegisterNativesImpl() */ + DCHECK(g_${JAVA_CLASS}_clazz); + jmethodID method_id = + ${GET_METHOD_ID_IMPL} + ${RETURN_DECLARATION} + ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL}, + method_id${PARAMS_IN_CALL})${POST_CALL}; + ${CHECK_EXCEPTION} + ${RETURN_CLAUSE} +}""") + if called_by_native.static or called_by_native.is_constructor: + first_param_in_declaration = '' + first_param_in_call = ('g_%s_clazz' % + (called_by_native.java_class_name or + self.class_name)) + else: + first_param_in_declaration = ', jobject obj' + first_param_in_call = 'obj' + params_in_declaration = self.GetCalledByNativeParamsInDeclaration( + called_by_native) + if params_in_declaration: + params_in_declaration = ', ' + params_in_declaration + params_for_call = ', '.join(param.name + for param in called_by_native.params) + if params_for_call: + params_for_call = ', ' + params_for_call + pre_call = '' + post_call = '' + if called_by_native.static_cast: + pre_call = 'static_cast<%s>(' % called_by_native.static_cast + post_call = ')' + check_exception = '' + if not called_by_native.unchecked: + check_exception = 'base::android::CheckException(env);' + return_type = JavaDataTypeToC(called_by_native.return_type) + return_declaration = '' + return_clause = '' + if return_type != 'void': + pre_call = ' ' + pre_call + return_declaration = return_type + ' ret =' + if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type): + return_type = 'ScopedJavaLocalRef<' + return_type + '>' + return_clause = 'return ' + return_type + '(env, ret);' + else: + return_clause = 'return ret;' + values = { + 'JAVA_CLASS': called_by_native.java_class_name or self.class_name, + 'METHOD': called_by_native.name, + 'RETURN_TYPE': return_type, + 'RETURN_DECLARATION': return_declaration, + 'RETURN_CLAUSE': return_clause, + 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration, + 'PARAMS_IN_DECLARATION': params_in_declaration, + 'STATIC': 'Static' if called_by_native.static else '', + 'PRE_CALL': pre_call, + 'POST_CALL': post_call, + 'ENV_CALL': called_by_native.env_call, + 'FIRST_PARAM_IN_CALL': first_param_in_call, + 'PARAMS_IN_CALL': params_for_call, + 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, + 'CHECK_EXCEPTION': check_exception, + 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native) + } + values['FUNCTION_SIGNATURE'] = ( + function_signature_template.substitute(values)) + if called_by_native.system_class: + values['FUNCTION_HEADER'] = ( + function_header_with_unused_template.substitute(values)) + else: + values['FUNCTION_HEADER'] = function_header_template.substitute(values) + return template.substitute(values) + + def GetKMethodArrayEntry(self, native): + template = Template("""\ + { "native${NAME}", ${JNI_SIGNATURE}, reinterpret_cast<void*>(${NAME}) },""") + values = {'NAME': native.name, + 'JNI_SIGNATURE': JniParams.Signature(native.params, + native.return_type, + True)} + return template.substitute(values) + + def GetUniqueClasses(self, origin): + ret = {self.class_name: self.fully_qualified_class} + for entry in origin: + class_name = self.class_name + jni_class_path = self.fully_qualified_class + if entry.java_class_name: + class_name = entry.java_class_name + jni_class_path = self.fully_qualified_class + '$' + class_name + ret[class_name] = jni_class_path + return ret + + def GetClassPathDefinitions(self): + """Returns the ClassPath constants.""" + ret = [] + template = Template("""\ +const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""") + native_classes = self.GetUniqueClasses(self.natives) + called_by_native_classes = self.GetUniqueClasses(self.called_by_natives) + all_classes = native_classes + all_classes.update(called_by_native_classes) + for clazz in all_classes: + values = { + 'JAVA_CLASS': clazz, + 'JNI_CLASS_PATH': all_classes[clazz], + } + ret += [template.substitute(values)] + ret += '' + for clazz in called_by_native_classes: + template = Template("""\ +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_${JAVA_CLASS}_clazz = NULL;""") + values = { + 'JAVA_CLASS': clazz, + } + ret += [template.substitute(values)] + return '\n'.join(ret) + + def GetFindClasses(self): + """Returns the imlementation of FindClass for all known classes.""" + template = Template("""\ + g_${JAVA_CLASS}_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetUnscopedClass(env, k${JAVA_CLASS}ClassPath)));""") + ret = [] + for clazz in self.GetUniqueClasses(self.called_by_natives): + values = {'JAVA_CLASS': clazz} + ret += [template.substitute(values)] + return '\n'.join(ret) + + def GetMethodIDImpl(self, called_by_native): + """Returns the implementation of GetMethodID.""" + template = Template("""\ + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_${STATIC}>( + env, g_${JAVA_CLASS}_clazz, + "${JNI_NAME}", + ${JNI_SIGNATURE}, + &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}); +""") + jni_name = called_by_native.name + jni_return_type = called_by_native.return_type + if called_by_native.is_constructor: + jni_name = '<init>' + jni_return_type = 'void' + values = { + 'JAVA_CLASS': called_by_native.java_class_name or self.class_name, + 'JNI_NAME': jni_name, + 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, + 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE', + 'JNI_SIGNATURE': JniParams.Signature(called_by_native.params, + jni_return_type, + True) + } + return template.substitute(values) + + +def WrapOutput(output): + ret = [] + for line in output.splitlines(): + # Do not wrap lines under 80 characters or preprocessor directives. + if len(line) < 80 or line.lstrip()[:1] == '#': + stripped = line.rstrip() + if len(ret) == 0 or len(ret[-1]) or len(stripped): + ret.append(stripped) + else: + first_line_indent = ' ' * (len(line) - len(line.lstrip())) + subsequent_indent = first_line_indent + ' ' * 4 + if line.startswith('//'): + subsequent_indent = '//' + subsequent_indent + wrapper = textwrap.TextWrapper(width=80, + subsequent_indent=subsequent_indent, + break_long_words=False) + ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)] + ret += [''] + return '\n'.join(ret) + + +def ExtractJarInputFile(jar_file, input_file, out_dir): + """Extracts input file from jar and returns the filename. + + The input file is extracted to the same directory that the generated jni + headers will be placed in. This is passed as an argument to script. + + Args: + jar_file: the jar file containing the input files to extract. + input_files: the list of files to extract from the jar file. + out_dir: the name of the directories to extract to. + + Returns: + the name of extracted input file. + """ + jar_file = zipfile.ZipFile(jar_file) + + out_dir = os.path.join(out_dir, os.path.dirname(input_file)) + if not os.path.exists(out_dir): + os.makedirs(out_dir) + extracted_file_name = os.path.join(out_dir, os.path.basename(input_file)) + with open(extracted_file_name, 'w') as outfile: + outfile.write(jar_file.read(input_file)) + + return extracted_file_name + + +def GenerateJNIHeader(input_file, output_file, namespace): + try: + if os.path.splitext(input_file)[1] == '.class': + jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, namespace) + content = jni_from_javap.GetContent() + else: + jni_from_java_source = JNIFromJavaSource.CreateFromFile(input_file) + content = jni_from_java_source.GetContent() + except ParseError, e: + print e + sys.exit(1) + if output_file: + if not os.path.exists(os.path.dirname(os.path.abspath(output_file))): + os.makedirs(os.path.dirname(os.path.abspath(output_file))) + with file(output_file, 'w') as f: + f.write(content) + else: + print output + + +def main(argv): + usage = """usage: %prog [OPTIONS] +This script will parse the given java source code extracting the native +declarations and print the header file to stdout (or a file). +See SampleForTests.java for more details. + """ + option_parser = optparse.OptionParser(usage=usage) + option_parser.add_option('-j', dest='jar_file', + help='Extract the list of input files from' + ' a specified jar file.' + ' Uses javap to extract the methods from a' + ' pre-compiled class. --input should point' + ' to pre-compiled Java .class files.') + option_parser.add_option('-n', dest='namespace', + help='Uses as a namespace in the generated header,' + ' instead of the javap class name.') + option_parser.add_option('--input_file', + help='Single input file name. The output file name ' + 'will be derived from it. Must be used with ' + '--output_dir.') + option_parser.add_option('--output_dir', + help='The output directory. Must be used with ' + '--input') + options, args = option_parser.parse_args(argv) + if options.jar_file: + input_file = ExtractJarInputFile(options.jar_file, options.input_file, + options.output_dir) + else: + input_file = options.input_file + output_file = None + if options.output_dir: + root_name = os.path.splitext(os.path.basename(input_file))[0] + output_file = os.path.join(options.output_dir, root_name) + '_jni.h' + GenerateJNIHeader(input_file, output_file, options.namespace) + + +if __name__ == '__main__': + sys.exit(main(sys.argv))
diff --git a/src/base/android/jni_generator/jni_generator_tests.py b/src/base/android/jni_generator/jni_generator_tests.py new file mode 100755 index 0000000..5863977 --- /dev/null +++ b/src/base/android/jni_generator/jni_generator_tests.py
@@ -0,0 +1,1590 @@ +#!/usr/bin/env python +# 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. + +"""Tests for jni_generator.py. + +This test suite contains various tests for the JNI generator. +It exercises the low-level parser all the way up to the +code generator and ensures the output matches a golden +file. +""" + +import difflib +import os +import sys +import unittest +import jni_generator +from jni_generator import CalledByNative, NativeMethod, Param + + +class TestGenerator(unittest.TestCase): + def assertObjEquals(self, first, second): + dict_first = first.__dict__ + dict_second = second.__dict__ + self.assertEquals(dict_first.keys(), dict_second.keys()) + for key, value in dict_first.iteritems(): + if (type(value) is list and len(value) and + isinstance(type(value[0]), object)): + self.assertListEquals(value, second.__getattribute__(key)) + else: + actual = second.__getattribute__(key) + self.assertEquals(value, actual, + 'Key ' + key + ': ' + str(value) + '!=' + str(actual)) + + def assertListEquals(self, first, second): + self.assertEquals(len(first), len(second)) + for i in xrange(len(first)): + if isinstance(first[i], object): + self.assertObjEquals(first[i], second[i]) + else: + self.assertEquals(first[i], second[i]) + + def assertTextEquals(self, golden_text, generated_text): + stripped_golden = [l.strip() for l in golden_text.split('\n')] + stripped_generated = [l.strip() for l in generated_text.split('\n')] + if stripped_golden != stripped_generated: + print self.id() + for line in difflib.context_diff(stripped_golden, stripped_generated): + print line + print '\n\nGenerated' + print '=' * 80 + print generated_text + print '=' * 80 + self.fail('Golden text mismatch') + + def testNatives(self): + test_data = """" + interface OnFrameAvailableListener {} + private native int nativeInit(); + private native void nativeDestroy(int nativeChromeBrowserProvider); + private native long nativeAddBookmark( + int nativeChromeBrowserProvider, + String url, String title, boolean isFolder, long parentId); + private static native String nativeGetDomainAndRegistry(String url); + private static native void nativeCreateHistoricalTabFromState( + byte[] state, int tab_index); + private native byte[] nativeGetStateAsByteArray(View view); + private static native String[] nativeGetAutofillProfileGUIDs(); + private native void nativeSetRecognitionResults( + int sessionId, String[] results); + private native long nativeAddBookmarkFromAPI( + int nativeChromeBrowserProvider, + String url, Long created, Boolean isBookmark, + Long date, byte[] favicon, String title, Integer visits); + native int nativeFindAll(String find); + private static native OnFrameAvailableListener nativeGetInnerClass(); + private native Bitmap nativeQueryBitmap( + int nativeChromeBrowserProvider, + String[] projection, String selection, + String[] selectionArgs, String sortOrder); + private native void nativeGotOrientation( + int nativeDataFetcherImplAndroid, + double alpha, double beta, double gamma); + """ + jni_generator.JniParams.ExtractImportsAndInnerClasses(test_data) + natives = jni_generator.ExtractNatives(test_data) + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', + params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='void', static=False, name='Destroy', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='long', static=False, name='AddBookmark', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider'), + Param(datatype='String', + name='url'), + Param(datatype='String', + name='title'), + Param(datatype='boolean', + name='isFolder'), + Param(datatype='long', + name='parentId')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='String', static=True, + name='GetDomainAndRegistry', + params=[Param(datatype='String', + name='url')], + java_class_name=None, + type='function'), + NativeMethod(return_type='void', static=True, + name='CreateHistoricalTabFromState', + params=[Param(datatype='byte[]', + name='state'), + Param(datatype='int', + name='tab_index')], + java_class_name=None, + type='function'), + NativeMethod(return_type='byte[]', static=False, + name='GetStateAsByteArray', + params=[Param(datatype='View', name='view')], + java_class_name=None, + type='function'), + NativeMethod(return_type='String[]', static=True, + name='GetAutofillProfileGUIDs', params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='void', static=False, + name='SetRecognitionResults', + params=[Param(datatype='int', name='sessionId'), + Param(datatype='String[]', name='results')], + java_class_name=None, + type='function'), + NativeMethod(return_type='long', static=False, + name='AddBookmarkFromAPI', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider'), + Param(datatype='String', + name='url'), + Param(datatype='Long', + name='created'), + Param(datatype='Boolean', + name='isBookmark'), + Param(datatype='Long', + name='date'), + Param(datatype='byte[]', + name='favicon'), + Param(datatype='String', + name='title'), + Param(datatype='Integer', + name='visits')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='int', static=False, + name='FindAll', + params=[Param(datatype='String', + name='find')], + java_class_name=None, + type='function'), + NativeMethod(return_type='OnFrameAvailableListener', static=True, + name='GetInnerClass', + params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='Bitmap', + static=False, + name='QueryBitmap', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider'), + Param(datatype='String[]', + name='projection'), + Param(datatype='String', + name='selection'), + Param(datatype='String[]', + name='selectionArgs'), + Param(datatype='String', + name='sortOrder'), + ], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='void', static=False, + name='GotOrientation', + params=[Param(datatype='int', + name='nativeDataFetcherImplAndroid'), + Param(datatype='double', + name='alpha'), + Param(datatype='double', + name='beta'), + Param(datatype='double', + name='gamma'), + ], + java_class_name=None, + type='method', + p0_type='content::DataFetcherImplAndroid'), + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, []) + golden_content = """\ +// 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator_tests.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +const char kTestJniClassPath[] = "org/chromium/TestJni"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_TestJni_clazz = NULL; +} // namespace + +static jint Init(JNIEnv* env, jobject obj); + +static jstring GetDomainAndRegistry(JNIEnv* env, jclass clazz, + jstring url); + +static void CreateHistoricalTabFromState(JNIEnv* env, jclass clazz, + jbyteArray state, + jint tab_index); + +static jbyteArray GetStateAsByteArray(JNIEnv* env, jobject obj, + jobject view); + +static jobjectArray GetAutofillProfileGUIDs(JNIEnv* env, jclass clazz); + +static void SetRecognitionResults(JNIEnv* env, jobject obj, + jint sessionId, + jobjectArray results); + +static jint FindAll(JNIEnv* env, jobject obj, + jstring find); + +static jobject GetInnerClass(JNIEnv* env, jclass clazz); + +// Step 2: method stubs. +static void Destroy(JNIEnv* env, jobject obj, + jint nativeChromeBrowserProvider) { + DCHECK(nativeChromeBrowserProvider) << "Destroy"; + ChromeBrowserProvider* native = + reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider); + return native->Destroy(env, obj); +} + +static jlong AddBookmark(JNIEnv* env, jobject obj, + jint nativeChromeBrowserProvider, + jstring url, + jstring title, + jboolean isFolder, + jlong parentId) { + DCHECK(nativeChromeBrowserProvider) << "AddBookmark"; + ChromeBrowserProvider* native = + reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider); + return native->AddBookmark(env, obj, url, title, isFolder, parentId); +} + +static jlong AddBookmarkFromAPI(JNIEnv* env, jobject obj, + jint nativeChromeBrowserProvider, + jstring url, + jobject created, + jobject isBookmark, + jobject date, + jbyteArray favicon, + jstring title, + jobject visits) { + DCHECK(nativeChromeBrowserProvider) << "AddBookmarkFromAPI"; + ChromeBrowserProvider* native = + reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider); + return native->AddBookmarkFromAPI(env, obj, url, created, isBookmark, date, + favicon, title, visits); +} + +static jobject QueryBitmap(JNIEnv* env, jobject obj, + jint nativeChromeBrowserProvider, + jobjectArray projection, + jstring selection, + jobjectArray selectionArgs, + jstring sortOrder) { + DCHECK(nativeChromeBrowserProvider) << "QueryBitmap"; + ChromeBrowserProvider* native = + reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider); + return native->QueryBitmap(env, obj, projection, selection, selectionArgs, + sortOrder).Release(); +} + +static void GotOrientation(JNIEnv* env, jobject obj, + jint nativeDataFetcherImplAndroid, + jdouble alpha, + jdouble beta, + jdouble gamma) { + DCHECK(nativeDataFetcherImplAndroid) << "GotOrientation"; + DataFetcherImplAndroid* native = + reinterpret_cast<DataFetcherImplAndroid*>(nativeDataFetcherImplAndroid); + return native->GotOrientation(env, obj, alpha, beta, gamma); +} + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_TestJni_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetUnscopedClass(env, kTestJniClassPath))); + static const JNINativeMethod kMethodsTestJni[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast<void*>(Init) }, + { "nativeDestroy", +"(" +"I" +")" +"V", reinterpret_cast<void*>(Destroy) }, + { "nativeAddBookmark", +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Z" +"J" +")" +"J", reinterpret_cast<void*>(AddBookmark) }, + { "nativeGetDomainAndRegistry", +"(" +"Ljava/lang/String;" +")" +"Ljava/lang/String;", reinterpret_cast<void*>(GetDomainAndRegistry) }, + { "nativeCreateHistoricalTabFromState", +"(" +"[B" +"I" +")" +"V", reinterpret_cast<void*>(CreateHistoricalTabFromState) }, + { "nativeGetStateAsByteArray", +"(" +"Landroid/view/View;" +")" +"[B", reinterpret_cast<void*>(GetStateAsByteArray) }, + { "nativeGetAutofillProfileGUIDs", +"(" +")" +"[Ljava/lang/String;", reinterpret_cast<void*>(GetAutofillProfileGUIDs) }, + { "nativeSetRecognitionResults", +"(" +"I" +"[Ljava/lang/String;" +")" +"V", reinterpret_cast<void*>(SetRecognitionResults) }, + { "nativeAddBookmarkFromAPI", +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/Long;" +"Ljava/lang/Boolean;" +"Ljava/lang/Long;" +"[B" +"Ljava/lang/String;" +"Ljava/lang/Integer;" +")" +"J", reinterpret_cast<void*>(AddBookmarkFromAPI) }, + { "nativeFindAll", +"(" +"Ljava/lang/String;" +")" +"I", reinterpret_cast<void*>(FindAll) }, + { "nativeGetInnerClass", +"(" +")" +"Lorg/chromium/example/jni_generator/SampleForTests$OnFrameAvailableListener;", + reinterpret_cast<void*>(GetInnerClass) }, + { "nativeQueryBitmap", +"(" +"I" +"[Ljava/lang/String;" +"Ljava/lang/String;" +"[Ljava/lang/String;" +"Ljava/lang/String;" +")" +"Landroid/graphics/Bitmap;", reinterpret_cast<void*>(QueryBitmap) }, + { "nativeGotOrientation", +"(" +"I" +"D" +"D" +"D" +")" +"V", reinterpret_cast<void*>(GotOrientation) }, + }; + const int kMethodsTestJniSize = arraysize(kMethodsTestJni); + + if (env->RegisterNatives(g_TestJni_clazz, + kMethodsTestJni, + kMethodsTestJniSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI +""" + self.assertTextEquals(golden_content, h.GetContent()) + + def testInnerClassNatives(self): + test_data = """ + class MyInnerClass { + @NativeCall("MyInnerClass") + private native int nativeInit(); + } + """ + natives = jni_generator.ExtractNatives(test_data) + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyInnerClass', + type='function') + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, []) + golden_content = """\ +// 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator_tests.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +const char kTestJniClassPath[] = "org/chromium/TestJni"; +const char kMyInnerClassClassPath[] = "org/chromium/TestJni$MyInnerClass"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_TestJni_clazz = NULL; +} // namespace + +static jint Init(JNIEnv* env, jobject obj); + +// Step 2: method stubs. + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_TestJni_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetUnscopedClass(env, kTestJniClassPath))); + static const JNINativeMethod kMethodsMyInnerClass[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast<void*>(Init) }, + }; + const int kMethodsMyInnerClassSize = arraysize(kMethodsMyInnerClass); + + if (env->RegisterNatives(g_MyInnerClass_clazz, + kMethodsMyInnerClass, + kMethodsMyInnerClassSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI +""" + self.assertTextEquals(golden_content, h.GetContent()) + + def testInnerClassNativesMultiple(self): + test_data = """ + class MyInnerClass { + @NativeCall("MyInnerClass") + private native int nativeInit(); + } + class MyOtherInnerClass { + @NativeCall("MyOtherInnerClass") + private native int nativeInit(); + } + """ + natives = jni_generator.ExtractNatives(test_data) + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyInnerClass', + type='function'), + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyOtherInnerClass', + type='function') + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, []) + golden_content = """\ +// 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator_tests.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +const char kMyOtherInnerClassClassPath[] = + "org/chromium/TestJni$MyOtherInnerClass"; +const char kTestJniClassPath[] = "org/chromium/TestJni"; +const char kMyInnerClassClassPath[] = "org/chromium/TestJni$MyInnerClass"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_TestJni_clazz = NULL; +} // namespace + +static jint Init(JNIEnv* env, jobject obj); + +static jint Init(JNIEnv* env, jobject obj); + +// Step 2: method stubs. + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_TestJni_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetUnscopedClass(env, kTestJniClassPath))); + static const JNINativeMethod kMethodsMyOtherInnerClass[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast<void*>(Init) }, + }; + const int kMethodsMyOtherInnerClassSize = + arraysize(kMethodsMyOtherInnerClass); + + if (env->RegisterNatives(g_MyOtherInnerClass_clazz, + kMethodsMyOtherInnerClass, + kMethodsMyOtherInnerClassSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + static const JNINativeMethod kMethodsMyInnerClass[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast<void*>(Init) }, + }; + const int kMethodsMyInnerClassSize = arraysize(kMethodsMyInnerClass); + + if (env->RegisterNatives(g_MyInnerClass_clazz, + kMethodsMyInnerClass, + kMethodsMyInnerClassSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI +""" + self.assertTextEquals(golden_content, h.GetContent()) + + def testInnerClassNativesBothInnerAndOuter(self): + test_data = """ + class MyOuterClass { + private native int nativeInit(); + class MyOtherInnerClass { + @NativeCall("MyOtherInnerClass") + private native int nativeInit(); + } + } + """ + natives = jni_generator.ExtractNatives(test_data) + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyOtherInnerClass', + type='function') + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, []) + golden_content = """\ +// 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator_tests.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +const char kMyOtherInnerClassClassPath[] = + "org/chromium/TestJni$MyOtherInnerClass"; +const char kTestJniClassPath[] = "org/chromium/TestJni"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_TestJni_clazz = NULL; +} // namespace + +static jint Init(JNIEnv* env, jobject obj); + +static jint Init(JNIEnv* env, jobject obj); + +// Step 2: method stubs. + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_TestJni_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetUnscopedClass(env, kTestJniClassPath))); + static const JNINativeMethod kMethodsMyOtherInnerClass[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast<void*>(Init) }, + }; + const int kMethodsMyOtherInnerClassSize = + arraysize(kMethodsMyOtherInnerClass); + + if (env->RegisterNatives(g_MyOtherInnerClass_clazz, + kMethodsMyOtherInnerClass, + kMethodsMyOtherInnerClassSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + static const JNINativeMethod kMethodsTestJni[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast<void*>(Init) }, + }; + const int kMethodsTestJniSize = arraysize(kMethodsTestJni); + + if (env->RegisterNatives(g_TestJni_clazz, + kMethodsTestJni, + kMethodsTestJniSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI +""" + self.assertTextEquals(golden_content, h.GetContent()) + + def testCalledByNatives(self): + test_data = """" + import android.graphics.Bitmap; + import android.view.View; + import java.io.InputStream; + + class InnerClass {} + + @CalledByNative + InnerClass showConfirmInfoBar(int nativeInfoBar, + String buttonOk, String buttonCancel, String title, Bitmap icon) { + InfoBar infobar = new ConfirmInfoBar(nativeInfoBar, mContext, + buttonOk, buttonCancel, + title, icon); + return infobar; + } + @CalledByNative + InnerClass showAutoLoginInfoBar(int nativeInfoBar, + String realm, String account, String args) { + AutoLoginInfoBar infobar = new AutoLoginInfoBar(nativeInfoBar, mContext, + realm, account, args); + if (infobar.displayedAccountCount() == 0) + infobar = null; + return infobar; + } + @CalledByNative("InfoBar") + void dismiss(); + @SuppressWarnings("unused") + @CalledByNative + private static boolean shouldShowAutoLogin(View view, + String realm, String account, String args) { + AccountManagerContainer accountManagerContainer = + new AccountManagerContainer((Activity)contentView.getContext(), + realm, account, args); + String[] logins = accountManagerContainer.getAccountLogins(null); + return logins.length != 0; + } + @CalledByNative + static InputStream openUrl(String url) { + return null; + } + @CalledByNative + private void activateHardwareAcceleration(final boolean activated, + final int iPid, final int iType, + final int iPrimaryID, final int iSecondaryID) { + if (!activated) { + return + } + } + @CalledByNativeUnchecked + private void uncheckedCall(int iParam); + """ + jni_generator.JniParams.SetFullyQualifiedClass('org/chromium/Foo') + jni_generator.JniParams.ExtractImportsAndInnerClasses(test_data) + called_by_natives = jni_generator.ExtractCalledByNatives(test_data) + golden_called_by_natives = [ + CalledByNative( + return_type='InnerClass', + system_class=False, + static=False, + name='showConfirmInfoBar', + method_id_var_name='showConfirmInfoBar', + java_class_name='', + params=[Param(datatype='int', name='nativeInfoBar'), + Param(datatype='String', name='buttonOk'), + Param(datatype='String', name='buttonCancel'), + Param(datatype='String', name='title'), + Param(datatype='Bitmap', name='icon')], + env_call=('Object', ''), + unchecked=False, + ), + CalledByNative( + return_type='InnerClass', + system_class=False, + static=False, + name='showAutoLoginInfoBar', + method_id_var_name='showAutoLoginInfoBar', + java_class_name='', + params=[Param(datatype='int', name='nativeInfoBar'), + Param(datatype='String', name='realm'), + Param(datatype='String', name='account'), + Param(datatype='String', name='args')], + env_call=('Object', ''), + unchecked=False, + ), + CalledByNative( + return_type='void', + system_class=False, + static=False, + name='dismiss', + method_id_var_name='dismiss', + java_class_name='InfoBar', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='boolean', + system_class=False, + static=True, + name='shouldShowAutoLogin', + method_id_var_name='shouldShowAutoLogin', + java_class_name='', + params=[Param(datatype='View', name='view'), + Param(datatype='String', name='realm'), + Param(datatype='String', name='account'), + Param(datatype='String', name='args')], + env_call=('Boolean', ''), + unchecked=False, + ), + CalledByNative( + return_type='InputStream', + system_class=False, + static=True, + name='openUrl', + method_id_var_name='openUrl', + java_class_name='', + params=[Param(datatype='String', name='url')], + env_call=('Object', ''), + unchecked=False, + ), + CalledByNative( + return_type='void', + system_class=False, + static=False, + name='activateHardwareAcceleration', + method_id_var_name='activateHardwareAcceleration', + java_class_name='', + params=[Param(datatype='boolean', name='activated'), + Param(datatype='int', name='iPid'), + Param(datatype='int', name='iType'), + Param(datatype='int', name='iPrimaryID'), + Param(datatype='int', name='iSecondaryID'), + ], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='void', + system_class=False, + static=False, + name='uncheckedCall', + method_id_var_name='uncheckedCall', + java_class_name='', + params=[Param(datatype='int', name='iParam')], + env_call=('Void', ''), + unchecked=True, + ), + ] + self.assertListEquals(golden_called_by_natives, called_by_natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + [], called_by_natives) + golden_content = """\ +// 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator_tests.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +const char kTestJniClassPath[] = "org/chromium/TestJni"; +const char kInfoBarClassPath[] = "org/chromium/TestJni$InfoBar"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_TestJni_clazz = NULL; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_InfoBar_clazz = NULL; +} // namespace + +// Step 2: method stubs. + +static base::subtle::AtomicWord g_TestJni_showConfirmInfoBar = 0; +static ScopedJavaLocalRef<jobject> Java_TestJni_showConfirmInfoBar(JNIEnv* env, + jobject obj, jint nativeInfoBar, + jstring buttonOk, + jstring buttonCancel, + jstring title, + jobject icon) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "showConfirmInfoBar", + +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Landroid/graphics/Bitmap;" +")" +"Lorg/chromium/Foo$InnerClass;", + &g_TestJni_showConfirmInfoBar); + + jobject ret = + env->CallObjectMethod(obj, + method_id, nativeInfoBar, buttonOk, buttonCancel, title, icon); + base::android::CheckException(env); + return ScopedJavaLocalRef<jobject>(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_showAutoLoginInfoBar = 0; +static ScopedJavaLocalRef<jobject> Java_TestJni_showAutoLoginInfoBar(JNIEnv* + env, jobject obj, jint nativeInfoBar, + jstring realm, + jstring account, + jstring args) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "showAutoLoginInfoBar", + +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Ljava/lang/String;" +")" +"Lorg/chromium/Foo$InnerClass;", + &g_TestJni_showAutoLoginInfoBar); + + jobject ret = + env->CallObjectMethod(obj, + method_id, nativeInfoBar, realm, account, args); + base::android::CheckException(env); + return ScopedJavaLocalRef<jobject>(env, ret); +} + +static base::subtle::AtomicWord g_InfoBar_dismiss = 0; +static void Java_InfoBar_dismiss(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InfoBar_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InfoBar_clazz, + "dismiss", + +"(" +")" +"V", + &g_InfoBar_dismiss); + + env->CallVoidMethod(obj, + method_id); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_TestJni_shouldShowAutoLogin = 0; +static jboolean Java_TestJni_shouldShowAutoLogin(JNIEnv* env, jobject view, + jstring realm, + jstring account, + jstring args) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, g_TestJni_clazz, + "shouldShowAutoLogin", + +"(" +"Landroid/view/View;" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Ljava/lang/String;" +")" +"Z", + &g_TestJni_shouldShowAutoLogin); + + jboolean ret = + env->CallStaticBooleanMethod(g_TestJni_clazz, + method_id, view, realm, account, args); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_TestJni_openUrl = 0; +static ScopedJavaLocalRef<jobject> Java_TestJni_openUrl(JNIEnv* env, jstring + url) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, g_TestJni_clazz, + "openUrl", + +"(" +"Ljava/lang/String;" +")" +"Ljava/io/InputStream;", + &g_TestJni_openUrl); + + jobject ret = + env->CallStaticObjectMethod(g_TestJni_clazz, + method_id, url); + base::android::CheckException(env); + return ScopedJavaLocalRef<jobject>(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_activateHardwareAcceleration = 0; +static void Java_TestJni_activateHardwareAcceleration(JNIEnv* env, jobject obj, + jboolean activated, + jint iPid, + jint iType, + jint iPrimaryID, + jint iSecondaryID) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "activateHardwareAcceleration", + +"(" +"Z" +"I" +"I" +"I" +"I" +")" +"V", + &g_TestJni_activateHardwareAcceleration); + + env->CallVoidMethod(obj, + method_id, activated, iPid, iType, iPrimaryID, iSecondaryID); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_TestJni_uncheckedCall = 0; +static void Java_TestJni_uncheckedCall(JNIEnv* env, jobject obj, jint iParam) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "uncheckedCall", + +"(" +"I" +")" +"V", + &g_TestJni_uncheckedCall); + + env->CallVoidMethod(obj, + method_id, iParam); + +} + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_TestJni_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetUnscopedClass(env, kTestJniClassPath))); + g_InfoBar_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetUnscopedClass(env, kInfoBarClassPath))); + return true; +} + +#endif // org_chromium_TestJni_JNI +""" + self.assertTextEquals(golden_content, h.GetContent()) + + def testCalledByNativeParseError(self): + try: + jni_generator.ExtractCalledByNatives(""" +@CalledByNative +public static int foo(); // This one is fine + +@CalledByNative +scooby doo +""") + self.fail('Expected a ParseError') + except jni_generator.ParseError, e: + self.assertEquals(('@CalledByNative', 'scooby doo'), e.context_lines) + + def testFullyQualifiedClassName(self): + contents = """ +// Copyright (c) 2010 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. + +package org.chromium.content.browser; + +import org.chromium.base.BuildInfo; +""" + self.assertEquals('org/chromium/content/browser/Foo', + jni_generator.ExtractFullyQualifiedJavaClassName( + 'org/chromium/content/browser/Foo.java', contents)) + self.assertEquals('org/chromium/content/browser/Foo', + jni_generator.ExtractFullyQualifiedJavaClassName( + 'frameworks/Foo.java', contents)) + self.assertRaises(SyntaxError, + jni_generator.ExtractFullyQualifiedJavaClassName, + 'com/foo/Bar', 'no PACKAGE line') + + def testMethodNameMangling(self): + self.assertEquals('closeV', + jni_generator.GetMangledMethodName('close', [], 'void')) + self.assertEquals('readI_AB_I_I', + jni_generator.GetMangledMethodName('read', + [Param(name='p1', + datatype='byte[]'), + Param(name='p2', + datatype='int'), + Param(name='p3', + datatype='int'),], + 'int')) + self.assertEquals('openJIIS_JLS', + jni_generator.GetMangledMethodName('open', + [Param(name='p1', + datatype='java/lang/String'),], + 'java/io/InputStream')) + + def testFromJavaP(self): + contents = """ +public abstract class java.io.InputStream extends java.lang.Object + implements java.io.Closeable{ + public java.io.InputStream(); + public int available() throws java.io.IOException; + public void close() throws java.io.IOException; + public void mark(int); + public boolean markSupported(); + public abstract int read() throws java.io.IOException; + public int read(byte[]) throws java.io.IOException; + public int read(byte[], int, int) throws java.io.IOException; + public synchronized void reset() throws java.io.IOException; + public long skip(long) throws java.io.IOException; +} +""" + jni_from_javap = jni_generator.JNIFromJavaP(contents.split('\n'), None) + self.assertEquals(10, len(jni_from_javap.called_by_natives)) + golden_content = """\ +// 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator_tests.py +// For +// java/io/InputStream + +#ifndef java_io_InputStream_JNI +#define java_io_InputStream_JNI + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +const char kInputStreamClassPath[] = "java/io/InputStream"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_InputStream_clazz = NULL; +} // namespace + +namespace JNI_InputStream { + +// Step 2: method stubs. + +static base::subtle::AtomicWord g_InputStream_available = 0; +static jint Java_InputStream_available(JNIEnv* env, jobject obj) __attribute__ + ((unused)); +static jint Java_InputStream_available(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "available", + +"(" +")" +"I", + &g_InputStream_available); + + jint ret = + env->CallIntMethod(obj, + method_id); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_close = 0; +static void Java_InputStream_close(JNIEnv* env, jobject obj) __attribute__ + ((unused)); +static void Java_InputStream_close(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "close", + +"(" +")" +"V", + &g_InputStream_close); + + env->CallVoidMethod(obj, + method_id); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_InputStream_mark = 0; +static void Java_InputStream_mark(JNIEnv* env, jobject obj, jint p0) + __attribute__ ((unused)); +static void Java_InputStream_mark(JNIEnv* env, jobject obj, jint p0) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "mark", + +"(" +"I" +")" +"V", + &g_InputStream_mark); + + env->CallVoidMethod(obj, + method_id, p0); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_InputStream_markSupported = 0; +static jboolean Java_InputStream_markSupported(JNIEnv* env, jobject obj) + __attribute__ ((unused)); +static jboolean Java_InputStream_markSupported(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "markSupported", + +"(" +")" +"Z", + &g_InputStream_markSupported); + + jboolean ret = + env->CallBooleanMethod(obj, + method_id); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_readI = 0; +static jint Java_InputStream_readI(JNIEnv* env, jobject obj) __attribute__ + ((unused)); +static jint Java_InputStream_readI(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "read", + +"(" +")" +"I", + &g_InputStream_readI); + + jint ret = + env->CallIntMethod(obj, + method_id); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_readI_AB = 0; +static jint Java_InputStream_readI_AB(JNIEnv* env, jobject obj, jbyteArray p0) + __attribute__ ((unused)); +static jint Java_InputStream_readI_AB(JNIEnv* env, jobject obj, jbyteArray p0) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "read", + +"(" +"[B" +")" +"I", + &g_InputStream_readI_AB); + + jint ret = + env->CallIntMethod(obj, + method_id, p0); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_readI_AB_I_I = 0; +static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, jobject obj, jbyteArray + p0, + jint p1, + jint p2) __attribute__ ((unused)); +static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, jobject obj, jbyteArray + p0, + jint p1, + jint p2) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "read", + +"(" +"[B" +"I" +"I" +")" +"I", + &g_InputStream_readI_AB_I_I); + + jint ret = + env->CallIntMethod(obj, + method_id, p0, p1, p2); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_reset = 0; +static void Java_InputStream_reset(JNIEnv* env, jobject obj) __attribute__ + ((unused)); +static void Java_InputStream_reset(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "reset", + +"(" +")" +"V", + &g_InputStream_reset); + + env->CallVoidMethod(obj, + method_id); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_InputStream_skip = 0; +static jlong Java_InputStream_skip(JNIEnv* env, jobject obj, jlong p0) + __attribute__ ((unused)); +static jlong Java_InputStream_skip(JNIEnv* env, jobject obj, jlong p0) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "skip", + +"(" +"J" +")" +"J", + &g_InputStream_skip); + + jlong ret = + env->CallLongMethod(obj, + method_id, p0); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_Constructor = 0; +static ScopedJavaLocalRef<jobject> Java_InputStream_Constructor(JNIEnv* env) + __attribute__ ((unused)); +static ScopedJavaLocalRef<jobject> Java_InputStream_Constructor(JNIEnv* env) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "<init>", + +"(" +")" +"V", + &g_InputStream_Constructor); + + jobject ret = + env->NewObject(g_InputStream_clazz, + method_id); + base::android::CheckException(env); + return ScopedJavaLocalRef<jobject>(env, ret); +} + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_InputStream_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetUnscopedClass(env, kInputStreamClassPath))); + return true; +} +} // namespace JNI_InputStream + +#endif // java_io_InputStream_JNI +""" + self.assertTextEquals(golden_content, jni_from_javap.GetContent()) + + def testREForNatives(self): + # We should not match "native SyncSetupFlow" inside the comment. + test_data = """ + /** + * Invoked when the setup process is complete so we can disconnect from the + * native-side SyncSetupFlowHandler. + */ + public void destroy() { + Log.v(TAG, "Destroying native SyncSetupFlow"); + if (mNativeSyncSetupFlow != 0) { + nativeSyncSetupEnded(mNativeSyncSetupFlow); + mNativeSyncSetupFlow = 0; + } + } + private native void nativeSyncSetupEnded( + int nativeAndroidSyncSetupFlowHandler); + """ + jni_from_java = jni_generator.JNIFromJavaSource(test_data, 'foo/bar') + + def testRaisesOnNonJNIMethod(self): + test_data = """ + class MyInnerClass { + private int Foo(int p0) { + } + } + """ + self.assertRaises(SyntaxError, + jni_generator.JNIFromJavaSource, + test_data, 'foo/bar') + + def testJniSelfDocumentingExample(self): + script_dir = os.path.dirname(sys.argv[0]) + content = file(os.path.join(script_dir, 'SampleForTests.java')).read() + golden_content = file(os.path.join(script_dir, + 'golden_sample_for_tests_jni.h')).read() + jni_from_java = jni_generator.JNIFromJavaSource( + content, 'org/chromium/example/jni_generator/SampleForTests') + self.assertTextEquals(golden_content, jni_from_java.GetContent()) + + def testNoWrappingPreprocessorLines(self): + test_data = """ + package com.google.lookhowextremelylongiam.snarf.icankeepthisupallday; + + class ReallyLongClassNamesAreAllTheRage { + private static native int nativeTest(); + } + """ + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, ('com/google/lookhowextremelylongiam/snarf/' + 'icankeepthisupallday/ReallyLongClassNamesAreAllTheRage')) + jni_lines = jni_from_java.GetContent().split('\n') + line = filter(lambda line: line.lstrip().startswith('#ifndef'), + jni_lines)[0] + self.assertTrue(len(line) > 80, + ('Expected #ifndef line to be > 80 chars: ', line)) + + def testImports(self): + import_header = """ +// 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. + +package org.chromium.content.app; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.graphics.SurfaceTexture; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; +import android.view.Surface; + +import java.util.ArrayList; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; +import org.chromium.content.app.ContentMain; +import org.chromium.content.browser.SandboxedProcessConnection; +import org.chromium.content.common.ISandboxedProcessCallback; +import org.chromium.content.common.ISandboxedProcessService; +import org.chromium.content.common.SurfaceCallback; +import org.chromium.content.common.WillNotRaise.AnException; +import org.chromium.content.common.WillRaise.AnException; + +import static org.chromium.Bar.Zoo; + +class Foo { + public static class BookmarkNode implements Parcelable { + } + public interface PasswordListObserver { + } +} + """ + jni_generator.JniParams.SetFullyQualifiedClass( + 'org/chromium/content/app/Foo') + jni_generator.JniParams.ExtractImportsAndInnerClasses(import_header) + self.assertTrue('Lorg/chromium/content/common/ISandboxedProcessService' in + jni_generator.JniParams._imports) + self.assertTrue('Lorg/chromium/Bar/Zoo' in + jni_generator.JniParams._imports) + self.assertTrue('Lorg/chromium/content/app/Foo$BookmarkNode' in + jni_generator.JniParams._inner_classes) + self.assertTrue('Lorg/chromium/content/app/Foo$PasswordListObserver' in + jni_generator.JniParams._inner_classes) + self.assertEquals('Lorg/chromium/content/app/ContentMain$Inner', + jni_generator.JniParams.JavaToJni('ContentMain.Inner')) + self.assertRaises(SyntaxError, + jni_generator.JniParams.JavaToJni, + 'AnException') + + +if __name__ == '__main__': + unittest.main()
diff --git a/src/base/android/jni_generator/sample_for_tests.cc b/src/base/android/jni_generator/sample_for_tests.cc new file mode 100644 index 0000000..5b5cfc5 --- /dev/null +++ b/src/base/android/jni_generator/sample_for_tests.cc
@@ -0,0 +1,81 @@ +// 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/android/jni_android.h" +#include "base/android/scoped_java_ref.h" + +// The main purpose of this is to ensure sample_for_tests_jni.h +// compiles and the functions declared in it as expected. + +using base::android::AttachCurrentThread; +using base::android::ScopedJavaLocalRef; + +namespace base { +namespace android { + +class CPPClass { + public: + class InnerClass { + public: + jdouble MethodOtherP0(JNIEnv* env, jobject obj) { + return 0.0; + } + }; + + void Destroy(JNIEnv* env, jobject obj) { + delete this; + } + + jint Method(JNIEnv* env, jobject obj) { + return 0; + } + + ScopedJavaLocalRef<jstring> InnerMethod(JNIEnv* env, jobject obj) { + return ScopedJavaLocalRef<jstring>(); + } +}; + +static jint Init(JNIEnv* env, jobject obj) { + return 0; +} + +static jdouble GetDoubleFunction(JNIEnv*, jobject) { + return 0; +} + +static jfloat GetFloatFunction(JNIEnv*, jclass) { + return 0; +} + +static void SetNonPODDatatype(JNIEnv*, jobject, jobject) {} + +static jobject GetNonPODDatatype(JNIEnv*, jobject) { + return NULL; +} + +static jint InnerFunction(JNIEnv*, jclass) { + return 0; +} + +} // namespace android +} // namespace base + +#include "jni/SampleForTests_jni.h" + +int main() { + // On a regular application, you'd call AttachCurrentThread(). This sample is + // not yet linking with all the libraries. + JNIEnv* env = /* AttachCurrentThread() */ NULL; + + // This is how you call a java static method from C++. + bool foo = base::android::Java_SampleForTests_staticJavaMethod(env); + + // This is how you call a java method from C++. Note that you must have + // obtained the jobject somehow. + jobject my_java_object = NULL; + int bar = base::android::Java_SampleForTests_javaMethod( + env, my_java_object, 1, 2); + + return 0; +}
diff --git a/src/base/android/jni_helper.cc b/src/base/android/jni_helper.cc new file mode 100644 index 0000000..c0833c9 --- /dev/null +++ b/src/base/android/jni_helper.cc
@@ -0,0 +1,60 @@ +// 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/android/jni_helper.h" + +#include "base/android/jni_android.h" +#include "base/logging.h" + +using base::android::AttachCurrentThread; + +JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef() + : obj_(NULL) { +} + +JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef( + const JavaObjectWeakGlobalRef& orig) { + Assign(orig); +} + +JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef(JNIEnv* env, jobject obj) + : obj_(env->NewWeakGlobalRef(obj)) { + DCHECK(obj_); +} + +JavaObjectWeakGlobalRef::~JavaObjectWeakGlobalRef() { + reset(); +} + +void JavaObjectWeakGlobalRef::operator=(const JavaObjectWeakGlobalRef& rhs) { + Assign(rhs); +} + +void JavaObjectWeakGlobalRef::reset() { + if (obj_) { + AttachCurrentThread()->DeleteWeakGlobalRef(obj_); + obj_ = NULL; + } +} + +base::android::ScopedJavaLocalRef<jobject> + JavaObjectWeakGlobalRef::get(JNIEnv* env) const { + return GetRealObject(env, obj_); +} + +base::android::ScopedJavaLocalRef<jobject> GetRealObject( + JNIEnv* env, jweak obj) { + jobject real = NULL; + if (obj) { + real = env->NewLocalRef(obj); + if (!real) + DLOG(ERROR) << "The real object has been deleted!"; + } + return base::android::ScopedJavaLocalRef<jobject>(env, real); +} + +void JavaObjectWeakGlobalRef::Assign(const JavaObjectWeakGlobalRef& other) { + JNIEnv* env = AttachCurrentThread(); + obj_ = env->NewWeakGlobalRef(other.obj_); +}
diff --git a/src/base/android/jni_helper.h b/src/base/android/jni_helper.h new file mode 100644 index 0000000..895bf95 --- /dev/null +++ b/src/base/android/jni_helper.h
@@ -0,0 +1,41 @@ +// 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_ANDROID_JNI_HELPER_H_ +#define BASE_ANDROID_JNI_HELPER_H_ + +#include <jni.h> + +#include "base/base_export.h" +#include "base/android/scoped_java_ref.h" + +// Manages WeakGlobalRef lifecycle. +// This class is not thread-safe w.r.t. get() and reset(). Multiple threads may +// safely use get() concurrently, but if the user calls reset() (or of course, +// calls the destructor) they'll need to provide their own synchronization. +class BASE_EXPORT JavaObjectWeakGlobalRef { + public: + JavaObjectWeakGlobalRef(); + JavaObjectWeakGlobalRef(const JavaObjectWeakGlobalRef& orig); + JavaObjectWeakGlobalRef(JNIEnv* env, jobject obj); + virtual ~JavaObjectWeakGlobalRef(); + + void operator=(const JavaObjectWeakGlobalRef& rhs); + + base::android::ScopedJavaLocalRef<jobject> get(JNIEnv* env) const; + + void reset(); + + private: + void Assign(const JavaObjectWeakGlobalRef& rhs); + + jweak obj_; +}; + +// Get the real object stored in the weak reference returned as a +// ScopedJavaLocalRef. +BASE_EXPORT base::android::ScopedJavaLocalRef<jobject> GetRealObject( + JNIEnv* env, jweak obj); + +#endif // BASE_ANDROID_JNI_HELPER_H_
diff --git a/src/base/android/jni_registrar.cc b/src/base/android/jni_registrar.cc new file mode 100644 index 0000000..696924a --- /dev/null +++ b/src/base/android/jni_registrar.cc
@@ -0,0 +1,28 @@ +// 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/android/jni_registrar.h" + +#include "base/logging.h" +#include "base/android/jni_android.h" + +namespace base { +namespace android { + +bool RegisterNativeMethods(JNIEnv* env, + const RegistrationMethod* method, + size_t count) { + const RegistrationMethod* end = method + count; + while (method != end) { + if (!method->func(env) < 0) { + DLOG(ERROR) << method->name << " failed registration!"; + return false; + } + method++; + } + return true; +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/jni_registrar.h b/src/base/android/jni_registrar.h new file mode 100644 index 0000000..849d07f --- /dev/null +++ b/src/base/android/jni_registrar.h
@@ -0,0 +1,27 @@ +// 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_ANDROID_JNI_REGISTRAR_H_ +#define BASE_ANDROID_JNI_REGISTRAR_H_ + +#include <jni.h> +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace android { + +struct RegistrationMethod; + +// Registers the JNI bindings for the specified |method| definition containing +// |count| elements. Returns whether the registration of the given methods +// succeeded. +BASE_EXPORT bool RegisterNativeMethods(JNIEnv* env, + const RegistrationMethod* method, + size_t count); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_REGISTRAR_H_
diff --git a/src/base/android/jni_string.cc b/src/base/android/jni_string.cc new file mode 100644 index 0000000..ac3975f --- /dev/null +++ b/src/base/android/jni_string.cc
@@ -0,0 +1,99 @@ +// 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/android/jni_string.h" + +#include "base/android/jni_android.h" +#include "base/logging.h" +#include "base/utf_string_conversions.h" + +namespace { + +// Internal version that does not use a scoped local pointer. +jstring ConvertUTF16ToJavaStringImpl(JNIEnv* env, + const base::StringPiece16& str) { + jstring result = env->NewString(str.data(), str.length()); + base::android::CheckException(env); + return result; +} + +} + +namespace base { +namespace android { + +void ConvertJavaStringToUTF8(JNIEnv* env, jstring str, std::string* result) { + if (!str) { + LOG(WARNING) << "ConvertJavaStringToUTF8 called with null string."; + result->clear(); + return; + } + // JNI's GetStringUTFChars() returns strings in Java "modified" UTF8, so + // instead get the String in UTF16 and convert using chromium's conversion + // function that yields plain (non Java-modified) UTF8. + const jchar* chars = env->GetStringChars(str, NULL); + DCHECK(chars); + UTF16ToUTF8(chars, env->GetStringLength(str), result); + env->ReleaseStringChars(str, chars); + CheckException(env); +} + +std::string ConvertJavaStringToUTF8(JNIEnv* env, jstring str) { + std::string result; + ConvertJavaStringToUTF8(env, str, &result); + return result; +} + +std::string ConvertJavaStringToUTF8(const JavaRef<jstring>& str) { + return ConvertJavaStringToUTF8(AttachCurrentThread(), str.obj()); +} + +ScopedJavaLocalRef<jstring> ConvertUTF8ToJavaString( + JNIEnv* env, + const base::StringPiece& str) { + // JNI's NewStringUTF expects "modified" UTF8 so instead create the string + // via our own UTF16 conversion utility. + // Further, Dalvik requires the string passed into NewStringUTF() to come from + // a trusted source. We can't guarantee that all UTF8 will be sanitized before + // it gets here, so constructing via UTF16 side-steps this issue. + // (Dalvik stores strings internally as UTF16 anyway, so there shouldn't be + // a significant performance hit by doing it this way). + return ScopedJavaLocalRef<jstring>(env, ConvertUTF16ToJavaStringImpl( + env, UTF8ToUTF16(str))); +} + +void ConvertJavaStringToUTF16(JNIEnv* env, jstring str, string16* result) { + if (!str) { + LOG(WARNING) << "ConvertJavaStringToUTF16 called with null string."; + result->clear(); + return; + } + const jchar* chars = env->GetStringChars(str, NULL); + DCHECK(chars); + // GetStringChars isn't required to NULL-terminate the strings + // it returns, so the length must be explicitly checked. + result->assign(chars, env->GetStringLength(str)); + env->ReleaseStringChars(str, chars); + CheckException(env); +} + +string16 ConvertJavaStringToUTF16(JNIEnv* env, jstring str) { + string16 result; + ConvertJavaStringToUTF16(env, str, &result); + return result; +} + +string16 ConvertJavaStringToUTF16(const JavaRef<jstring>& str) { + return ConvertJavaStringToUTF16(AttachCurrentThread(), str.obj()); +} + +ScopedJavaLocalRef<jstring> ConvertUTF16ToJavaString( + JNIEnv* env, + const base::StringPiece16& str) { + return ScopedJavaLocalRef<jstring>(env, + ConvertUTF16ToJavaStringImpl(env, str)); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/jni_string.h b/src/base/android/jni_string.h new file mode 100644 index 0000000..222b78d --- /dev/null +++ b/src/base/android/jni_string.h
@@ -0,0 +1,45 @@ +// 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_ANDROID_JNI_STRING_H_ +#define BASE_ANDROID_JNI_STRING_H_ + +#include <jni.h> +#include <string> + +#include "base/android/scoped_java_ref.h" +#include "base/base_export.h" +#include "base/string_piece.h" + +namespace base { +namespace android { + +// Convert a Java string to UTF8. Returns a std string. +BASE_EXPORT void ConvertJavaStringToUTF8(JNIEnv* env, + jstring str, + std::string* result); +BASE_EXPORT std::string ConvertJavaStringToUTF8(JNIEnv* env, jstring str); +BASE_EXPORT std::string ConvertJavaStringToUTF8(const JavaRef<jstring>& str); + +// Convert a std string to Java string. +BASE_EXPORT ScopedJavaLocalRef<jstring> ConvertUTF8ToJavaString( + JNIEnv* env, + const base::StringPiece& str); + +// Convert a Java string to UTF16. Returns a string16. +BASE_EXPORT void ConvertJavaStringToUTF16(JNIEnv* env, + jstring str, + string16* result); +BASE_EXPORT string16 ConvertJavaStringToUTF16(JNIEnv* env, jstring str); +BASE_EXPORT string16 ConvertJavaStringToUTF16(const JavaRef<jstring>& str); + +// Convert a string16 to a Java string. +BASE_EXPORT ScopedJavaLocalRef<jstring> ConvertUTF16ToJavaString( + JNIEnv* env, + const base::StringPiece16& str); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_STRING_H_
diff --git a/src/base/android/jni_string_unittest.cc b/src/base/android/jni_string_unittest.cc new file mode 100644 index 0000000..59a847e --- /dev/null +++ b/src/base/android/jni_string_unittest.cc
@@ -0,0 +1,32 @@ +// 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/android/jni_string.h" + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +TEST(JniString, BasicConversionsUTF8) { + const std::string kSimpleString = "SimpleTest8"; + JNIEnv* env = AttachCurrentThread(); + std::string result = + ConvertJavaStringToUTF8(ConvertUTF8ToJavaString(env, kSimpleString)); + EXPECT_EQ(kSimpleString, result); +} + +TEST(JniString, BasicConversionsUTF16) { + const string16 kSimpleString = UTF8ToUTF16("SimpleTest16"); + JNIEnv* env = AttachCurrentThread(); + string16 result = + ConvertJavaStringToUTF16(ConvertUTF16ToJavaString(env, kSimpleString)); + EXPECT_EQ(kSimpleString, result); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/locale_utils.cc b/src/base/android/locale_utils.cc new file mode 100644 index 0000000..60b8f84 --- /dev/null +++ b/src/base/android/locale_utils.cc
@@ -0,0 +1,95 @@ +// 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/android/locale_utils.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "jni/LocaleUtils_jni.h" +#include "unicode/uloc.h" + +namespace base { +namespace android { + +std::string GetDefaultLocale() { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> locale = Java_LocaleUtils_getDefaultLocale(env); + return ConvertJavaStringToUTF8(locale); +} + +namespace { + +// Common prototype of ICU uloc_getXXX() functions. +typedef int32_t (*UlocGetComponentFunc)(const char*, char*, int32_t, + UErrorCode*); + +std::string GetLocaleComponent(const std::string& locale, + UlocGetComponentFunc uloc_func, + int32_t max_capacity) { + std::string result; + UErrorCode error = U_ZERO_ERROR; + int32_t actual_length = uloc_func(locale.c_str(), + WriteInto(&result, max_capacity), + max_capacity, + &error); + DCHECK(U_SUCCESS(error)); + DCHECK(actual_length < max_capacity); + result.resize(actual_length); + return result; +} + +ScopedJavaLocalRef<jobject> NewJavaLocale( + JNIEnv* env, + ScopedJavaLocalRef<jclass> locale_class, + jmethodID constructor_id, + const std::string& locale) { + // TODO(wangxianzhu): Use new Locale API once Android supports scripts. + std::string language = GetLocaleComponent( + locale, uloc_getLanguage, ULOC_LANG_CAPACITY); + std::string country = GetLocaleComponent( + locale, uloc_getCountry, ULOC_COUNTRY_CAPACITY); + std::string variant = GetLocaleComponent( + locale, uloc_getVariant, ULOC_FULLNAME_CAPACITY); + return ScopedJavaLocalRef<jobject>( + env, env->NewObject( + locale_class.obj(), constructor_id, + ConvertUTF8ToJavaString(env, language).obj(), + ConvertUTF8ToJavaString(env, country).obj(), + ConvertUTF8ToJavaString(env, variant).obj())); +} + +} // namespace + +string16 GetDisplayNameForLocale(const std::string& locale, + const std::string& display_locale) { + JNIEnv* env = AttachCurrentThread(); + + ScopedJavaLocalRef<jclass> locale_class = GetClass(env, "java/util/Locale"); + jmethodID constructor_id = MethodID::Get<MethodID::TYPE_INSTANCE>( + env, locale_class.obj(), "<init>", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + ScopedJavaLocalRef<jobject> java_locale = NewJavaLocale( + env, locale_class, constructor_id, locale); + ScopedJavaLocalRef<jobject> java_display_locale = NewJavaLocale( + env, locale_class, constructor_id, display_locale); + + jmethodID method_id = MethodID::Get<MethodID::TYPE_INSTANCE>( + env, locale_class.obj(), "getDisplayName", + "(Ljava/util/Locale;)Ljava/lang/String;"); + ScopedJavaLocalRef<jstring> java_result( + env, + static_cast<jstring>(env->CallObjectMethod(java_locale.obj(), method_id, + java_display_locale.obj()))); + return ConvertJavaStringToUTF16(java_result); +} + +bool RegisterLocaleUtils(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/locale_utils.h b/src/base/android/locale_utils.h new file mode 100644 index 0000000..cef39f4 --- /dev/null +++ b/src/base/android/locale_utils.h
@@ -0,0 +1,29 @@ +// 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_ANDROID_LOCALE_UTILS_H_ +#define BASE_ANDROID_LOCALE_UTILS_H_ + +#include <jni.h> + +#include <string> + +#include "base/base_export.h" +#include "base/string16.h" + +namespace base { +namespace android { + +// Return the current default locale of the device. +BASE_EXPORT std::string GetDefaultLocale(); + +BASE_EXPORT string16 GetDisplayNameForLocale(const std::string& locale, + const std::string& display_locale); + +bool RegisterLocaleUtils(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_LOCALE_UTILS_H_
diff --git a/src/base/android/path_service_android.cc b/src/base/android/path_service_android.cc new file mode 100644 index 0000000..a9bf92d --- /dev/null +++ b/src/base/android/path_service_android.cc
@@ -0,0 +1,26 @@ +// 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/android/path_service_android.h" + +#include "base/file_path.h" +#include "base/path_service.h" +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "jni/PathService_jni.h" + +namespace base { +namespace android { + +void Override(JNIEnv* env, jclass clazz, jint what, jstring path) { + FilePath file_path(ConvertJavaStringToUTF8(env, path)); + PathService::Override(what, file_path); +} + +bool RegisterPathService(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/path_service_android.h b/src/base/android/path_service_android.h new file mode 100644 index 0000000..26040f9 --- /dev/null +++ b/src/base/android/path_service_android.h
@@ -0,0 +1,18 @@ +// 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_ANDROID_PATH_SERVICE_ANDROID_H_ +#define BASE_ANDROID_PATH_SERVICE_ANDROID_H_ + +#include <jni.h> + +namespace base { +namespace android { + +bool RegisterPathService(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_PATH_SERVICE_ANDROID_H_
diff --git a/src/base/android/path_utils.cc b/src/base/android/path_utils.cc new file mode 100644 index 0000000..3d86177 --- /dev/null +++ b/src/base/android/path_utils.cc
@@ -0,0 +1,67 @@ +// 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/android/path_utils.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/file_path.h" + +#include "jni/PathUtils_jni.h" + +namespace base { +namespace android { + +bool GetDataDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = + Java_PathUtils_getDataDirectory(env, GetApplicationContext()); + FilePath data_path(ConvertJavaStringToUTF8(path)); + *result = data_path; + return true; +} + +bool GetCacheDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = + Java_PathUtils_getCacheDirectory(env, GetApplicationContext()); + FilePath cache_path(ConvertJavaStringToUTF8(path)); + *result = cache_path; + return true; +} + +bool GetDownloadsDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = + Java_PathUtils_getDownloadsDirectory(env, GetApplicationContext()); + FilePath downloads_path(ConvertJavaStringToUTF8(path)); + *result = downloads_path; + return true; +} + +bool GetNativeLibraryDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = + Java_PathUtils_getNativeLibraryDirectory(env, GetApplicationContext()); + FilePath library_path(ConvertJavaStringToUTF8(path)); + *result = library_path; + return true; +} + +bool GetExternalStorageDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = + Java_PathUtils_getExternalStorageDirectory(env); + FilePath storage_path(ConvertJavaStringToUTF8(path)); + *result = storage_path; + return true; +} + +bool RegisterPathUtils(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/path_utils.h b/src/base/android/path_utils.h new file mode 100644 index 0000000..78f0267 --- /dev/null +++ b/src/base/android/path_utils.h
@@ -0,0 +1,47 @@ +// 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_ANDROID_PATH_UTILS_H_ +#define BASE_ANDROID_PATH_UTILS_H_ + +#include <jni.h> + +#include "base/base_export.h" + +class FilePath; + +namespace base { +namespace android { + +// Retrieves the absolute path to the data directory of the current +// application. The result is placed in the FilePath pointed to by 'result'. +// This method is dedicated for base_paths_android.c, Using +// PathService::Get(base::DIR_ANDROID_APP_DATA, ...) gets the data dir. +BASE_EXPORT bool GetDataDirectory(FilePath* result); + +// Retrieves the absolute path to the cache directory. The result is placed in +// the FilePath pointed to by 'result'. This method is dedicated for +// base_paths_android.c, Using PathService::Get(base::DIR_CACHE, ...) gets the +// cache dir. +BASE_EXPORT bool GetCacheDirectory(FilePath* result); + +// Retrieves the path to the public downloads directory. The result is placed +// in the FilePath pointed to by 'result'. +bool GetDownloadsDirectory(FilePath* result); + +// Retrieves the path to the native JNI libraries via +// ApplicationInfo.nativeLibraryDir on the Java side. The result is placed in +// the FilePath pointed to by 'result'. +BASE_EXPORT bool GetNativeLibraryDirectory(FilePath* result); + +// Retrieves the absolute path to the external storage directory. The result +// is placed in the FilePath pointed to by 'result'. +BASE_EXPORT bool GetExternalStorageDirectory(FilePath* result); + +bool RegisterPathUtils(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_PATH_UTILS_H_
diff --git a/src/base/android/path_utils_unittest.cc b/src/base/android/path_utils_unittest.cc new file mode 100644 index 0000000..636e3fa --- /dev/null +++ b/src/base/android/path_utils_unittest.cc
@@ -0,0 +1,46 @@ +// 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/android/path_utils.h" +#include "base/file_path.h" +#include "base/file_util.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +typedef testing::Test PathUtilsTest; + +TEST_F(PathUtilsTest, TestGetDataDirectory) { + // The string comes from the Java side and depends on the APK + // we are running in. Assumes that we are packaged in + // org.chromium.native_test + FilePath path; + GetDataDirectory(&path); + EXPECT_STREQ("/data/data/org.chromium.native_test/app_chrome", + path.value().c_str()); +} + +TEST_F(PathUtilsTest, TestGetCacheDirectory) { + // The string comes from the Java side and depends on the APK + // we are running in. Assumes that we are packaged in + // org.chromium.native_test + FilePath path; + GetCacheDirectory(&path); + EXPECT_STREQ("/data/data/org.chromium.native_test/cache", + path.value().c_str()); +} + +TEST_F(PathUtilsTest, TestGetNativeLibraryDirectory) { + // The string comes from the Java side and depends on the APK + // we are running in. Assumes that the directory contains + // the base tests shared object. + FilePath path; + GetNativeLibraryDirectory(&path); + EXPECT_TRUE(file_util::PathExists(path.Append(("libbase_unittests.so")))); +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/scoped_java_ref.cc b/src/base/android/scoped_java_ref.cc new file mode 100644 index 0000000..21b466e --- /dev/null +++ b/src/base/android/scoped_java_ref.cc
@@ -0,0 +1,73 @@ +// 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/android/scoped_java_ref.h" + +#include "base/android/jni_android.h" +#include "base/logging.h" + +namespace base { +namespace android { + +JavaRef<jobject>::JavaRef() : obj_(NULL) {} + +JavaRef<jobject>::JavaRef(JNIEnv* env, jobject obj) : obj_(obj) { + if (obj) { + DCHECK(env && env->GetObjectRefType(obj) == JNILocalRefType); + } +} + +JavaRef<jobject>::~JavaRef() { +} + +JNIEnv* JavaRef<jobject>::SetNewLocalRef(JNIEnv* env, jobject obj) { + if (!env) { + env = AttachCurrentThread(); + } else { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + } + if (obj) + obj = env->NewLocalRef(obj); + if (obj_) + env->DeleteLocalRef(obj_); + obj_ = obj; + return env; +} + +void JavaRef<jobject>::SetNewGlobalRef(JNIEnv* env, jobject obj) { + if (!env) { + env = AttachCurrentThread(); + } else { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + } + if (obj) + obj = env->NewGlobalRef(obj); + if (obj_) + env->DeleteGlobalRef(obj_); + obj_ = obj; +} + +void JavaRef<jobject>::ResetLocalRef(JNIEnv* env) { + if (obj_) { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + env->DeleteLocalRef(obj_); + obj_ = NULL; + } +} + +void JavaRef<jobject>::ResetGlobalRef() { + if (obj_) { + AttachCurrentThread()->DeleteGlobalRef(obj_); + obj_ = NULL; + } +} + +jobject JavaRef<jobject>::ReleaseInternal() { + jobject obj = obj_; + obj_ = NULL; + return obj; +} + +} // namespace android +} // namespace base
diff --git a/src/base/android/scoped_java_ref.h b/src/base/android/scoped_java_ref.h new file mode 100644 index 0000000..a5d71e2 --- /dev/null +++ b/src/base/android/scoped_java_ref.h
@@ -0,0 +1,198 @@ +// 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_ANDROID_SCOPED_JAVA_REF_H_ +#define BASE_ANDROID_SCOPED_JAVA_REF_H_ + +#include <jni.h> +#include <stddef.h> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace android { + +// Forward declare the generic java reference template class. +template<typename T> class JavaRef; + +// Template specialization of JavaRef, which acts as the base class for all +// other JavaRef<> template types. This allows you to e.g. pass +// ScopedJavaLocalRef<jstring> into a function taking const JavaRef<jobject>& +template<> +class BASE_EXPORT JavaRef<jobject> { + public: + jobject obj() const { return obj_; } + + bool is_null() const { return obj_ == NULL; } + + protected: + // Initializes a NULL reference. + JavaRef(); + + // Takes ownership of the |obj| reference passed; requires it to be a local + // reference type. + JavaRef(JNIEnv* env, jobject obj); + + ~JavaRef(); + + // The following are implementation detail convenience methods, for + // use by the sub-classes. + JNIEnv* SetNewLocalRef(JNIEnv* env, jobject obj); + void SetNewGlobalRef(JNIEnv* env, jobject obj); + void ResetLocalRef(JNIEnv* env); + void ResetGlobalRef(); + jobject ReleaseInternal(); + + private: + jobject obj_; + + DISALLOW_COPY_AND_ASSIGN(JavaRef); +}; + +// Generic base class for ScopedJavaLocalRef and ScopedJavaGlobalRef. Useful +// for allowing functions to accept a reference without having to mandate +// whether it is a local or global type. +template<typename T> +class JavaRef : public JavaRef<jobject> { + public: + T obj() const { return static_cast<T>(JavaRef<jobject>::obj()); } + + protected: + JavaRef() {} + ~JavaRef() {} + + JavaRef(JNIEnv* env, T obj) : JavaRef<jobject>(env, obj) {} + + private: + DISALLOW_COPY_AND_ASSIGN(JavaRef); +}; + +// Holds a local reference to a Java object. The local reference is scoped +// to the lifetime of this object. +// Instances of this class may hold onto any JNIEnv passed into it until +// destroyed. Therefore, since a JNIEnv is only suitable for use on a single +// thread, objects of this class must be created, used, and destroyed, on a +// single thread. +// Therefore, this class should only be used as a stack-based object and from a +// single thread. If you wish to have the reference outlive the current +// callstack (e.g. as a class member) or you wish to pass it across threads, +// use a ScopedJavaGlobalRef instead. +template<typename T> +class ScopedJavaLocalRef : public JavaRef<T> { + public: + ScopedJavaLocalRef() : env_(NULL) {} + + // Non-explicit copy constructor, to allow ScopedJavaLocalRef to be returned + // by value as this is the normal usage pattern. + ScopedJavaLocalRef(const ScopedJavaLocalRef<T>& other) + : env_(other.env_) { + this->SetNewLocalRef(env_, other.obj()); + } + + template<typename U> + explicit ScopedJavaLocalRef(const U& other) + : env_(NULL) { + this->Reset(other); + } + + // Assumes that |obj| is a local reference to a Java object and takes + // ownership of this local reference. + ScopedJavaLocalRef(JNIEnv* env, T obj) : JavaRef<T>(env, obj), env_(env) {} + + ~ScopedJavaLocalRef() { + this->Reset(); + } + + // Overloaded assignment operator defined for consistency with the implicit + // copy constructor. + void operator=(const ScopedJavaLocalRef<T>& other) { + this->Reset(other); + } + + void Reset() { + this->ResetLocalRef(env_); + } + + template<typename U> + void Reset(const ScopedJavaLocalRef<U>& other) { + // We can copy over env_ here as |other| instance must be from the same + // thread as |this| local ref. (See class comment for multi-threading + // limitations, and alternatives). + this->Reset(other.env_, other.obj()); + } + + template<typename U> + void Reset(const U& other) { + // If |env_| was not yet set (is still NULL) it will be attached to the + // current thread in SetNewLocalRef(). + this->Reset(env_, other.obj()); + } + + template<typename U> + void Reset(JNIEnv* env, U obj) { + implicit_cast<T>(obj); // Ensure U is assignable to T + env_ = this->SetNewLocalRef(env, obj); + } + + // Releases the local reference to the caller. The caller *must* delete the + // local reference when it is done with it. + T Release() { + return static_cast<T>(this->ReleaseInternal()); + } + + private: + // This class is only good for use on the thread it was created on so + // it's safe to cache the non-threadsafe JNIEnv* inside this object. + JNIEnv* env_; +}; + +// Holds a global reference to a Java object. The global reference is scoped +// to the lifetime of this object. This class does not hold onto any JNIEnv* +// passed to it, hence it is safe to use across threads (within the constraints +// imposed by the underlying Java object that it references). +template<typename T> +class ScopedJavaGlobalRef : public JavaRef<T> { + public: + ScopedJavaGlobalRef() {} + + explicit ScopedJavaGlobalRef(const ScopedJavaGlobalRef<T>& other) { + this->Reset(other); + } + + template<typename U> + explicit ScopedJavaGlobalRef(const U& other) { + this->Reset(other); + } + + ~ScopedJavaGlobalRef() { + this->Reset(); + } + + void Reset() { + this->ResetGlobalRef(); + } + + template<typename U> + void Reset(const U& other) { + this->Reset(NULL, other.obj()); + } + + template<typename U> + void Reset(JNIEnv* env, U obj) { + implicit_cast<T>(obj); // Ensure U is assignable to T + this->SetNewGlobalRef(env, obj); + } + + // Releases the global reference to the caller. The caller *must* delete the + // global reference when it is done with it. + T Release() { + return static_cast<T>(this->ReleaseInternal()); + } +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_SCOPED_JAVA_REF_H_
diff --git a/src/base/android/scoped_java_ref_unittest.cc b/src/base/android/scoped_java_ref_unittest.cc new file mode 100644 index 0000000..36f253c --- /dev/null +++ b/src/base/android/scoped_java_ref_unittest.cc
@@ -0,0 +1,122 @@ +// 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/android/scoped_java_ref.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +namespace { +int g_local_refs = 0; +int g_global_refs = 0; + +const JNINativeInterface* g_previous_functions; + +jobject NewGlobalRef(JNIEnv* env, jobject obj) { + ++g_global_refs; + return g_previous_functions->NewGlobalRef(env, obj); +} + +void DeleteGlobalRef(JNIEnv* env, jobject obj) { + --g_global_refs; + return g_previous_functions->DeleteGlobalRef(env, obj); +} + +jobject NewLocalRef(JNIEnv* env, jobject obj) { + ++g_local_refs; + return g_previous_functions->NewLocalRef(env, obj); +} + +void DeleteLocalRef(JNIEnv* env, jobject obj) { + --g_local_refs; + return g_previous_functions->DeleteLocalRef(env, obj); +} +} // namespace + +class ScopedJavaRefTest : public testing::Test { + protected: + virtual void SetUp() { + g_local_refs = 0; + g_global_refs = 0; + JNIEnv* env = AttachCurrentThread(); + g_previous_functions = env->functions; + hooked_functions = *g_previous_functions; + env->functions = &hooked_functions; + // We inject our own functions in JNINativeInterface so we can keep track + // of the reference counting ourselves. + hooked_functions.NewGlobalRef = &NewGlobalRef; + hooked_functions.DeleteGlobalRef = &DeleteGlobalRef; + hooked_functions.NewLocalRef = &NewLocalRef; + hooked_functions.DeleteLocalRef = &DeleteLocalRef; + } + + virtual void TearDown() { + JNIEnv* env = AttachCurrentThread(); + env->functions = g_previous_functions; + } + // From JellyBean release, the instance of this struct provided in JNIEnv is + // read-only, so we deep copy it to allow individual functions to be hooked. + JNINativeInterface hooked_functions; +}; + +// The main purpose of this is testing the various conversions compile. +TEST_F(ScopedJavaRefTest, Conversions) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> str = ConvertUTF8ToJavaString(env, "string"); + ScopedJavaGlobalRef<jstring> global(str); + { + ScopedJavaGlobalRef<jobject> global_obj(str); + ScopedJavaLocalRef<jobject> local_obj(global); + const JavaRef<jobject>& obj_ref1(str); + const JavaRef<jobject>& obj_ref2(global); + EXPECT_TRUE(env->IsSameObject(obj_ref1.obj(), obj_ref2.obj())); + EXPECT_TRUE(env->IsSameObject(global_obj.obj(), obj_ref2.obj())); + } + global.Reset(str); + const JavaRef<jstring>& str_ref = str; + EXPECT_EQ("string", ConvertJavaStringToUTF8(str_ref)); + str.Reset(); +} + +TEST_F(ScopedJavaRefTest, RefCounts) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> str; + // The ConvertJavaStringToUTF8 below creates a new string that would normally + // return a local ref. We simulate that by starting the g_local_refs count at + // 1. + g_local_refs = 1; + str.Reset(ConvertUTF8ToJavaString(env, "string")); + EXPECT_EQ(1, g_local_refs); + EXPECT_EQ(0, g_global_refs); + { + ScopedJavaGlobalRef<jstring> global_str(str); + ScopedJavaGlobalRef<jobject> global_obj(global_str); + EXPECT_EQ(1, g_local_refs); + EXPECT_EQ(2, g_global_refs); + + ScopedJavaLocalRef<jstring> str2(env, str.Release()); + EXPECT_EQ(1, g_local_refs); + { + ScopedJavaLocalRef<jstring> str3(str2); + EXPECT_EQ(2, g_local_refs); + } + EXPECT_EQ(1, g_local_refs); + str2.Reset(); + EXPECT_EQ(0, g_local_refs); + global_str.Reset(); + EXPECT_EQ(1, g_global_refs); + ScopedJavaGlobalRef<jobject> global_obj2(global_obj); + EXPECT_EQ(2, g_global_refs); + } + + EXPECT_EQ(0, g_local_refs); + EXPECT_EQ(0, g_global_refs); +} + +} // namespace android +} // namespace base
diff --git a/src/base/at_exit.cc b/src/base/at_exit.cc new file mode 100644 index 0000000..0fba355 --- /dev/null +++ b/src/base/at_exit.cc
@@ -0,0 +1,82 @@ +// 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/at_exit.h" + +#include <stddef.h> +#include <ostream> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" + +namespace base { + +// Keep a stack of registered AtExitManagers. We always operate on the most +// recent, and we should never have more than one outside of testing (for a +// statically linked version of this library). Testing may use the shadow +// version of the constructor, and if we are building a dynamic library we may +// end up with multiple AtExitManagers on the same process. We don't protect +// this for thread-safe access, since it will only be modified in testing. +static AtExitManager* g_top_manager = NULL; + +AtExitManager::AtExitManager() : next_manager_(g_top_manager) { +// If multiple modules instantiate AtExitManagers they'll end up living in this +// module... they have to coexist. +#if !defined(COMPONENT_BUILD) + DCHECK(!g_top_manager); +#endif + g_top_manager = this; +} + +AtExitManager::~AtExitManager() { + if (!g_top_manager) { + NOTREACHED() << "Tried to ~AtExitManager without an AtExitManager"; + return; + } + DCHECK_EQ(this, g_top_manager); + + ProcessCallbacksNow(); + g_top_manager = next_manager_; +} + +// static +void AtExitManager::RegisterCallback(AtExitCallbackType func, void* param) { + DCHECK(func); + RegisterTask(base::Bind(func, param)); +} + +// static +void AtExitManager::RegisterTask(base::Closure task) { + if (!g_top_manager) { + NOTREACHED() << "Tried to RegisterCallback without an AtExitManager"; + return; + } + + AutoLock lock(g_top_manager->lock_); + g_top_manager->stack_.push(task); +} + +// static +void AtExitManager::ProcessCallbacksNow() { + if (!g_top_manager) { + NOTREACHED() << "Tried to ProcessCallbacksNow without an AtExitManager"; + return; + } + + AutoLock lock(g_top_manager->lock_); + + while (!g_top_manager->stack_.empty()) { + base::Closure task = g_top_manager->stack_.top(); + task.Run(); + g_top_manager->stack_.pop(); + } +} + +AtExitManager::AtExitManager(bool shadow) : next_manager_(g_top_manager) { + DCHECK(shadow || !g_top_manager); + g_top_manager = this; +} + +} // namespace base
diff --git a/src/base/at_exit.h b/src/base/at_exit.h new file mode 100644 index 0000000..6b28ae9 --- /dev/null +++ b/src/base/at_exit.h
@@ -0,0 +1,76 @@ +// 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. + +#ifndef BASE_AT_EXIT_H_ +#define BASE_AT_EXIT_H_ + +#include <stack> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/synchronization/lock.h" + +namespace base { + +// This class provides a facility similar to the CRT atexit(), except that +// we control when the callbacks are executed. Under Windows for a DLL they +// happen at a really bad time and under the loader lock. This facility is +// mostly used by base::Singleton. +// +// The usage is simple. Early in the main() or WinMain() scope create an +// AtExitManager object on the stack: +// int main(...) { +// base::AtExitManager exit_manager; +// +// } +// When the exit_manager object goes out of scope, all the registered +// callbacks and singleton destructors will be called. + +class BASE_EXPORT AtExitManager { + public: + typedef void (*AtExitCallbackType)(void*); + + AtExitManager(); + + // The dtor calls all the registered callbacks. Do not try to register more + // callbacks after this point. + ~AtExitManager(); + + // Registers the specified function to be called at exit. The prototype of + // the callback function is void func(void*). + static void RegisterCallback(AtExitCallbackType func, void* param); + + // Registers the specified task to be called at exit. + static void RegisterTask(base::Closure task); + + // Calls the functions registered with RegisterCallback in LIFO order. It + // is possible to register new callbacks after calling this function. + static void ProcessCallbacksNow(); + + protected: + // This constructor will allow this instance of AtExitManager to be created + // even if one already exists. This should only be used for testing! + // AtExitManagers are kept on a global stack, and it will be removed during + // destruction. This allows you to shadow another AtExitManager. + explicit AtExitManager(bool shadow); + + private: + base::Lock lock_; + std::stack<base::Closure> stack_; + AtExitManager* next_manager_; // Stack of managers to allow shadowing. + + DISALLOW_COPY_AND_ASSIGN(AtExitManager); +}; + +#if defined(__LB_SHELL__) || defined(UNIT_TEST) || defined(COBALT) +class ShadowingAtExitManager : public AtExitManager { + public: + ShadowingAtExitManager() : AtExitManager(true) {} +}; +#endif // defined(UNIT_TEST) + +} // namespace base + +#endif // BASE_AT_EXIT_H_
diff --git a/src/base/at_exit_unittest.cc b/src/base/at_exit_unittest.cc new file mode 100644 index 0000000..cda7340 --- /dev/null +++ b/src/base/at_exit_unittest.cc
@@ -0,0 +1,87 @@ +// 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/at_exit.h" +#include "base/bind.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +int g_test_counter_1 = 0; +int g_test_counter_2 = 0; + +void IncrementTestCounter1(void* unused) { + ++g_test_counter_1; +} + +void IncrementTestCounter2(void* unused) { + ++g_test_counter_2; +} + +void ZeroTestCounters() { + g_test_counter_1 = 0; + g_test_counter_2 = 0; +} + +void ExpectCounter1IsZero(void* unused) { + EXPECT_EQ(0, g_test_counter_1); +} + +void ExpectParamIsNull(void* param) { + EXPECT_EQ(static_cast<void*>(NULL), param); +} + +void ExpectParamIsCounter(void* param) { + EXPECT_EQ(&g_test_counter_1, param); +} + +} // namespace + +class AtExitTest : public testing::Test { + private: + // Don't test the global AtExitManager, because asking it to process its + // AtExit callbacks can ruin the global state that other tests may depend on. + base::ShadowingAtExitManager exit_manager_; +}; + +TEST_F(AtExitTest, Basic) { + ZeroTestCounters(); + base::AtExitManager::RegisterCallback(&IncrementTestCounter1, NULL); + base::AtExitManager::RegisterCallback(&IncrementTestCounter2, NULL); + base::AtExitManager::RegisterCallback(&IncrementTestCounter1, NULL); + + EXPECT_EQ(0, g_test_counter_1); + EXPECT_EQ(0, g_test_counter_2); + base::AtExitManager::ProcessCallbacksNow(); + EXPECT_EQ(2, g_test_counter_1); + EXPECT_EQ(1, g_test_counter_2); +} + +TEST_F(AtExitTest, LIFOOrder) { + ZeroTestCounters(); + base::AtExitManager::RegisterCallback(&IncrementTestCounter1, NULL); + base::AtExitManager::RegisterCallback(&ExpectCounter1IsZero, NULL); + base::AtExitManager::RegisterCallback(&IncrementTestCounter2, NULL); + + EXPECT_EQ(0, g_test_counter_1); + EXPECT_EQ(0, g_test_counter_2); + base::AtExitManager::ProcessCallbacksNow(); + EXPECT_EQ(1, g_test_counter_1); + EXPECT_EQ(1, g_test_counter_2); +} + +TEST_F(AtExitTest, Param) { + base::AtExitManager::RegisterCallback(&ExpectParamIsNull, NULL); + base::AtExitManager::RegisterCallback(&ExpectParamIsCounter, + &g_test_counter_1); + base::AtExitManager::ProcessCallbacksNow(); +} + +TEST_F(AtExitTest, Task) { + ZeroTestCounters(); + base::AtExitManager::RegisterTask(base::Bind(&ExpectParamIsCounter, + &g_test_counter_1)); + base::AtExitManager::ProcessCallbacksNow(); +}
diff --git a/src/base/atomic_ref_count.h b/src/base/atomic_ref_count.h new file mode 100644 index 0000000..5130860 --- /dev/null +++ b/src/base/atomic_ref_count.h
@@ -0,0 +1,80 @@ +// 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 is a low level implementation of atomic semantics for reference +// counting. Please use base/memory/ref_counted.h directly instead. +// +// The implementation includes annotations to avoid some false positives +// when using data race detection tools. + +#ifndef BASE_ATOMIC_REF_COUNT_H_ +#define BASE_ATOMIC_REF_COUNT_H_ + +#include "base/atomicops.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" + +namespace base { + +typedef subtle::Atomic32 AtomicRefCount; + +// Increment a reference count by "increment", which must exceed 0. +inline void AtomicRefCountIncN(volatile AtomicRefCount *ptr, + AtomicRefCount increment) { + subtle::NoBarrier_AtomicIncrement(ptr, increment); +} + +// Decrement a reference count by "decrement", which must exceed 0, +// and return whether the result is non-zero. +// Insert barriers to ensure that state written before the reference count +// became zero will be visible to a thread that has just made the count zero. +inline bool AtomicRefCountDecN(volatile AtomicRefCount *ptr, + AtomicRefCount decrement) { + ANNOTATE_HAPPENS_BEFORE(ptr); + bool res = (subtle::Barrier_AtomicIncrement(ptr, -decrement) != 0); + if (!res) { + ANNOTATE_HAPPENS_AFTER(ptr); + } + return res; +} + +// Increment a reference count by 1. +inline void AtomicRefCountInc(volatile AtomicRefCount *ptr) { + base::AtomicRefCountIncN(ptr, 1); +} + +// Decrement a reference count by 1 and return whether the result is non-zero. +// Insert barriers to ensure that state written before the reference count +// became zero will be visible to a thread that has just made the count zero. +inline bool AtomicRefCountDec(volatile AtomicRefCount *ptr) { + return base::AtomicRefCountDecN(ptr, 1); +} + +// Return whether the reference count is one. If the reference count is used +// in the conventional way, a refrerence count of 1 implies that the current +// thread owns the reference and no other thread shares it. This call performs +// the test for a reference count of one, and performs the memory barrier +// needed for the owning thread to act on the object, knowing that it has +// exclusive access to the object. +inline bool AtomicRefCountIsOne(volatile AtomicRefCount *ptr) { + bool res = (subtle::Acquire_Load(ptr) == 1); + if (res) { + ANNOTATE_HAPPENS_AFTER(ptr); + } + return res; +} + +// Return whether the reference count is zero. With conventional object +// referencing counting, the object will be destroyed, so the reference count +// should never be zero. Hence this is generally used for a debug check. +inline bool AtomicRefCountIsZero(volatile AtomicRefCount *ptr) { + bool res = (subtle::Acquire_Load(ptr) == 0); + if (res) { + ANNOTATE_HAPPENS_AFTER(ptr); + } + return res; +} + +} // namespace base + +#endif // BASE_ATOMIC_REF_COUNT_H_
diff --git a/src/base/atomic_sequence_num.h b/src/base/atomic_sequence_num.h new file mode 100644 index 0000000..7bf2778 --- /dev/null +++ b/src/base/atomic_sequence_num.h
@@ -0,0 +1,60 @@ +// 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_ATOMIC_SEQUENCE_NUM_H_ +#define BASE_ATOMIC_SEQUENCE_NUM_H_ + +#include "base/atomicops.h" +#include "base/basictypes.h" + +namespace base { + +class AtomicSequenceNumber; + +// Static (POD) AtomicSequenceNumber that MUST be used in global scope (or +// non-function scope) ONLY. This implementation does not generate any static +// initializer. Note that it does not implement any constructor which means +// that its fields are not initialized except when it is stored in the global +// data section (.data in ELF). If you want to allocate an atomic sequence +// number on the stack (or heap), please use the AtomicSequenceNumber class +// declared below. +class StaticAtomicSequenceNumber { + public: + inline int GetNext() { + return static_cast<int>( + base::subtle::NoBarrier_AtomicIncrement(&seq_, 1) - 1); + } + + private: + friend class AtomicSequenceNumber; + + inline void Reset() { + base::subtle::Release_Store(&seq_, 0); + } + + base::subtle::Atomic32 seq_; +}; + +// AtomicSequenceNumber that can be stored and used safely (i.e. its fields are +// always initialized as opposed to StaticAtomicSequenceNumber declared above). +// Please use StaticAtomicSequenceNumber if you want to declare an atomic +// sequence number in the global scope. +class AtomicSequenceNumber { + public: + AtomicSequenceNumber() { + seq_.Reset(); + } + + inline int GetNext() { + return seq_.GetNext(); + } + + private: + StaticAtomicSequenceNumber seq_; + DISALLOW_COPY_AND_ASSIGN(AtomicSequenceNumber); +}; + +} // namespace base + +#endif // BASE_ATOMIC_SEQUENCE_NUM_H_
diff --git a/src/base/atomicops.h b/src/base/atomicops.h new file mode 100644 index 0000000..230a601 --- /dev/null +++ b/src/base/atomicops.h
@@ -0,0 +1,169 @@ +// 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. + +// For atomic operations on reference counts, see atomic_refcount.h. +// For atomic operations on sequence numbers, see atomic_sequence_num.h. + +// The routines exported by this module are subtle. If you use them, even if +// you get the code right, it will depend on careful reasoning about atomicity +// and memory ordering; it will be less readable, and harder to maintain. If +// you plan to use these routines, you should have a good reason, such as solid +// evidence that performance would otherwise suffer, or there being no +// alternative. You should assume only properties explicitly guaranteed by the +// specifications in this file. You are almost certainly _not_ writing code +// just for the x86; if you assume x86 semantics, x86 hardware bugs and +// implementations on other archtectures will cause your code to break. If you +// do not know what you are doing, avoid these routines, and use a Mutex. +// +// It is incorrect to make direct assignments to/from an atomic variable. +// You should use one of the Load or Store routines. The NoBarrier +// versions are provided when no barriers are needed: +// NoBarrier_Store() +// NoBarrier_Load() +// Although there are currently no compiler enforcement, you are encouraged +// to use these. +// + +#ifndef BASE_ATOMICOPS_H_ +#define BASE_ATOMICOPS_H_ + +#include "base/basictypes.h" +#include "build/build_config.h" + +#if (defined(OS_WIN) && defined(ARCH_CPU_64_BITS)) || defined(__LB_XB360__) || defined(__LB_XB1__) +// windows.h #defines this (only on x64). This causes problems because the +// public API also uses MemoryBarrier at the public name for this fence. So, on +// X64, undef it, and call its documented +// (http://msdn.microsoft.com/en-us/library/windows/desktop/ms684208.aspx) +// implementation directly. +#undef MemoryBarrier +#endif + +namespace base { +namespace subtle { + +typedef int32 Atomic32; +#ifdef ARCH_CPU_64_BITS +// We need to be able to go between Atomic64 and AtomicWord implicitly. This +// means Atomic64 and AtomicWord should be the same type on 64-bit. +#if defined(OS_NACL) +// NaCl's intptr_t is not actually 64-bits on 64-bit! +// http://code.google.com/p/nativeclient/issues/detail?id=1162 +typedef int64_t Atomic64; +#else +typedef intptr_t Atomic64; +#endif +#endif + +// Use AtomicWord for a machine-sized pointer. It will use the Atomic32 or +// Atomic64 routines below, depending on your architecture. +typedef intptr_t AtomicWord; + +// Atomically execute: +// result = *ptr; +// if (*ptr == old_value) +// *ptr = new_value; +// return result; +// +// I.e., replace "*ptr" with "new_value" if "*ptr" used to be "old_value". +// Always return the old value of "*ptr" +// +// This routine implies no memory barriers. +Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value); + +// Atomically store new_value into *ptr, returning the previous value held in +// *ptr. This routine implies no memory barriers. +Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, Atomic32 new_value); + +// Atomically increment *ptr by "increment". Returns the new value of +// *ptr with the increment applied. This routine implies no memory barriers. +Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, Atomic32 increment); + +Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment); + +// These following lower-level operations are typically useful only to people +// implementing higher-level synchronization operations like spinlocks, +// mutexes, and condition-variables. They combine CompareAndSwap(), a load, or +// a store with appropriate memory-ordering instructions. "Acquire" operations +// ensure that no later memory access can be reordered ahead of the operation. +// "Release" operations ensure that no previous memory access can be reordered +// after the operation. "Barrier" operations have both "Acquire" and "Release" +// semantics. A MemoryBarrier() has "Barrier" semantics, but does no memory +// access. +Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value); +Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value); + +void MemoryBarrier(); +void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value); +void Acquire_Store(volatile Atomic32* ptr, Atomic32 value); +void Release_Store(volatile Atomic32* ptr, Atomic32 value); + +Atomic32 NoBarrier_Load(volatile const Atomic32* ptr); +Atomic32 Acquire_Load(volatile const Atomic32* ptr); +Atomic32 Release_Load(volatile const Atomic32* ptr); + +// 64-bit atomic operations (only available on 64-bit processors). +#ifdef ARCH_CPU_64_BITS +Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value); +Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, Atomic64 new_value); +Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, Atomic64 increment); +Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, Atomic64 increment); + +Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value); +Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value); +void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value); +void Acquire_Store(volatile Atomic64* ptr, Atomic64 value); +void Release_Store(volatile Atomic64* ptr, Atomic64 value); +Atomic64 NoBarrier_Load(volatile const Atomic64* ptr); +Atomic64 Acquire_Load(volatile const Atomic64* ptr); +Atomic64 Release_Load(volatile const Atomic64* ptr); +#endif // ARCH_CPU_64_BITS + +} // namespace base::subtle +} // namespace base + +// Include our platform specific implementation. +#if defined(THREAD_SANITIZER) +#include "base/atomicops_internals_tsan.h" +#elif defined(OS_WIN) && defined(COMPILER_MSVC) && defined(ARCH_CPU_X86_FAMILY) +#include "base/atomicops_internals_x86_msvc.h" +#elif defined(OS_MACOSX) +#include "base/atomicops_internals_mac.h" +#elif defined(OS_STARBOARD) +#include "base/atomicops_internals_starboard.h" +#elif defined(__LB_SHELL__) +#define SHELL_BEGIN_ATOMICOPS_NAMESPACES namespace base { namespace subtle { +#define SHELL_END_ATOMICOPS_NAMESPACES } } +#include "atomicops_internals_shell.h" // from the platform lib +#elif (defined(COMPILER_GCC) && defined(ARCH_CPU_ARM_FAMILY)) || \ + defined(OS_NACL) +#include "base/atomicops_internals_gcc.h" +#elif defined(COMPILER_GCC) && defined(ARCH_CPU_X86_FAMILY) +#include "base/atomicops_internals_x86_gcc.h" +#elif defined(COMPILER_GCC) && defined(ARCH_CPU_MIPS_FAMILY) +#include "base/atomicops_internals_mips_gcc.h" +#else +#error "Atomic operations are not supported on your platform" +#endif + +// On some platforms we need additional declarations to make +// AtomicWord compatible with our other Atomic* types. +#if defined(OS_MACOSX) || defined(OS_OPENBSD) +#include "base/atomicops_internals_atomicword_compat.h" +#endif + +#endif // BASE_ATOMICOPS_H_
diff --git a/src/base/atomicops_internals_atomicword_compat.h b/src/base/atomicops_internals_atomicword_compat.h new file mode 100644 index 0000000..e02d11d --- /dev/null +++ b/src/base/atomicops_internals_atomicword_compat.h
@@ -0,0 +1,100 @@ +// 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 is an internal atomic implementation, use base/atomicops.h instead. + +#ifndef BASE_ATOMICOPS_INTERNALS_ATOMICWORD_COMPAT_H_ +#define BASE_ATOMICOPS_INTERNALS_ATOMICWORD_COMPAT_H_ + +// AtomicWord is a synonym for intptr_t, and Atomic32 is a synonym for int32, +// which in turn means int. On some LP32 platforms, intptr_t is an int, but +// on others, it's a long. When AtomicWord and Atomic32 are based on different +// fundamental types, their pointers are incompatible. +// +// This file defines function overloads to allow both AtomicWord and Atomic32 +// data to be used with this interface. +// +// On LP64 platforms, AtomicWord and Atomic64 are both always long, +// so this problem doesn't occur. + +#if !defined(ARCH_CPU_64_BITS) + +namespace base { +namespace subtle { + +inline AtomicWord NoBarrier_CompareAndSwap(volatile AtomicWord* ptr, + AtomicWord old_value, + AtomicWord new_value) { + return NoBarrier_CompareAndSwap( + reinterpret_cast<volatile Atomic32*>(ptr), old_value, new_value); +} + +inline AtomicWord NoBarrier_AtomicExchange(volatile AtomicWord* ptr, + AtomicWord new_value) { + return NoBarrier_AtomicExchange( + reinterpret_cast<volatile Atomic32*>(ptr), new_value); +} + +inline AtomicWord NoBarrier_AtomicIncrement(volatile AtomicWord* ptr, + AtomicWord increment) { + return NoBarrier_AtomicIncrement( + reinterpret_cast<volatile Atomic32*>(ptr), increment); +} + +inline AtomicWord Barrier_AtomicIncrement(volatile AtomicWord* ptr, + AtomicWord increment) { + return Barrier_AtomicIncrement( + reinterpret_cast<volatile Atomic32*>(ptr), increment); +} + +inline AtomicWord Acquire_CompareAndSwap(volatile AtomicWord* ptr, + AtomicWord old_value, + AtomicWord new_value) { + return base::subtle::Acquire_CompareAndSwap( + reinterpret_cast<volatile Atomic32*>(ptr), old_value, new_value); +} + +inline AtomicWord Release_CompareAndSwap(volatile AtomicWord* ptr, + AtomicWord old_value, + AtomicWord new_value) { + return base::subtle::Release_CompareAndSwap( + reinterpret_cast<volatile Atomic32*>(ptr), old_value, new_value); +} + +inline void NoBarrier_Store(volatile AtomicWord *ptr, AtomicWord value) { + NoBarrier_Store( + reinterpret_cast<volatile Atomic32*>(ptr), value); +} + +inline void Acquire_Store(volatile AtomicWord* ptr, AtomicWord value) { + return base::subtle::Acquire_Store( + reinterpret_cast<volatile Atomic32*>(ptr), value); +} + +inline void Release_Store(volatile AtomicWord* ptr, AtomicWord value) { + return base::subtle::Release_Store( + reinterpret_cast<volatile Atomic32*>(ptr), value); +} + +inline AtomicWord NoBarrier_Load(volatile const AtomicWord *ptr) { + return NoBarrier_Load( + reinterpret_cast<volatile const Atomic32*>(ptr)); +} + +inline AtomicWord Acquire_Load(volatile const AtomicWord* ptr) { + return base::subtle::Acquire_Load( + reinterpret_cast<volatile const Atomic32*>(ptr)); +} + +inline AtomicWord Release_Load(volatile const AtomicWord* ptr) { + return base::subtle::Release_Load( + reinterpret_cast<volatile const Atomic32*>(ptr)); +} + +} // namespace base::subtle +} // namespace base + +#endif // !defined(ARCH_CPU_64_BITS) + +#endif // BASE_ATOMICOPS_INTERNALS_ATOMICWORD_COMPAT_H_
diff --git a/src/base/atomicops_internals_gcc.h b/src/base/atomicops_internals_gcc.h new file mode 100644 index 0000000..ed1b2d7 --- /dev/null +++ b/src/base/atomicops_internals_gcc.h
@@ -0,0 +1,106 @@ +// Copyright (c) 2009 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 is an internal atomic implementation, include base/atomicops.h +// instead. This file is for platforms that use GCC intrinsics rather than +// platform-specific assembly code for atomic operations. + +#ifndef BASE_ATOMICOPS_INTERNALS_GCC_H_ +#define BASE_ATOMICOPS_INTERNALS_GCC_H_ + +namespace base { +namespace subtle { + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev_value; + do { + if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) + return old_value; + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + Atomic32 old_value; + do { + old_value = *ptr; + } while (!__sync_bool_compare_and_swap(ptr, old_value, new_value)); + return old_value; +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return Barrier_AtomicIncrement(ptr, increment); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + for (;;) { + // Atomic exchange the old value with an incremented one. + Atomic32 old_value = *ptr; + Atomic32 new_value = old_value + increment; + if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) { + // The exchange took place as expected. + return new_value; + } + // Otherwise, *ptr changed mid-loop and we need to retry. + } +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + // Since NoBarrier_CompareAndSwap uses __sync_bool_compare_and_swap, which + // is a full memory barrier, none is needed here or below in Release. + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void MemoryBarrier() { + __sync_synchronize(); +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +} // namespace base::subtle +} // namespace base + +#endif // BASE_ATOMICOPS_INTERNALS_GCC_H_ +
diff --git a/src/base/atomicops_internals_mac.h b/src/base/atomicops_internals_mac.h new file mode 100644 index 0000000..658ed54 --- /dev/null +++ b/src/base/atomicops_internals_mac.h
@@ -0,0 +1,197 @@ +// 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. + +// This file is an internal atomic implementation, use base/atomicops.h instead. + +#ifndef BASE_ATOMICOPS_INTERNALS_MAC_H_ +#define BASE_ATOMICOPS_INTERNALS_MAC_H_ + +#include <libkern/OSAtomic.h> + +namespace base { +namespace subtle { + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32 *ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev_value; + do { + if (OSAtomicCompareAndSwap32(old_value, new_value, + const_cast<Atomic32*>(ptr))) { + return old_value; + } + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32 *ptr, + Atomic32 new_value) { + Atomic32 old_value; + do { + old_value = *ptr; + } while (!OSAtomicCompareAndSwap32(old_value, new_value, + const_cast<Atomic32*>(ptr))); + return old_value; +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32 *ptr, + Atomic32 increment) { + return OSAtomicAdd32(increment, const_cast<Atomic32*>(ptr)); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32 *ptr, + Atomic32 increment) { + return OSAtomicAdd32Barrier(increment, const_cast<Atomic32*>(ptr)); +} + +inline void MemoryBarrier() { + OSMemoryBarrier(); +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32 *ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev_value; + do { + if (OSAtomicCompareAndSwap32Barrier(old_value, new_value, + const_cast<Atomic32*>(ptr))) { + return old_value; + } + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32 *ptr, + Atomic32 old_value, + Atomic32 new_value) { + return Acquire_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic32 *ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic32 *ptr, Atomic32 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32 *ptr) { + Atomic32 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32 *ptr) { + MemoryBarrier(); + return *ptr; +} + +#ifdef __LP64__ + +// 64-bit implementation on 64-bit platform + +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64 *ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 prev_value; + do { + if (OSAtomicCompareAndSwap64(old_value, new_value, + reinterpret_cast<volatile int64_t*>(ptr))) { + return old_value; + } + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64 *ptr, + Atomic64 new_value) { + Atomic64 old_value; + do { + old_value = *ptr; + } while (!OSAtomicCompareAndSwap64(old_value, new_value, + reinterpret_cast<volatile int64_t*>(ptr))); + return old_value; +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64 *ptr, + Atomic64 increment) { + return OSAtomicAdd64(increment, reinterpret_cast<volatile int64_t*>(ptr)); +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64 *ptr, + Atomic64 increment) { + return OSAtomicAdd64Barrier(increment, + reinterpret_cast<volatile int64_t*>(ptr)); +} + +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64 *ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 prev_value; + do { + if (OSAtomicCompareAndSwap64Barrier( + old_value, new_value, reinterpret_cast<volatile int64_t*>(ptr))) { + return old_value; + } + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64 *ptr, + Atomic64 old_value, + Atomic64 new_value) { + // The lib kern interface does not distinguish between + // Acquire and Release memory barriers; they are equivalent. + return Acquire_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic64 *ptr, Atomic64 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic64 *ptr, Atomic64 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { + return *ptr; +} + +inline Atomic64 Acquire_Load(volatile const Atomic64 *ptr) { + Atomic64 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic64 Release_Load(volatile const Atomic64 *ptr) { + MemoryBarrier(); + return *ptr; +} + +#endif // defined(__LP64__) + +} // namespace base::subtle +} // namespace base + +#endif // BASE_ATOMICOPS_INTERNALS_MAC_H_
diff --git a/src/base/atomicops_internals_mips_gcc.h b/src/base/atomicops_internals_mips_gcc.h new file mode 100644 index 0000000..505597e --- /dev/null +++ b/src/base/atomicops_internals_mips_gcc.h
@@ -0,0 +1,161 @@ +// 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. + +// This file is an internal atomic implementation, use base/atomicops.h instead. +// +// LinuxKernelCmpxchg and Barrier_AtomicIncrement are from Google Gears. + +#ifndef BASE_ATOMICOPS_INTERNALS_MIPS_GCC_H_ +#define BASE_ATOMICOPS_INTERNALS_MIPS_GCC_H_ + +#define ATOMICOPS_COMPILER_BARRIER() __asm__ __volatile__("" : : : "memory") + +namespace base { +namespace subtle { + +// Atomically execute: +// result = *ptr; +// if (*ptr == old_value) +// *ptr = new_value; +// return result; +// +// I.e., replace "*ptr" with "new_value" if "*ptr" used to be "old_value". +// Always return the old value of "*ptr" +// +// This routine implies no memory barriers. +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev, tmp; + __asm__ __volatile__(".set push\n" + ".set noreorder\n" + "1:\n" + "ll %0, %5\n" // prev = *ptr + "bne %0, %3, 2f\n" // if (prev != old_value) goto 2 + "move %2, %4\n" // tmp = new_value + "sc %2, %1\n" // *ptr = tmp (with atomic check) + "beqz %2, 1b\n" // start again on atomic error + "nop\n" // delay slot nop + "2:\n" + ".set pop\n" + : "=&r" (prev), "=m" (*ptr), "=&r" (tmp) + : "Ir" (old_value), "r" (new_value), "m" (*ptr) + : "memory"); + return prev; +} + +// Atomically store new_value into *ptr, returning the previous value held in +// *ptr. This routine implies no memory barriers. +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + Atomic32 temp, old; + __asm__ __volatile__(".set push\n" + ".set noreorder\n" + "1:\n" + "ll %1, %2\n" // old = *ptr + "move %0, %3\n" // temp = new_value + "sc %0, %2\n" // *ptr = temp (with atomic check) + "beqz %0, 1b\n" // start again on atomic error + "nop\n" // delay slot nop + ".set pop\n" + : "=&r" (temp), "=&r" (old), "=m" (*ptr) + : "r" (new_value), "m" (*ptr) + : "memory"); + + return old; +} + +// Atomically increment *ptr by "increment". Returns the new value of +// *ptr with the increment applied. This routine implies no memory barriers. +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + Atomic32 temp, temp2; + + __asm__ __volatile__(".set push\n" + ".set noreorder\n" + "1:\n" + "ll %0, %2\n" // temp = *ptr + "addu %1, %0, %3\n" // temp2 = temp + increment + "sc %1, %2\n" // *ptr = temp2 (with atomic check) + "beqz %1, 1b\n" // start again on atomic error + "addu %1, %0, %3\n" // temp2 = temp + increment + ".set pop\n" + : "=&r" (temp), "=&r" (temp2), "=m" (*ptr) + : "Ir" (increment), "m" (*ptr) + : "memory"); + // temp2 now holds the final value. + return temp2; +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + ATOMICOPS_COMPILER_BARRIER(); + Atomic32 res = NoBarrier_AtomicIncrement(ptr, increment); + ATOMICOPS_COMPILER_BARRIER(); + return res; +} + +// "Acquire" operations +// ensure that no later memory access can be reordered ahead of the operation. +// "Release" operations ensure that no previous memory access can be reordered +// after the operation. "Barrier" operations have both "Acquire" and "Release" +// semantics. A MemoryBarrier() has "Barrier" semantics, but does no memory +// access. +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + ATOMICOPS_COMPILER_BARRIER(); + Atomic32 res = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + ATOMICOPS_COMPILER_BARRIER(); + return res; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + ATOMICOPS_COMPILER_BARRIER(); + Atomic32 res = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + ATOMICOPS_COMPILER_BARRIER(); + return res; +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void MemoryBarrier() { + __asm__ __volatile__("sync" : : : "memory"); +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +} // namespace base::subtle +} // namespace base + +#undef ATOMICOPS_COMPILER_BARRIER + +#endif // BASE_ATOMICOPS_INTERNALS_MIPS_GCC_H_
diff --git a/src/base/atomicops_internals_starboard.h b/src/base/atomicops_internals_starboard.h new file mode 100644 index 0000000..cb1c759 --- /dev/null +++ b/src/base/atomicops_internals_starboard.h
@@ -0,0 +1,151 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file is an internal atomic implementation, use base/atomicops.h instead. + +// This is the Starboard implementation, which defers all specific +// implementation decisions for atomic operations to the Starboard port. + +#ifndef BASE_ATOMICOPS_INTERNALS_STARBOARD_H_ +#define BASE_ATOMICOPS_INTERNALS_STARBOARD_H_ + +#include "starboard/atomic.h" + +namespace base { +namespace subtle { + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32 *ptr, + Atomic32 old_value, + Atomic32 new_value) { + return SbAtomicNoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32 *ptr, + Atomic32 new_value) { + return SbAtomicNoBarrier_Exchange(ptr, new_value); +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32 *ptr, + Atomic32 increment) { + return SbAtomicNoBarrier_Increment(ptr, increment); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32 *ptr, + Atomic32 increment) { + return SbAtomicBarrier_Increment(ptr, increment); +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32 *ptr, + Atomic32 old_value, + Atomic32 new_value) { + return SbAtomicAcquire_CompareAndSwap(ptr, old_value, new_value); +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32 *ptr, + Atomic32 old_value, + Atomic32 new_value) { + return SbAtomicRelease_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32 *ptr, Atomic32 value) { + SbAtomicNoBarrier_Store(ptr, value); +} + +inline void MemoryBarrier() { + SbAtomicMemoryBarrier(); +} + +inline void Acquire_Store(volatile Atomic32 *ptr, Atomic32 value) { + SbAtomicAcquire_Store(ptr, value); +} + +inline void Release_Store(volatile Atomic32 *ptr, Atomic32 value) { + SbAtomicRelease_Store(ptr, value); +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32 *ptr) { + return SbAtomicNoBarrier_Load(ptr); +} + +inline Atomic32 Acquire_Load(volatile const Atomic32 *ptr) { + return SbAtomicAcquire_Load(ptr); +} + +inline Atomic32 Release_Load(volatile const Atomic32 *ptr) { + return SbAtomicRelease_Load(ptr); +} + +#if SB_IS(64_BIT) +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64 *ptr, + Atomic64 old_value, + Atomic64 new_value) { + return SbAtomicNoBarrier_CompareAndSwap64(ptr, old_value, new_value); +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64 *ptr, + Atomic64 new_value) { + return SbAtomicNoBarrier_Exchange64(ptr, new_value); +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64 *ptr, + Atomic64 increment) { + return SbAtomicNoBarrier_Increment64(ptr, increment); +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64 *ptr, + Atomic64 increment) { + return SbAtomicBarrier_Increment64(ptr, increment); +} + +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64 *ptr, + Atomic64 old_value, + Atomic64 new_value) { + return SbAtomicAcquire_CompareAndSwap64(ptr, old_value, new_value); +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64 *ptr, + Atomic64 old_value, + Atomic64 new_value) { + return SbAtomicRelease_CompareAndSwap64(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic64 *ptr, Atomic64 value) { + SbAtomicNoBarrier_Store64(ptr, value); +} + +inline void Acquire_Store(volatile Atomic64 *ptr, Atomic64 value) { + SbAtomicAcquire_Store64(ptr, value); +} + +inline void Release_Store(volatile Atomic64 *ptr, Atomic64 value) { + SbAtomicRelease_Store64(ptr, value); +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64 *ptr) { + return SbAtomicNoBarrier_Load64(ptr); +} + +inline Atomic64 Acquire_Load(volatile const Atomic64 *ptr) { + return SbAtomicAcquire_Load64(ptr); +} + +inline Atomic64 Release_Load(volatile const Atomic64 *ptr) { + return SbAtomicRelease_Load64(ptr); +} +#endif // SB_IS(64_BIT) + +} // namespace base::subtle +} // namespace base + +#endif // BASE_ATOMICOPS_INTERNALS_STARBOARD_H_
diff --git a/src/base/atomicops_internals_tsan.h b/src/base/atomicops_internals_tsan.h new file mode 100644 index 0000000..44d6400 --- /dev/null +++ b/src/base/atomicops_internals_tsan.h
@@ -0,0 +1,378 @@ +// 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. + +// This file is an internal atomic implementation for compiler-based +// ThreadSanitizer. Use base/atomicops.h instead. + +#ifndef BASE_ATOMICOPS_INTERNALS_TSAN_H_ +#define BASE_ATOMICOPS_INTERNALS_TSAN_H_ + +#include "base/base_export.h" + +// This struct is not part of the public API of this module; clients may not +// use it. (However, it's exported via BASE_EXPORT because clients implicitly +// do use it at link time by inlining these functions.) +// Features of this x86. Values may not be correct before main() is run, +// but are set conservatively. +struct AtomicOps_x86CPUFeatureStruct { + bool has_amd_lock_mb_bug; // Processor has AMD memory-barrier bug; do lfence + // after acquire compare-and-swap. + bool has_sse2; // Processor has SSE2. +}; +BASE_EXPORT extern struct AtomicOps_x86CPUFeatureStruct + AtomicOps_Internalx86CPUFeatures; + +#define ATOMICOPS_COMPILER_BARRIER() __asm__ __volatile__("" : : : "memory") + +namespace base { +namespace subtle { + +#ifndef TSAN_INTERFACE_ATOMIC_H +#define TSAN_INTERFACE_ATOMIC_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef char __tsan_atomic8; +typedef short __tsan_atomic16; // NOLINT +typedef int __tsan_atomic32; +typedef long __tsan_atomic64; // NOLINT + +#if defined(__SIZEOF_INT128__) \ + || (__clang_major__ * 100 + __clang_minor__ >= 302) +typedef __int128 __tsan_atomic128; +#define __TSAN_HAS_INT128 1 +#else +typedef char __tsan_atomic128; +#define __TSAN_HAS_INT128 0 +#endif + +typedef enum { + __tsan_memory_order_relaxed, + __tsan_memory_order_consume, + __tsan_memory_order_acquire, + __tsan_memory_order_release, + __tsan_memory_order_acq_rel, + __tsan_memory_order_seq_cst, +} __tsan_memory_order; + +__tsan_atomic8 __tsan_atomic8_load(const volatile __tsan_atomic8 *a, + __tsan_memory_order mo); +__tsan_atomic16 __tsan_atomic16_load(const volatile __tsan_atomic16 *a, + __tsan_memory_order mo); +__tsan_atomic32 __tsan_atomic32_load(const volatile __tsan_atomic32 *a, + __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_load(const volatile __tsan_atomic64 *a, + __tsan_memory_order mo); +__tsan_atomic128 __tsan_atomic128_load(const volatile __tsan_atomic128 *a, + __tsan_memory_order mo); + +void __tsan_atomic8_store(volatile __tsan_atomic8 *a, __tsan_atomic8 v, + __tsan_memory_order mo); +void __tsan_atomic16_store(volatile __tsan_atomic16 *a, __tsan_atomic16 v, + __tsan_memory_order mo); +void __tsan_atomic32_store(volatile __tsan_atomic32 *a, __tsan_atomic32 v, + __tsan_memory_order mo); +void __tsan_atomic64_store(volatile __tsan_atomic64 *a, __tsan_atomic64 v, + __tsan_memory_order mo); +void __tsan_atomic128_store(volatile __tsan_atomic128 *a, __tsan_atomic128 v, + __tsan_memory_order mo); + +__tsan_atomic8 __tsan_atomic8_exchange(volatile __tsan_atomic8 *a, + __tsan_atomic8 v, __tsan_memory_order mo); +__tsan_atomic16 __tsan_atomic16_exchange(volatile __tsan_atomic16 *a, + __tsan_atomic16 v, __tsan_memory_order mo); +__tsan_atomic32 __tsan_atomic32_exchange(volatile __tsan_atomic32 *a, + __tsan_atomic32 v, __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_exchange(volatile __tsan_atomic64 *a, + __tsan_atomic64 v, __tsan_memory_order mo); +__tsan_atomic128 __tsan_atomic128_exchange(volatile __tsan_atomic128 *a, + __tsan_atomic128 v, __tsan_memory_order mo); + +__tsan_atomic8 __tsan_atomic8_fetch_add(volatile __tsan_atomic8 *a, + __tsan_atomic8 v, __tsan_memory_order mo); +__tsan_atomic16 __tsan_atomic16_fetch_add(volatile __tsan_atomic16 *a, + __tsan_atomic16 v, __tsan_memory_order mo); +__tsan_atomic32 __tsan_atomic32_fetch_add(volatile __tsan_atomic32 *a, + __tsan_atomic32 v, __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_fetch_add(volatile __tsan_atomic64 *a, + __tsan_atomic64 v, __tsan_memory_order mo); +__tsan_atomic128 __tsan_atomic128_fetch_add(volatile __tsan_atomic128 *a, + __tsan_atomic128 v, __tsan_memory_order mo); + +__tsan_atomic8 __tsan_atomic8_fetch_and(volatile __tsan_atomic8 *a, + __tsan_atomic8 v, __tsan_memory_order mo); +__tsan_atomic16 __tsan_atomic16_fetch_and(volatile __tsan_atomic16 *a, + __tsan_atomic16 v, __tsan_memory_order mo); +__tsan_atomic32 __tsan_atomic32_fetch_and(volatile __tsan_atomic32 *a, + __tsan_atomic32 v, __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_fetch_and(volatile __tsan_atomic64 *a, + __tsan_atomic64 v, __tsan_memory_order mo); +__tsan_atomic128 __tsan_atomic128_fetch_and(volatile __tsan_atomic128 *a, + __tsan_atomic128 v, __tsan_memory_order mo); + +__tsan_atomic8 __tsan_atomic8_fetch_or(volatile __tsan_atomic8 *a, + __tsan_atomic8 v, __tsan_memory_order mo); +__tsan_atomic16 __tsan_atomic16_fetch_or(volatile __tsan_atomic16 *a, + __tsan_atomic16 v, __tsan_memory_order mo); +__tsan_atomic32 __tsan_atomic32_fetch_or(volatile __tsan_atomic32 *a, + __tsan_atomic32 v, __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_fetch_or(volatile __tsan_atomic64 *a, + __tsan_atomic64 v, __tsan_memory_order mo); +__tsan_atomic128 __tsan_atomic128_fetch_or(volatile __tsan_atomic128 *a, + __tsan_atomic128 v, __tsan_memory_order mo); + +__tsan_atomic8 __tsan_atomic8_fetch_xor(volatile __tsan_atomic8 *a, + __tsan_atomic8 v, __tsan_memory_order mo); +__tsan_atomic16 __tsan_atomic16_fetch_xor(volatile __tsan_atomic16 *a, + __tsan_atomic16 v, __tsan_memory_order mo); +__tsan_atomic32 __tsan_atomic32_fetch_xor(volatile __tsan_atomic32 *a, + __tsan_atomic32 v, __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_fetch_xor(volatile __tsan_atomic64 *a, + __tsan_atomic64 v, __tsan_memory_order mo); +__tsan_atomic128 __tsan_atomic128_fetch_xor(volatile __tsan_atomic128 *a, + __tsan_atomic128 v, __tsan_memory_order mo); + +__tsan_atomic8 __tsan_atomic8_fetch_nand(volatile __tsan_atomic8 *a, + __tsan_atomic8 v, __tsan_memory_order mo); +__tsan_atomic16 __tsan_atomic16_fetch_nand(volatile __tsan_atomic16 *a, + __tsan_atomic16 v, __tsan_memory_order mo); +__tsan_atomic32 __tsan_atomic32_fetch_nand(volatile __tsan_atomic32 *a, + __tsan_atomic32 v, __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_fetch_nand(volatile __tsan_atomic64 *a, + __tsan_atomic64 v, __tsan_memory_order mo); +__tsan_atomic128 __tsan_atomic128_fetch_nand(volatile __tsan_atomic128 *a, + __tsan_atomic128 v, __tsan_memory_order mo); + +int __tsan_atomic8_compare_exchange_weak(volatile __tsan_atomic8 *a, + __tsan_atomic8 *c, __tsan_atomic8 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo); +int __tsan_atomic16_compare_exchange_weak(volatile __tsan_atomic16 *a, + __tsan_atomic16 *c, __tsan_atomic16 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo); +int __tsan_atomic32_compare_exchange_weak(volatile __tsan_atomic32 *a, + __tsan_atomic32 *c, __tsan_atomic32 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo); +int __tsan_atomic64_compare_exchange_weak(volatile __tsan_atomic64 *a, + __tsan_atomic64 *c, __tsan_atomic64 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo); +int __tsan_atomic128_compare_exchange_weak(volatile __tsan_atomic128 *a, + __tsan_atomic128 *c, __tsan_atomic128 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo); + +int __tsan_atomic8_compare_exchange_strong(volatile __tsan_atomic8 *a, + __tsan_atomic8 *c, __tsan_atomic8 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo); +int __tsan_atomic16_compare_exchange_strong(volatile __tsan_atomic16 *a, + __tsan_atomic16 *c, __tsan_atomic16 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo); +int __tsan_atomic32_compare_exchange_strong(volatile __tsan_atomic32 *a, + __tsan_atomic32 *c, __tsan_atomic32 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo); +int __tsan_atomic64_compare_exchange_strong(volatile __tsan_atomic64 *a, + __tsan_atomic64 *c, __tsan_atomic64 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo); +int __tsan_atomic128_compare_exchange_strong(volatile __tsan_atomic128 *a, + __tsan_atomic128 *c, __tsan_atomic128 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo); + +__tsan_atomic8 __tsan_atomic8_compare_exchange_val( + volatile __tsan_atomic8 *a, __tsan_atomic8 c, __tsan_atomic8 v, + __tsan_memory_order mo, __tsan_memory_order fail_mo); +__tsan_atomic16 __tsan_atomic16_compare_exchange_val( + volatile __tsan_atomic16 *a, __tsan_atomic16 c, __tsan_atomic16 v, + __tsan_memory_order mo, __tsan_memory_order fail_mo); +__tsan_atomic32 __tsan_atomic32_compare_exchange_val( + volatile __tsan_atomic32 *a, __tsan_atomic32 c, __tsan_atomic32 v, + __tsan_memory_order mo, __tsan_memory_order fail_mo); +__tsan_atomic64 __tsan_atomic64_compare_exchange_val( + volatile __tsan_atomic64 *a, __tsan_atomic64 c, __tsan_atomic64 v, + __tsan_memory_order mo, __tsan_memory_order fail_mo); +__tsan_atomic128 __tsan_atomic128_compare_exchange_val( + volatile __tsan_atomic128 *a, __tsan_atomic128 c, __tsan_atomic128 v, + __tsan_memory_order mo, __tsan_memory_order fail_mo); + +void __tsan_atomic_thread_fence(__tsan_memory_order mo); +void __tsan_atomic_signal_fence(__tsan_memory_order mo); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // #ifndef TSAN_INTERFACE_ATOMIC_H + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32 *ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 cmp = old_value; + __tsan_atomic32_compare_exchange_strong(ptr, &cmp, new_value, + __tsan_memory_order_relaxed, __tsan_memory_order_relaxed); + return cmp; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32 *ptr, + Atomic32 new_value) { + return __tsan_atomic32_exchange(ptr, new_value, + __tsan_memory_order_relaxed); +} + +inline Atomic32 Acquire_AtomicExchange(volatile Atomic32 *ptr, + Atomic32 new_value) { + return __tsan_atomic32_exchange(ptr, new_value, + __tsan_memory_order_acquire); +} + +inline Atomic32 Release_AtomicExchange(volatile Atomic32 *ptr, + Atomic32 new_value) { + return __tsan_atomic32_exchange(ptr, new_value, + __tsan_memory_order_release); +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32 *ptr, + Atomic32 increment) { + return increment + __tsan_atomic32_fetch_add(ptr, increment, + __tsan_memory_order_relaxed); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32 *ptr, + Atomic32 increment) { + return increment + __tsan_atomic32_fetch_add(ptr, increment, + __tsan_memory_order_acq_rel); +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32 *ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 cmp = old_value; + __tsan_atomic32_compare_exchange_strong(ptr, &cmp, new_value, + __tsan_memory_order_acquire, __tsan_memory_order_acquire); + return cmp; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32 *ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 cmp = old_value; + __tsan_atomic32_compare_exchange_strong(ptr, &cmp, new_value, + __tsan_memory_order_release, __tsan_memory_order_relaxed); + return cmp; +} + +inline void NoBarrier_Store(volatile Atomic32 *ptr, Atomic32 value) { + __tsan_atomic32_store(ptr, value, __tsan_memory_order_relaxed); +} + +inline void Acquire_Store(volatile Atomic32 *ptr, Atomic32 value) { + __tsan_atomic32_store(ptr, value, __tsan_memory_order_relaxed); + __tsan_atomic_thread_fence(__tsan_memory_order_seq_cst); +} + +inline void Release_Store(volatile Atomic32 *ptr, Atomic32 value) { + __tsan_atomic32_store(ptr, value, __tsan_memory_order_release); +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32 *ptr) { + return __tsan_atomic32_load(ptr, __tsan_memory_order_relaxed); +} + +inline Atomic32 Acquire_Load(volatile const Atomic32 *ptr) { + return __tsan_atomic32_load(ptr, __tsan_memory_order_acquire); +} + +inline Atomic32 Release_Load(volatile const Atomic32 *ptr) { + __tsan_atomic_thread_fence(__tsan_memory_order_seq_cst); + return __tsan_atomic32_load(ptr, __tsan_memory_order_relaxed); +} + +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64 *ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 cmp = old_value; + __tsan_atomic64_compare_exchange_strong(ptr, &cmp, new_value, + __tsan_memory_order_relaxed, __tsan_memory_order_relaxed); + return cmp; +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64 *ptr, + Atomic64 new_value) { + return __tsan_atomic64_exchange(ptr, new_value, __tsan_memory_order_relaxed); +} + +inline Atomic64 Acquire_AtomicExchange(volatile Atomic64 *ptr, + Atomic64 new_value) { + return __tsan_atomic64_exchange(ptr, new_value, __tsan_memory_order_acquire); +} + +inline Atomic64 Release_AtomicExchange(volatile Atomic64 *ptr, + Atomic64 new_value) { + return __tsan_atomic64_exchange(ptr, new_value, __tsan_memory_order_release); +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64 *ptr, + Atomic64 increment) { + return increment + __tsan_atomic64_fetch_add(ptr, increment, + __tsan_memory_order_relaxed); +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64 *ptr, + Atomic64 increment) { + return increment + __tsan_atomic64_fetch_add(ptr, increment, + __tsan_memory_order_acq_rel); +} + +inline void NoBarrier_Store(volatile Atomic64 *ptr, Atomic64 value) { + __tsan_atomic64_store(ptr, value, __tsan_memory_order_relaxed); +} + +inline void Acquire_Store(volatile Atomic64 *ptr, Atomic64 value) { + __tsan_atomic64_store(ptr, value, __tsan_memory_order_relaxed); + __tsan_atomic_thread_fence(__tsan_memory_order_seq_cst); +} + +inline void Release_Store(volatile Atomic64 *ptr, Atomic64 value) { + __tsan_atomic64_store(ptr, value, __tsan_memory_order_release); +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64 *ptr) { + return __tsan_atomic64_load(ptr, __tsan_memory_order_relaxed); +} + +inline Atomic64 Acquire_Load(volatile const Atomic64 *ptr) { + return __tsan_atomic64_load(ptr, __tsan_memory_order_acquire); +} + +inline Atomic64 Release_Load(volatile const Atomic64 *ptr) { + __tsan_atomic_thread_fence(__tsan_memory_order_seq_cst); + return __tsan_atomic64_load(ptr, __tsan_memory_order_relaxed); +} + +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64 *ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 cmp = old_value; + __tsan_atomic64_compare_exchange_strong(ptr, &cmp, new_value, + __tsan_memory_order_acquire, __tsan_memory_order_acquire); + return cmp; +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64 *ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 cmp = old_value; + __tsan_atomic64_compare_exchange_strong(ptr, &cmp, new_value, + __tsan_memory_order_release, __tsan_memory_order_relaxed); + return cmp; +} + +inline void MemoryBarrier() { + __tsan_atomic_thread_fence(__tsan_memory_order_seq_cst); +} + +} // namespace base::subtle +} // namespace base + +#undef ATOMICOPS_COMPILER_BARRIER + +#endif // BASE_ATOMICOPS_INTERNALS_TSAN_H_
diff --git a/src/base/atomicops_internals_x86_gcc.cc b/src/base/atomicops_internals_x86_gcc.cc new file mode 100644 index 0000000..933ca51 --- /dev/null +++ b/src/base/atomicops_internals_x86_gcc.cc
@@ -0,0 +1,104 @@ +// Copyright (c) 2006-2008 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 module gets enough CPU information to optimize the +// atomicops module on x86. + +#include <string.h> + +#include "base/atomicops.h" +#include "base/basictypes.h" + +// This file only makes sense with atomicops_internals_x86_gcc.h -- it +// depends on structs that are defined in that file. If atomicops.h +// doesn't sub-include that file, then we aren't needed, and shouldn't +// try to do anything. +#ifdef BASE_ATOMICOPS_INTERNALS_X86_GCC_H_ + +// Inline cpuid instruction. In PIC compilations, %ebx contains the address +// of the global offset table. To avoid breaking such executables, this code +// must preserve that register's value across cpuid instructions. +#if defined(__i386__) +#define cpuid(a, b, c, d, inp) \ + asm ("mov %%ebx, %%edi\n" \ + "cpuid\n" \ + "xchg %%edi, %%ebx\n" \ + : "=a" (a), "=D" (b), "=c" (c), "=d" (d) : "a" (inp)) +#elif defined (__x86_64__) +#define cpuid(a, b, c, d, inp) \ + asm ("mov %%rbx, %%rdi\n" \ + "cpuid\n" \ + "xchg %%rdi, %%rbx\n" \ + : "=a" (a), "=D" (b), "=c" (c), "=d" (d) : "a" (inp)) +#endif + +#if defined(cpuid) // initialize the struct only on x86 + +// Set the flags so that code will run correctly and conservatively, so even +// if we haven't been initialized yet, we're probably single threaded, and our +// default values should hopefully be pretty safe. +struct AtomicOps_x86CPUFeatureStruct AtomicOps_Internalx86CPUFeatures = { + false, // bug can't exist before process spawns multiple threads + false, // no SSE2 +}; + +// Initialize the AtomicOps_Internalx86CPUFeatures struct. +static void AtomicOps_Internalx86CPUFeaturesInit() { + uint32 eax; + uint32 ebx; + uint32 ecx; + uint32 edx; + + // Get vendor string (issue CPUID with eax = 0) + cpuid(eax, ebx, ecx, edx, 0); + char vendor[13]; + memcpy(vendor, &ebx, 4); + memcpy(vendor + 4, &edx, 4); + memcpy(vendor + 8, &ecx, 4); + vendor[12] = 0; + + // get feature flags in ecx/edx, and family/model in eax + cpuid(eax, ebx, ecx, edx, 1); + + int family = (eax >> 8) & 0xf; // family and model fields + int model = (eax >> 4) & 0xf; + if (family == 0xf) { // use extended family and model fields + family += (eax >> 20) & 0xff; + model += ((eax >> 16) & 0xf) << 4; + } + + // Opteron Rev E has a bug in which on very rare occasions a locked + // instruction doesn't act as a read-acquire barrier if followed by a + // non-locked read-modify-write instruction. Rev F has this bug in + // pre-release versions, but not in versions released to customers, + // so we test only for Rev E, which is family 15, model 32..63 inclusive. + if (strcmp(vendor, "AuthenticAMD") == 0 && // AMD + family == 15 && + 32 <= model && model <= 63) { + AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug = true; + } else { + AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug = false; + } + + // edx bit 26 is SSE2 which we use to tell use whether we can use mfence + AtomicOps_Internalx86CPUFeatures.has_sse2 = ((edx >> 26) & 1); +} + +namespace { + +class AtomicOpsx86Initializer { + public: + AtomicOpsx86Initializer() { + AtomicOps_Internalx86CPUFeaturesInit(); + } +}; + +// A global to get use initialized on startup via static initialization :/ +AtomicOpsx86Initializer g_initer; + +} // namespace + +#endif // if x86 + +#endif // ifdef BASE_ATOMICOPS_INTERNALS_X86_GCC_H_
diff --git a/src/base/atomicops_internals_x86_gcc.h b/src/base/atomicops_internals_x86_gcc.h new file mode 100644 index 0000000..ac02b17 --- /dev/null +++ b/src/base/atomicops_internals_x86_gcc.h
@@ -0,0 +1,269 @@ +// 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 is an internal atomic implementation, use base/atomicops.h instead. + +#ifndef BASE_ATOMICOPS_INTERNALS_X86_GCC_H_ +#define BASE_ATOMICOPS_INTERNALS_X86_GCC_H_ + +#include "base/base_export.h" + +// This struct is not part of the public API of this module; clients may not +// use it. (However, it's exported via BASE_EXPORT because clients implicitly +// do use it at link time by inlining these functions.) +// Features of this x86. Values may not be correct before main() is run, +// but are set conservatively. +struct AtomicOps_x86CPUFeatureStruct { + bool has_amd_lock_mb_bug; // Processor has AMD memory-barrier bug; do lfence + // after acquire compare-and-swap. + bool has_sse2; // Processor has SSE2. +}; +BASE_EXPORT extern struct AtomicOps_x86CPUFeatureStruct + AtomicOps_Internalx86CPUFeatures; + +#define ATOMICOPS_COMPILER_BARRIER() __asm__ __volatile__("" : : : "memory") + +namespace base { +namespace subtle { + +// 32-bit low-level operations on any platform. + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev; + __asm__ __volatile__("lock; cmpxchgl %1,%2" + : "=a" (prev) + : "q" (new_value), "m" (*ptr), "0" (old_value) + : "memory"); + return prev; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + __asm__ __volatile__("xchgl %1,%0" // The lock prefix is implicit for xchg. + : "=r" (new_value) + : "m" (*ptr), "0" (new_value) + : "memory"); + return new_value; // Now it's the previous value. +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + Atomic32 temp = increment; + __asm__ __volatile__("lock; xaddl %0,%1" + : "+r" (temp), "+m" (*ptr) + : : "memory"); + // temp now holds the old value of *ptr + return temp + increment; +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + Atomic32 temp = increment; + __asm__ __volatile__("lock; xaddl %0,%1" + : "+r" (temp), "+m" (*ptr) + : : "memory"); + // temp now holds the old value of *ptr + if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { + __asm__ __volatile__("lfence" : : : "memory"); + } + return temp + increment; +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 x = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { + __asm__ __volatile__("lfence" : : : "memory"); + } + return x; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +#if defined(__x86_64__) + +// 64-bit implementations of memory barrier can be simpler, because it +// "mfence" is guaranteed to exist. +inline void MemoryBarrier() { + __asm__ __volatile__("mfence" : : : "memory"); +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +#else + +inline void MemoryBarrier() { + if (AtomicOps_Internalx86CPUFeatures.has_sse2) { + __asm__ __volatile__("mfence" : : : "memory"); + } else { // mfence is faster but not present on PIII + Atomic32 x = 0; + NoBarrier_AtomicExchange(&x, 0); // acts as a barrier on PIII + } +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + if (AtomicOps_Internalx86CPUFeatures.has_sse2) { + *ptr = value; + __asm__ __volatile__("mfence" : : : "memory"); + } else { + NoBarrier_AtomicExchange(ptr, value); + // acts as a barrier on PIII + } +} +#endif + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + ATOMICOPS_COMPILER_BARRIER(); + *ptr = value; // An x86 store acts as a release barrier. + // See comments in Atomic64 version of Release_Store(), below. +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; // An x86 load acts as a acquire barrier. + // See comments in Atomic64 version of Release_Store(), below. + ATOMICOPS_COMPILER_BARRIER(); + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +#if defined(__x86_64__) + +// 64-bit low-level operations on 64-bit platform. + +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 prev; + __asm__ __volatile__("lock; cmpxchgq %1,%2" + : "=a" (prev) + : "q" (new_value), "m" (*ptr), "0" (old_value) + : "memory"); + return prev; +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + __asm__ __volatile__("xchgq %1,%0" // The lock prefix is implicit for xchg. + : "=r" (new_value) + : "m" (*ptr), "0" (new_value) + : "memory"); + return new_value; // Now it's the previous value. +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + Atomic64 temp = increment; + __asm__ __volatile__("lock; xaddq %0,%1" + : "+r" (temp), "+m" (*ptr) + : : "memory"); + // temp now contains the previous value of *ptr + return temp + increment; +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + Atomic64 temp = increment; + __asm__ __volatile__("lock; xaddq %0,%1" + : "+r" (temp), "+m" (*ptr) + : : "memory"); + // temp now contains the previous value of *ptr + if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { + __asm__ __volatile__("lfence" : : : "memory"); + } + return temp + increment; +} + +inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { + ATOMICOPS_COMPILER_BARRIER(); + + *ptr = value; // An x86 store acts as a release barrier + // for current AMD/Intel chips as of Jan 2008. + // See also Acquire_Load(), below. + + // When new chips come out, check: + // IA-32 Intel Architecture Software Developer's Manual, Volume 3: + // System Programming Guide, Chatper 7: Multiple-processor management, + // Section 7.2, Memory Ordering. + // Last seen at: + // http://developer.intel.com/design/pentium4/manuals/index_new.htm + // + // x86 stores/loads fail to act as barriers for a few instructions (clflush + // maskmovdqu maskmovq movntdq movnti movntpd movntps movntq) but these are + // not generated by the compiler, and are rare. Users of these instructions + // need to know about cache behaviour in any case since all of these involve + // either flushing cache lines or non-temporal cache hints. +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { + return *ptr; +} + +inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { + Atomic64 value = *ptr; // An x86 load acts as a acquire barrier, + // for current AMD/Intel chips as of Jan 2008. + // See also Release_Store(), above. + ATOMICOPS_COMPILER_BARRIER(); + return value; +} + +inline Atomic64 Release_Load(volatile const Atomic64* ptr) { + MemoryBarrier(); + return *ptr; +} + +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 x = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { + __asm__ __volatile__("lfence" : : : "memory"); + } + return x; +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +#endif // defined(__x86_64__) + +} // namespace base::subtle +} // namespace base + +#undef ATOMICOPS_COMPILER_BARRIER + +#endif // BASE_ATOMICOPS_INTERNALS_X86_GCC_H_
diff --git a/src/base/atomicops_internals_x86_msvc.h b/src/base/atomicops_internals_x86_msvc.h new file mode 100644 index 0000000..2f428b6 --- /dev/null +++ b/src/base/atomicops_internals_x86_msvc.h
@@ -0,0 +1,224 @@ +// Copyright (c) 2006-2008 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 is an internal atomic implementation, use base/atomicops.h instead. + +#ifndef BASE_ATOMICOPS_INTERNALS_X86_MSVC_H_ +#define BASE_ATOMICOPS_INTERNALS_X86_MSVC_H_ + +#if defined(__LB_XB1__) && !defined(WIN32) +# include <intrin.h> +// These are normally defined by windows.h: +typedef long LONG; +typedef long long LONGLONG; +typedef void* PVOID; +void __faststorefence(void); +#pragma intrinsic(__faststorefence) +inline void MemoryBarrier() { + __faststorefence(); +} +#define InterlockedCompareExchange _InterlockedCompareExchange +#define InterlockedCompareExchangePointer _InterlockedCompareExchangePointer +#define InterlockedExchangePointer _InterlockedExchangePointer +#define InterlockedExchange _InterlockedExchange +#define InterlockedExchangeAdd _InterlockedExchangeAdd +#define InterlockedExchangeAdd64 _InterlockedExchangeAdd64 + +#else +# include <windows.h> + +#if defined(ARCH_CPU_64_BITS) || defined(__LB_XB360__) +// windows.h #defines this (only on x64). This causes problems because the +// public API also uses MemoryBarrier at the public name for this fence. So, on +// X64, undef it, and call its documented +// (http://msdn.microsoft.com/en-us/library/windows/desktop/ms684208.aspx) +// implementation directly. +#undef MemoryBarrier +#endif + +#endif + +namespace base { +namespace subtle { + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + LONG result = InterlockedCompareExchange( + reinterpret_cast<volatile LONG*>(ptr), + static_cast<LONG>(new_value), + static_cast<LONG>(old_value)); + return static_cast<Atomic32>(result); +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + LONG result = InterlockedExchange( + reinterpret_cast<volatile LONG*>(ptr), + static_cast<LONG>(new_value)); + return static_cast<Atomic32>(result); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return InterlockedExchangeAdd( + reinterpret_cast<volatile LONG*>(ptr), + static_cast<LONG>(increment)) + increment; +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return Barrier_AtomicIncrement(ptr, increment); +} + +#if !(defined(_MSC_VER) && _MSC_VER >= 1400) +#error "We require at least vs2005 for MemoryBarrier" +#endif +inline void MemoryBarrier() { +#if defined(ARCH_CPU_64_BITS) || defined(__LB_XB360__) + // See #undef and note at the top of this file. + __faststorefence(); +#else + // We use MemoryBarrier from WinNT.h + ::MemoryBarrier(); +#endif +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + NoBarrier_AtomicExchange(ptr, value); + // acts as a barrier in this implementation +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; // works w/o barrier for current Intel chips as of June 2005 + // See comments in Atomic64 version of Release_Store() below. +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +#if defined(_WIN64) + +// 64-bit low-level operations on 64-bit platform. + +COMPILE_ASSERT(sizeof(Atomic64) == sizeof(PVOID), atomic_word_is_atomic); + +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + PVOID result = InterlockedCompareExchangePointer( + reinterpret_cast<volatile PVOID*>(ptr), + reinterpret_cast<PVOID>(new_value), reinterpret_cast<PVOID>(old_value)); + return reinterpret_cast<Atomic64>(result); +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + PVOID result = InterlockedExchangePointer( + reinterpret_cast<volatile PVOID*>(ptr), + reinterpret_cast<PVOID>(new_value)); + return reinterpret_cast<Atomic64>(result); +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + return InterlockedExchangeAdd64( + reinterpret_cast<volatile LONGLONG*>(ptr), + static_cast<LONGLONG>(increment)) + increment; +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + return Barrier_AtomicIncrement(ptr, increment); +} + +inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { + NoBarrier_AtomicExchange(ptr, value); + // acts as a barrier in this implementation +} + +inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; // works w/o barrier for current Intel chips as of June 2005 + + // When new chips come out, check: + // IA-32 Intel Architecture Software Developer's Manual, Volume 3: + // System Programming Guide, Chatper 7: Multiple-processor management, + // Section 7.2, Memory Ordering. + // Last seen at: + // http://developer.intel.com/design/pentium4/manuals/index_new.htm +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { + return *ptr; +} + +inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { + Atomic64 value = *ptr; + return value; +} + +inline Atomic64 Release_Load(volatile const Atomic64* ptr) { + MemoryBarrier(); + return *ptr; +} + +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + + +#endif // defined(_WIN64) + +} // namespace base::subtle +} // namespace base + +#if defined(__LB_XB1__) +#undef InterlockedCompareExchange +#undef InterlockedCompareExchangePointer +#undef InterlockedExchangePointer +#undef InterlockedExchange +#undef InterlockedExchangeAdd +#undef InterlockedExchangeAdd64 +#endif + +#endif // BASE_ATOMICOPS_INTERNALS_X86_MSVC_H_
diff --git a/src/base/atomicops_unittest.cc b/src/base/atomicops_unittest.cc new file mode 100644 index 0000000..d73a098 --- /dev/null +++ b/src/base/atomicops_unittest.cc
@@ -0,0 +1,241 @@ +// 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/atomicops.h" + +#include <string.h> + +#include "base/port.h" +#include "testing/gtest/include/gtest/gtest.h" + +template <class AtomicType> +static void TestAtomicIncrement() { + // For now, we just test single threaded execution + + // use a guard value to make sure the NoBarrier_AtomicIncrement doesn't go + // outside the expected address bounds. This is in particular to + // test that some future change to the asm code doesn't cause the + // 32-bit NoBarrier_AtomicIncrement doesn't do the wrong thing on 64-bit + // machines. + struct { + AtomicType prev_word; + AtomicType count; + AtomicType next_word; + } s; + + AtomicType prev_word_value, next_word_value; + memset(&prev_word_value, 0xFF, sizeof(AtomicType)); + memset(&next_word_value, 0xEE, sizeof(AtomicType)); + + s.prev_word = prev_word_value; + s.count = 0; + s.next_word = next_word_value; + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 1), 1); + EXPECT_EQ(s.count, 1); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 2), 3); + EXPECT_EQ(s.count, 3); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 3), 6); + EXPECT_EQ(s.count, 6); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -3), 3); + EXPECT_EQ(s.count, 3); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -2), 1); + EXPECT_EQ(s.count, 1); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -1), 0); + EXPECT_EQ(s.count, 0); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -1), -1); + EXPECT_EQ(s.count, -1); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -4), -5); + EXPECT_EQ(s.count, -5); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); + + EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 5), 0); + EXPECT_EQ(s.count, 0); + EXPECT_EQ(s.prev_word, prev_word_value); + EXPECT_EQ(s.next_word, next_word_value); +} + + +#define NUM_BITS(T) (sizeof(T) * 8) + + +template <class AtomicType> +static void TestCompareAndSwap() { + AtomicType value = 0; + AtomicType prev = base::subtle::NoBarrier_CompareAndSwap(&value, 0, 1); + EXPECT_EQ(1, value); + EXPECT_EQ(0, prev); + + // Use test value that has non-zero bits in both halves, more for testing + // 64-bit implementation on 32-bit platforms. + const AtomicType k_test_val = (GG_ULONGLONG(1) << + (NUM_BITS(AtomicType) - 2)) + 11; + value = k_test_val; + prev = base::subtle::NoBarrier_CompareAndSwap(&value, 0, 5); + EXPECT_EQ(k_test_val, value); + EXPECT_EQ(k_test_val, prev); + + value = k_test_val; + prev = base::subtle::NoBarrier_CompareAndSwap(&value, k_test_val, 5); + EXPECT_EQ(5, value); + EXPECT_EQ(k_test_val, prev); +} + + +template <class AtomicType> +static void TestAtomicExchange() { + AtomicType value = 0; + AtomicType new_value = base::subtle::NoBarrier_AtomicExchange(&value, 1); + EXPECT_EQ(1, value); + EXPECT_EQ(0, new_value); + + // Use test value that has non-zero bits in both halves, more for testing + // 64-bit implementation on 32-bit platforms. + const AtomicType k_test_val = (GG_ULONGLONG(1) << + (NUM_BITS(AtomicType) - 2)) + 11; + value = k_test_val; + new_value = base::subtle::NoBarrier_AtomicExchange(&value, k_test_val); + EXPECT_EQ(k_test_val, value); + EXPECT_EQ(k_test_val, new_value); + + value = k_test_val; + new_value = base::subtle::NoBarrier_AtomicExchange(&value, 5); + EXPECT_EQ(5, value); + EXPECT_EQ(k_test_val, new_value); +} + + +template <class AtomicType> +static void TestAtomicIncrementBounds() { + // Test at rollover boundary between int_max and int_min + AtomicType test_val = (GG_ULONGLONG(1) << + (NUM_BITS(AtomicType) - 1)); + AtomicType value = -1 ^ test_val; + AtomicType new_value = base::subtle::NoBarrier_AtomicIncrement(&value, 1); + EXPECT_EQ(test_val, value); + EXPECT_EQ(value, new_value); + + base::subtle::NoBarrier_AtomicIncrement(&value, -1); + EXPECT_EQ(-1 ^ test_val, value); + + // Test at 32-bit boundary for 64-bit atomic type. + test_val = GG_ULONGLONG(1) << (NUM_BITS(AtomicType) / 2); + value = test_val - 1; + new_value = base::subtle::NoBarrier_AtomicIncrement(&value, 1); + EXPECT_EQ(test_val, value); + EXPECT_EQ(value, new_value); + + base::subtle::NoBarrier_AtomicIncrement(&value, -1); + EXPECT_EQ(test_val - 1, value); +} + +// Return an AtomicType with the value 0xa5a5a5.. +template <class AtomicType> +static AtomicType TestFillValue() { + AtomicType val = 0; + memset(&val, 0xa5, sizeof(AtomicType)); + return val; +} + +// This is a simple sanity check that values are correct. Not testing +// atomicity +template <class AtomicType> +static void TestStore() { + const AtomicType kVal1 = TestFillValue<AtomicType>(); + const AtomicType kVal2 = static_cast<AtomicType>(-1); + + AtomicType value; + + base::subtle::NoBarrier_Store(&value, kVal1); + EXPECT_EQ(kVal1, value); + base::subtle::NoBarrier_Store(&value, kVal2); + EXPECT_EQ(kVal2, value); + + base::subtle::Acquire_Store(&value, kVal1); + EXPECT_EQ(kVal1, value); + base::subtle::Acquire_Store(&value, kVal2); + EXPECT_EQ(kVal2, value); + + base::subtle::Release_Store(&value, kVal1); + EXPECT_EQ(kVal1, value); + base::subtle::Release_Store(&value, kVal2); + EXPECT_EQ(kVal2, value); +} + +// This is a simple sanity check that values are correct. Not testing +// atomicity +template <class AtomicType> +static void TestLoad() { + const AtomicType kVal1 = TestFillValue<AtomicType>(); + const AtomicType kVal2 = static_cast<AtomicType>(-1); + + AtomicType value; + + value = kVal1; + EXPECT_EQ(kVal1, base::subtle::NoBarrier_Load(&value)); + value = kVal2; + EXPECT_EQ(kVal2, base::subtle::NoBarrier_Load(&value)); + + value = kVal1; + EXPECT_EQ(kVal1, base::subtle::Acquire_Load(&value)); + value = kVal2; + EXPECT_EQ(kVal2, base::subtle::Acquire_Load(&value)); + + value = kVal1; + EXPECT_EQ(kVal1, base::subtle::Release_Load(&value)); + value = kVal2; + EXPECT_EQ(kVal2, base::subtle::Release_Load(&value)); +} + +TEST(AtomicOpsTest, Inc) { + TestAtomicIncrement<base::subtle::Atomic32>(); + TestAtomicIncrement<base::subtle::AtomicWord>(); +} + +TEST(AtomicOpsTest, CompareAndSwap) { + TestCompareAndSwap<base::subtle::Atomic32>(); + TestCompareAndSwap<base::subtle::AtomicWord>(); +} + +TEST(AtomicOpsTest, Exchange) { + TestAtomicExchange<base::subtle::Atomic32>(); + TestAtomicExchange<base::subtle::AtomicWord>(); +} + +TEST(AtomicOpsTest, IncrementBounds) { + TestAtomicIncrementBounds<base::subtle::Atomic32>(); + TestAtomicIncrementBounds<base::subtle::AtomicWord>(); +} + +TEST(AtomicOpsTest, Store) { + TestStore<base::subtle::Atomic32>(); + TestStore<base::subtle::AtomicWord>(); +} + +TEST(AtomicOpsTest, Load) { + TestLoad<base::subtle::Atomic32>(); + TestLoad<base::subtle::AtomicWord>(); +}
diff --git a/src/base/auto_reset.h b/src/base/auto_reset.h new file mode 100644 index 0000000..32f138e --- /dev/null +++ b/src/base/auto_reset.h
@@ -0,0 +1,41 @@ +// 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. + +#ifndef BASE_AUTO_RESET_H_ +#define BASE_AUTO_RESET_H_ + +#include "base/basictypes.h" + +// base::AutoReset<> is useful for setting a variable to a new value only within +// a particular scope. An base::AutoReset<> object resets a variable to its +// original value upon destruction, making it an alternative to writing +// "var = false;" or "var = old_val;" at all of a block's exit points. +// +// This should be obvious, but note that an base::AutoReset<> instance should +// have a shorter lifetime than its scoped_variable, to prevent invalid memory +// writes when the base::AutoReset<> object is destroyed. + +namespace base { + +template<typename T> +class AutoReset { + public: + AutoReset(T* scoped_variable, T new_value) + : scoped_variable_(scoped_variable), + original_value_(*scoped_variable) { + *scoped_variable_ = new_value; + } + + ~AutoReset() { *scoped_variable_ = original_value_; } + + private: + T* scoped_variable_; + T original_value_; + + DISALLOW_COPY_AND_ASSIGN(AutoReset); +}; + +} + +#endif // BASE_AUTO_RESET_H_
diff --git a/src/base/base.gyp b/src/base/base.gyp new file mode 100644 index 0000000..3838ff6 --- /dev/null +++ b/src/base/base.gyp
@@ -0,0 +1,1322 @@ +# 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'includes': [ + '../build/win_precompile.gypi', + 'base.gypi', + ], + 'targets': [ + { + 'target_name': 'base', + 'type': '<(component)', + 'toolsets': ['host', 'target'], + 'variables': { + 'base_target': 1, + 'enable_wexit_time_destructors': 1, + 'optimize': 'max', + }, + 'dependencies': [ + 'base_static', + 'allocator/allocator.gyp:allocator_extension_thunks', + '../testing/gtest.gyp:gtest_prod', + '../third_party/modp_b64/modp_b64.gyp:modp_b64', + 'third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + ], + # TODO(gregoryd): direct_dependent_settings should be shared with the + # 64-bit target, but it doesn't work due to a bug in gyp + 'direct_dependent_settings': { + 'include_dirs': [ + '..', + ], + }, + 'conditions': [ + ['use_glib==1', { + 'conditions': [ + ['chromeos==1', { + 'sources/': [ ['include', '_chromeos\\.cc$'] ] + }], + ['linux_use_tcmalloc==0', { + 'defines': [ + 'NO_TCMALLOC', + ], + 'direct_dependent_settings': { + 'defines': [ + 'NO_TCMALLOC', + ], + }, + }], + ['toolkit_uses_gtk==1', { + 'dependencies': [ + '../build/linux/system.gyp:gtk', + ], + 'export_dependent_settings': [ + '../build/linux/system.gyp:gtk', + ], + }], + ], + 'dependencies': [ + 'symbolize', + '../build/linux/system.gyp:glib', + '../build/linux/system.gyp:x11', + 'xdg_mime', + ], + 'defines': [ + 'USE_SYMBOLIZE', + ], + 'cflags': [ + '-Wno-write-strings', + ], + 'export_dependent_settings': [ + '../build/linux/system.gyp:glib', + '../build/linux/system.gyp:x11', + ], + }, { # use_glib!=1 + 'sources/': [ + ['exclude', '/xdg_user_dirs/'], + ['exclude', '_nss\\.cc$'], + ], + }], + ['OS == "android" and _toolset == "host"', { + # Base for host support is the minimum required to run the + # ssl false start blacklist tool. It requires further changes + # to generically support host builds (and tests). + # Note: when building for host, gyp has OS == "android", + # hence the *_android.cc files are included but the actual code + # doesn't have OS_ANDROID / ANDROID defined. + 'conditions': [ + # Host build on linux depends on system.gyp::gtk as + # default linux build has TOOLKIT_GTK defined. + ['host_os == "linux"', { + 'sources/': [ + ['include', '^atomicops_internals_x86_gcc\\.cc$'], + ], + 'dependencies': [ + '../build/linux/system.gyp:gtk', + ], + 'export_dependent_settings': [ + '../build/linux/system.gyp:gtk', + ], + }], + ['host_os == "mac"', { + 'sources/': [ + ['exclude', '^native_library_linux\\.cc$'], + ['exclude', '^process_util_linux\\.cc$'], + ['exclude', '^sys_info_linux\\.cc$'], + ['exclude', '^sys_string_conversions_linux\\.cc$'], + ['exclude', '^worker_pool_linux\\.cc$'], + ], + }], + ], + }], + ['(OS == "android" or ((OS == "lb_shell" or OS == "starboard") and target_arch == "android")) and _toolset == "target"', { + 'dependencies': [ + 'base_jni_headers', + 'symbolize', + '../third_party/ashmem/ashmem.gyp:ashmem', + '../third_party/icu/icu.gyp:icuuc', + ], + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/base', + ], + 'link_settings': { + 'libraries': [ + '-llog', + ], + }, + 'defines': [ + 'USE_SYMBOLIZE', + ], + 'sources!': [ + 'debug/stack_trace_posix.cc', + ], + 'conditions' : [ + ['target_arch == "ia32"', { + 'sources/': [ + ['include', '^atomicops_internals_x86_gcc\\.cc$'], + ], + }], + ['target_arch != "android"', { + 'includes': [ + '../build/android/cpufeatures.gypi', + ], + }], + ], + }], + ['(OS == "android" or ((OS == "lb_shell" or OS == "starboard") and target_arch == "android")) and _toolset == "target" and android_build_type == 0', { + 'dependencies': [ + 'base_java', + ], + }], + ['((OS == "lb_shell" or OS == "starboard") and target_arch == "android")', { + 'link_settings': { + 'libraries': [ + # for android_getCpuCount + '-lportable', + ], + }, + }], + ['os_bsd==1', { + 'include_dirs': [ + '/usr/local/include', + ], + 'link_settings': { + 'libraries': [ + '-L/usr/local/lib -lexecinfo', + ], + }, + }], + ['OS == "linux"', { + 'link_settings': { + 'libraries': [ + # We need rt for clock_gettime(). + '-lrt', + # For 'native_library_linux.cc' + '-ldl', + ], + }, + }], + ['OS == "mac"', { + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/System/Library/Frameworks/AppKit.framework', + '$(SDKROOT)/System/Library/Frameworks/ApplicationServices.framework', + '$(SDKROOT)/System/Library/Frameworks/Carbon.framework', + '$(SDKROOT)/System/Library/Frameworks/CoreFoundation.framework', + '$(SDKROOT)/System/Library/Frameworks/Foundation.framework', + '$(SDKROOT)/System/Library/Frameworks/IOKit.framework', + '$(SDKROOT)/System/Library/Frameworks/Security.framework', + ], + }, + 'dependencies': [ + '../third_party/mach_override/mach_override.gyp:mach_override', + ], + }], + ['OS == "ios"', { + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/System/Library/Frameworks/CoreFoundation.framework', + '$(SDKROOT)/System/Library/Frameworks/CoreGraphics.framework', + '$(SDKROOT)/System/Library/Frameworks/CoreText.framework', + '$(SDKROOT)/System/Library/Frameworks/Foundation.framework', + '$(SDKROOT)/System/Library/Frameworks/UIKit.framework', + ], + }, + }], + ['OS != "win" and OS != "ios"', { + 'dependencies': ['../third_party/libevent/libevent.gyp:libevent'], + },], + ['component=="shared_library"', { + 'conditions': [ + ['OS=="win"', { + 'sources!': [ + 'debug/debug_on_start_win.cc', + ], + }], + ], + }], + ['OS == "lb_shell"', { + 'conditions': [ + ['target_arch != "android"', { + 'dependencies!': [ + '../third_party/libevent/libevent.gyp:libevent' + ], + }], + ['target_arch == "linux"', { + 'defines': [ + 'USE_SYMBOLIZE', + ], + 'dependencies': [ + 'symbolize', + ], + }], + ['target_arch == "ps3" and _toolset == "target"', { + # Script for resolving symbols, for use by stack_trace_ps3.cc + 'copies': [ + { + 'destination': '<(PRODUCT_DIR)', + 'files': [ + 'debug/addr2line_ps3.py', + ], + }, + ], + 'dependencies': [ + # On PS3, we use both posix emulation and starboard for + # threading. We depend on starboard for its TLS implementation. + # Eventually this will replace the posix_emulation below. + '<(DEPTH)/starboard/starboard.gyp:starboard', + ], + }], + # toolset can be host or target. + # (host in the case of e.g. protobuf compiler.) + # We only want posix_emulation for target builds. + ['_toolset == "target"', { + 'dependencies': [ + '<(lbshell_root)/build/projects/posix_emulation.gyp:posix_emulation', + ], + }], # _toolset == "target" + ], + 'sources': [ + 'debug/debugger_shell.cc', + 'debug/stack_trace_shell.cc', + 'guid_shell.cc', + 'safe_strerror_shell.cc', + 'sys_string_conversions_shell.cc', + 'test/test_file_util_shell.cc', + 'threading/thread_local_shell.cc', + 'threading/thread_local_storage_shell.cc', + 'threading/worker_pool_shell.cc', + ], + }], # OS=="lb_shell" + ['OS == "starboard"', { + 'conditions': [ + ['_toolset == "target"', { + 'dependencies': [ + '<(DEPTH)/starboard/starboard.gyp:starboard', + '<(DEPTH)/starboard/client_porting/eztime/eztime.gyp:eztime', + ], + }], # _toolset == "target" + ], + 'dependencies!': [ + '../third_party/libevent/libevent.gyp:libevent' + ], + 'sources': [ + 'base_paths_starboard.cc', + 'test/test_file_util_starboard.cc', + ], + }], # OS=="starboard" + ['OS == "lb_shell" or OS == "starboard"', { + 'sources!': [ + 'file_descriptor_shuffle.cc', + ], + }], # OS == "lb_shell" or OS == "starboard" + ], + 'sources': [ + 'third_party/nspr/prcpucfg.h', + 'third_party/nspr/prcpucfg_win.h', + 'third_party/nspr/prtypes.h', + 'third_party/xdg_user_dirs/xdg_user_dir_lookup.cc', + 'third_party/xdg_user_dirs/xdg_user_dir_lookup.h', + 'auto_reset.h', + 'event_recorder.h', + 'event_recorder_stubs.cc', + 'event_recorder_win.cc', + 'linux_util.cc', + 'linux_util.h', + 'md5.cc', + 'md5.h', + 'message_pump_android.cc', + 'message_pump_android.h', + 'message_pump_glib.cc', + 'message_pump_glib.h', + 'message_pump_gtk.cc', + 'message_pump_gtk.h', + 'message_pump_io_ios.cc', + 'message_pump_io_ios.h', + 'message_pump_observer.h', + 'message_pump_aurax11.cc', + 'message_pump_aurax11.h', + 'message_pump_libevent.cc', + 'message_pump_libevent.h', + 'message_pump_mac.h', + 'message_pump_mac.mm', + 'metrics/field_trial.cc', + 'metrics/field_trial.h', + 'posix/file_descriptor_shuffle.cc', + 'posix/file_descriptor_shuffle.h', + 'sync_socket.h', + 'sync_socket_win.cc', + 'sync_socket_posix.cc', + ], + }, + { + 'target_name': 'base_i18n', + 'type': '<(component)', + 'variables': { + 'enable_wexit_time_destructors': 1, + 'optimize': 'max', + }, + 'dependencies': [ + 'base', + 'third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '../third_party/icu/icu.gyp:icui18n', + '../third_party/icu/icu.gyp:icuuc', + ], + 'conditions': [ + ['toolkit_uses_gtk==1', { + 'dependencies': [ + # i18n/rtl.cc uses gtk + '../build/linux/system.gyp:gtk', + ], + }], + ['OS=="lb_shell"', { + 'dependencies': [ + '<(lbshell_root)/build/projects/posix_emulation.gyp:posix_emulation', + ], + }], + ['OS=="starboard"', { + 'dependencies': [ + '<(DEPTH)/starboard/starboard.gyp:starboard', + '<(DEPTH)/starboard/client_porting/icu_init/icu_init.gyp:icu_init', + ], + }], + ], + 'export_dependent_settings': [ + 'base', + ], + 'defines': [ + 'BASE_I18N_IMPLEMENTATION', + ], + 'sources': [ + 'i18n/base_i18n_export.h', + 'i18n/bidi_line_iterator.cc', + 'i18n/bidi_line_iterator.h', + 'i18n/break_iterator.cc', + 'i18n/break_iterator.h', + 'i18n/char_iterator.cc', + 'i18n/char_iterator.h', + 'i18n/case_conversion.cc', + 'i18n/case_conversion.h', + 'i18n/file_util_icu.cc', + 'i18n/file_util_icu.h', + 'i18n/icu_encoding_detection.cc', + 'i18n/icu_encoding_detection.h', + 'i18n/icu_string_conversions.cc', + 'i18n/icu_string_conversions.h', + 'i18n/icu_util.cc', + 'i18n/icu_util.h', + 'i18n/number_formatting.cc', + 'i18n/number_formatting.h', + 'i18n/rtl.cc', + 'i18n/rtl.h', + 'i18n/string_search.cc', + 'i18n/string_search.h', + 'i18n/time_formatting.cc', + 'i18n/time_formatting.h', + ], + }, + { + 'target_name': 'base_prefs', + 'type': '<(component)', + 'variables': { + 'enable_wexit_time_destructors': 1, + 'optimize': 'max', + }, + 'dependencies': [ + 'base', + ], + 'export_dependent_settings': [ + 'base', + ], + 'defines': [ + 'BASE_PREFS_IMPLEMENTATION', + ], + 'sources': [ + 'prefs/default_pref_store.cc', + 'prefs/default_pref_store.h', + 'prefs/json_pref_store.cc', + 'prefs/json_pref_store.h', + 'prefs/overlay_user_pref_store.cc', + 'prefs/overlay_user_pref_store.h', + 'prefs/persistent_pref_store.h', + 'prefs/pref_observer.h', + 'prefs/pref_notifier.h', + 'prefs/pref_store.cc', + 'prefs/pref_store.h', + 'prefs/pref_value_map.cc', + 'prefs/pref_value_map.h', + 'prefs/public/pref_change_registrar.cc', + 'prefs/public/pref_change_registrar.h', + 'prefs/public/pref_member.cc', + 'prefs/public/pref_member.h', + 'prefs/public/pref_service_base.h', + 'prefs/value_map_pref_store.cc', + 'prefs/value_map_pref_store.h', + ], + 'conditions': [ + ['OS == "starboard"', { + 'sources!': [ + # Uses ImportantFileWriter, which is not supported by Starboard. + 'prefs/json_pref_store.cc', + 'prefs/json_pref_store.h', + ], + }], # OS=="starboard" + ], + }, + { + # This is the subset of files from base that should not be used with a + # dynamic library. Note that this library cannot depend on base because + # base depends on base_static. + 'target_name': 'base_static', + 'type': 'static_library', + 'variables': { + 'enable_wexit_time_destructors': 1, + 'optimize': 'max', + }, + 'toolsets': ['host', 'target'], + 'sources': [ + 'base_switches.cc', + 'base_switches.h', + 'win/pe_image.cc', + 'win/pe_image.h', + ], + 'include_dirs': [ + '..', + ], + 'conditions': [ + ['OS == "lb_shell" or OS=="starboard"', { + 'sources/': [ + ['exclude', '^win/'], + ], + }], + ], + }, + { + # TODO(rvargas): Remove this when gyp finally supports a clean model. + # See bug 36232. + 'target_name': 'base_static_win64', + 'type': 'static_library', + 'sources': [ + 'base_switches.cc', + 'base_switches.h', + 'win/pe_image.cc', + 'win/pe_image.h', + ], + 'sources!': [ + # base64.cc depends on modp_b64. + 'base64.cc', + ], + 'include_dirs': [ + '..', + ], + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + 'defines': [ + 'NACL_WIN64', + ], + # TODO(rvargas): Bug 78117. Remove this. + 'msvs_disabled_warnings': [ + 4244, + ], + }, + # Include this target for a main() function that simply instantiates + # and runs a base::TestSuite. + { + 'target_name': 'run_all_unittests', + 'type': 'static_library', + 'dependencies': [ + 'test_support_base', + ], + 'sources': [ + 'test/run_all_unittests.cc', + ], + }, + { + 'target_name': 'base_unittests', + 'type': '<(gtest_target_type)', + 'sources': [ + # Tests. + 'android/jni_android_unittest.cc', + 'android/jni_array_unittest.cc', + 'android/jni_string_unittest.cc', + 'android/path_utils_unittest.cc', + 'android/scoped_java_ref_unittest.cc', + 'at_exit_unittest.cc', + 'atomicops_unittest.cc', + 'base64_unittest.cc', + 'bind_helpers_unittest.cc', + 'bind_unittest.cc', + 'bind_unittest.nc', + 'bits_unittest.cc', + 'build_time_unittest.cc', + 'callback_unittest.cc', + 'callback_unittest.nc', + 'cancelable_callback_unittest.cc', + 'command_line_unittest.cc', + 'containers/linked_list_unittest.cc', + 'containers/mru_cache_unittest.cc', + 'containers/small_map_unittest.cc', + 'containers/stack_container_unittest.cc', + 'cpu_unittest.cc', + 'debug/leak_tracker_unittest.cc', + 'debug/stack_trace_unittest.cc', + 'debug/trace_event_unittest.cc', + 'debug/trace_event_unittest.h', + 'debug/trace_event_win_unittest.cc', + 'environment_unittest.cc', + 'file_path_unittest.cc', + 'file_util_proxy_unittest.cc', + 'file_util_unittest.cc', + 'file_version_info_unittest.cc', + 'files/dir_reader_posix_unittest.cc', + 'files/important_file_writer_unittest.cc', + 'files/scoped_temp_dir_unittest.cc', + 'gmock_unittest.cc', + 'guid_unittest.cc', + 'hi_res_timer_manager_unittest.cc', + 'id_map_unittest.cc', + 'i18n/break_iterator_unittest.cc', + 'i18n/char_iterator_unittest.cc', + 'i18n/case_conversion_unittest.cc', + 'i18n/file_util_icu_unittest.cc', + 'i18n/icu_string_conversions_unittest.cc', + 'i18n/number_formatting_unittest.cc', + 'i18n/rtl_unittest.cc', + 'i18n/string_search_unittest.cc', + 'i18n/time_formatting_unittest.cc', + 'ios/device_util_unittest.mm', + 'json/json_parser_unittest.cc', + 'json/json_reader_unittest.cc', + 'json/json_value_converter_unittest.cc', + 'json/json_value_serializer_unittest.cc', + 'json/json_writer_unittest.cc', + 'json/string_escape_unittest.cc', + 'lazy_instance_unittest.cc', + 'logging_unittest.cc', + 'mac/bind_objc_block_unittest.mm', + 'mac/foundation_util_unittest.mm', + 'mac/libdispatch_task_runner_unittest.cc', + 'mac/mac_util_unittest.mm', + 'mac/objc_property_releaser_unittest.mm', + 'mac/scoped_sending_event_unittest.mm', + 'md5_unittest.cc', + 'memory/aligned_memory_unittest.cc', + 'memory/linked_ptr_unittest.cc', + 'memory/ref_counted_memory_unittest.cc', + 'memory/ref_counted_unittest.cc', + 'memory/scoped_nsobject_unittest.mm', + 'memory/scoped_ptr_unittest.cc', + 'memory/scoped_ptr_unittest.nc', + 'memory/scoped_vector_unittest.cc', + 'memory/singleton_unittest.cc', + 'memory/weak_ptr_unittest.cc', + 'memory/weak_ptr_unittest.nc', + 'message_loop_proxy_impl_unittest.cc', + 'message_loop_proxy_unittest.cc', + 'message_loop_unittest.cc', + 'message_pump_glib_unittest.cc', + 'message_pump_io_ios_unittest.cc', + 'message_pump_libevent_unittest.cc', + 'metrics/sample_map_unittest.cc', + 'metrics/sample_vector_unittest.cc', + 'metrics/bucket_ranges_unittest.cc', + 'metrics/field_trial_unittest.cc', + 'metrics/histogram_unittest.cc', + 'metrics/sparse_histogram_unittest.cc', + 'metrics/stats_table_unittest.cc', + 'metrics/statistics_recorder_unittest.cc', + 'object_tracker.h', + 'observer_list_unittest.cc', + 'os_compat_android_unittest.cc', + 'path_service_unittest.cc', + 'pickle_unittest.cc', + 'platform_file_unittest.cc', + 'posix/file_descriptor_shuffle_unittest.cc', + 'pr_time_unittest.cc', + 'process_util_unittest.cc', + 'process_util_unittest_ios.cc', + 'process_util_unittest_mac.h', + 'process_util_unittest_mac.mm', + 'profiler/tracked_time_unittest.cc', + 'rand_util_unittest.cc', + 'scoped_native_library_unittest.cc', + 'scoped_observer.h', + 'sha1_unittest.cc', + 'shared_memory_unittest.cc', + 'stl_util_unittest.cc', + 'string16_unittest.cc', + 'string_number_conversions_unittest.cc', + 'string_piece_unittest.cc', + 'string_split_unittest.cc', + 'string_tokenizer_unittest.cc', + 'string_util_unittest.cc', + 'stringize_macros_unittest.cc', + 'stringprintf_unittest.cc', + 'synchronization/cancellation_flag_unittest.cc', + 'synchronization/condition_variable_unittest.cc', + 'synchronization/lock_unittest.cc', + 'synchronization/waitable_event_unittest.cc', + 'synchronization/waitable_event_watcher_unittest.cc', + 'sys_info_unittest.cc', + 'sys_string_conversions_mac_unittest.mm', + 'sys_string_conversions_unittest.cc', + 'system_monitor/system_monitor_unittest.cc', + 'task_runner_util_unittest.cc', + 'template_util_unittest.cc', + 'test/sequenced_worker_pool_owner.cc', + 'test/sequenced_worker_pool_owner.h', + 'test/trace_event_analyzer_unittest.cc', + 'test/time_helpers.cc', + 'test/time_helpers.h', + 'threading/non_thread_safe_unittest.cc', + 'threading/platform_thread_unittest.cc', + 'threading/sequenced_worker_pool_unittest.cc', + 'threading/simple_thread_unittest.cc', + 'threading/thread_checker_unittest.cc', + 'threading/thread_collision_warner_unittest.cc', + 'threading/thread_local_storage_unittest.cc', + 'threading/thread_local_unittest.cc', + 'threading/thread_unittest.cc', + 'threading/watchdog_unittest.cc', + 'threading/worker_pool_posix_unittest.cc', + 'threading/worker_pool_unittest.cc', + 'time_unittest.cc', + 'time_win_unittest.cc', + 'timer_unittest.cc', + 'tools_sanity_unittest.cc', + 'tracked_objects_unittest.cc', + 'tuple_unittest.cc', + 'utf_offset_string_conversions_unittest.cc', + 'utf_string_conversions_unittest.cc', + 'values_unittest.cc', + 'version_unittest.cc', + 'vlog_unittest.cc', + 'win/dllmain.cc', + 'win/enum_variant_unittest.cc', + 'win/event_trace_consumer_unittest.cc', + 'win/event_trace_controller_unittest.cc', + 'win/event_trace_provider_unittest.cc', + 'win/i18n_unittest.cc', + 'win/iunknown_impl_unittest.cc', + 'win/object_watcher_unittest.cc', + 'win/pe_image_unittest.cc', + 'win/registry_unittest.cc', + 'win/sampling_profiler_unittest.cc', + 'win/scoped_bstr_unittest.cc', + 'win/scoped_comptr_unittest.cc', + 'win/scoped_process_information_unittest.cc', + 'win/shortcut_unittest.cc', + 'win/startup_information_unittest.cc', + 'win/scoped_variant_unittest.cc', + 'win/win_util_unittest.cc', + 'win/wrapped_window_proc_unittest.cc', + ], + 'dependencies': [ + 'base', + 'base_i18n', + 'base_static', + 'run_all_unittests', + 'test_support_base', + 'third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '../testing/gmock.gyp:gmock', + '../testing/gtest.gyp:gtest', + '../third_party/icu/icu.gyp:icui18n', + '../third_party/icu/icu.gyp:icuuc', + ], + 'includes': ['../build/nocompile.gypi'], + 'variables': { + # TODO(ajwong): Is there a way to autodetect this? + 'module_dir': 'base' + }, + 'conditions': [ + ['OS == "android"', { + 'dependencies': [ + 'android/jni_generator/jni_generator.gyp:jni_generator_tests', + ], + 'conditions': [ + ['gtest_target_type == "shared_library"', { + 'dependencies': [ + '../testing/android/native_test.gyp:native_test_native_code', + ], + }], + ], + 'sources!': [ + # Broken on Android, and already disabled there. + 'debug/stack_trace_unittest.cc', + ], + }], + ['OS == "ios"', { + 'sources/': [ + # Only test the iOS-meaningful portion of process_utils. + ['exclude', '^process_util_unittest'], + ['include', '^process_util_unittest_ios\\.cc$'], + # Requires spawning processes. + ['exclude', '^metrics/stats_table_unittest\\.cc$'], + # iOS does not use message_pump_libevent. + ['exclude', '^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', + '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', + 'variables': { + 'test_data_files': [ + 'data/json/bom_feff.json', + 'data/file_util_unittest', + ], + 'test_data_prefix': 'base', + }, + 'includes': [ '../build/copy_test_data_ios.gypi' ], + }, + ], + }], + ['cobalt==1', { + 'actions': [ + { + 'action_name': 'copy_test_data', + 'variables': { + 'input_files': [ + 'data/json', + 'data/file_util_unittest', + ], + 'output_dir': 'base/data', + }, + 'includes': [ '../cobalt/build/copy_test_data.gypi' ], + }, + ], + }], + ['use_glib==1', { + 'sources!': [ + 'file_version_info_unittest.cc', + ], + 'conditions': [ + [ 'linux_use_tcmalloc==1', { + 'dependencies': [ + 'allocator/allocator.gyp:allocator', + ], + }, + ], + [ 'toolkit_uses_gtk==1', { + 'sources': [ + 'nix/xdg_util_unittest.cc', + ], + 'dependencies': [ + '../build/linux/system.gyp:gtk', + ] + }], + ], + 'dependencies': [ + '../build/linux/system.gyp:glib', + '../build/linux/system.gyp:ssl', + '../tools/xdisplaycheck/xdisplaycheck.gyp:xdisplaycheck', + ], + }, { # use_glib!=1 + 'sources!': [ + 'message_pump_glib_unittest.cc', + ] + }], + # This is needed to trigger the dll copy step on windows. + # TODO(mark): This should not be necessary. + ['OS == "win"', { + 'dependencies': [ + '../third_party/icu/icu.gyp:icudata', + ], + 'sources!': [ + 'file_descriptor_shuffle_unittest.cc', + 'files/dir_reader_posix_unittest.cc', + 'threading/worker_pool_posix_unittest.cc', + 'message_pump_libevent_unittest.cc', + ], + }, { # OS != "win" + 'dependencies': [ + '../third_party/libevent/libevent.gyp:libevent' + ], + 'sources/': [ + ['exclude', '^win/'], + ], + 'sources!': [ + 'debug/trace_event_win_unittest.cc', + 'time_win_unittest.cc', + 'win/win_util_unittest.cc', + ], + }], + ['OS == "lb_shell" or OS == "starboard"', { + 'sources!': [ + 'environment_unittest.cc', + 'files/important_file_writer_unittest.cc', + # We don't use field trials (an experiments framework) in Cobalt, + # and these tests depend on the current date being set correctly, + # so do not run them. + 'metrics/field_trial_unittest.cc', + 'metrics/stats_table_unittest.cc', + 'process_util_unittest.cc', + 'scoped_native_library_unittest.cc', + 'shared_memory_unittest.cc', + 'synchronization/waitable_event_watcher_unittest.cc', + ], + 'sources': [ + 'circular_buffer_shell_unittest.cc', + 'optional_unittest.cc', + 'state_machine_shell_unittest.cc', + ], + 'conditions': [ + ['target_arch != "android" or OS == "starboard"', { + 'dependencies!': [ + '../third_party/libevent/libevent.gyp:libevent' + ], + 'sources!': [ + 'message_pump_libevent_unittest.cc', + ], + }], + ], + }], + ], # conditions + 'target_conditions': [ + ['OS == "ios"', { + 'sources/': [ + # Pull in specific Mac files for iOS (which have been filtered out + # by file name rules). + ['include', '^mac/objc_property_releaser_unittest\\.mm$'], + ['include', '^mac/bind_objc_block_unittest\\.mm$'], + ['include', '^sys_string_conversions_mac_unittest\\.mm$'], + ], + }], + ], # target_conditions + 'msvs_disabled_warnings': [ + 4800, # forcing value to bool 'true' or 'false' (performance warning) + ], + }, + { + 'target_name': 'test_support_base', + 'type': 'static_library', + 'dependencies': [ + 'base', + 'base_static', + 'base_i18n', + '../testing/gmock.gyp:gmock', + '../testing/gtest.gyp:gtest', + ], + 'export_dependent_settings': [ + 'base', + ], + 'conditions': [ + ['cobalt==1', { + 'dependencies': [ + # Platform delegate is used to perform system specific + # initialization logic. This is a temporary solution and the code + # should eventually move into the chromium/base code. + '<(DEPTH)/cobalt/deprecated/deprecated.gyp:platform_delegate', + ], + }], + ['toolkit_uses_gtk==1', { + 'dependencies': [ + # test_suite initializes GTK. + '../build/linux/system.gyp:gtk', + ], + }], + ['os_posix==0', { + 'sources!': [ + 'test/scoped_locale.cc', + 'test/scoped_locale.h', + ], + }], + ['os_bsd==1', { + 'sources!': [ + 'test/test_file_util_linux.cc', + ], + }], + ['OS=="win"', { + 'direct_dependent_settings': { + 'msvs_settings': { + 'VCLinkerTool': { + 'DelayLoadDLLs': [ + 'propsys.dll', + ], + }, + }, + }, + }], + ], + 'sources': [ + 'perftimer.cc', + 'test/main_hook.cc', + 'test/main_hook.h', + 'test/main_hook_ios.mm', + 'test/mock_chrome_application_mac.h', + 'test/mock_chrome_application_mac.mm', + 'test/mock_devices_changed_observer.cc', + 'test/mock_devices_changed_observer.h', + 'test/mock_time_provider.cc', + 'test/mock_time_provider.h', + 'test/multiprocess_test.cc', + 'test/multiprocess_test.h', + 'test/multiprocess_test_android.cc', + 'test/perf_test_suite.cc', + 'test/perf_test_suite.h', + 'test/scoped_locale.cc', + 'test/scoped_locale.h', + 'test/scoped_path_override.cc', + 'test/scoped_path_override.h', + 'test/sequenced_task_runner_test_template.cc', + 'test/sequenced_task_runner_test_template.h', + 'test/task_runner_test_template.cc', + 'test/task_runner_test_template.h', + 'test/test_file_util.h', + '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_listener_ios.h', + 'test/test_listener_ios.mm', + 'test/test_reg_util_win.cc', + 'test/test_reg_util_win.h', + 'test/test_shortcut_win.cc', + 'test/test_shortcut_win.h', + 'test/test_suite.cc', + 'test/test_suite.h', + 'test/test_support_android.cc', + 'test/test_support_android.h', + 'test/test_support_ios.h', + 'test/test_support_ios.mm', + 'test/test_switches.cc', + 'test/test_switches.h', + 'test/test_timeouts.cc', + 'test/test_timeouts.h', + 'test/thread_test_helper.cc', + 'test/thread_test_helper.h', + 'test/trace_event_analyzer.cc', + 'test/trace_event_analyzer.h', + 'test/values_test_util.cc', + 'test/values_test_util.h', + ], + 'target_conditions': [ + ['OS == "ios"', { + 'sources/': [ + # Pull in specific Mac files for iOS (which have been filtered out + # by file name rules). + ['include', '^test/test_file_util_mac\\.cc$'], + ], + }], + ['OS == "lb_shell" or OS=="starboard"', { + 'sources/': [ + ['exclude', 'test/multiprocess_test'], + ], + 'sources!': [ + 'perftimer.cc', + ], + 'sources': [ + 'perftimer_starboard.cc', + ], + }], + ], # target_conditions + }, + { + 'target_name': 'test_support_perf', + 'type': 'static_library', + 'dependencies': [ + 'base', + '../testing/gtest.gyp:gtest', + ], + 'sources': [ + 'perftimer.cc', + 'test/run_all_perftests.cc', + ], + 'direct_dependent_settings': { + 'defines': [ + 'PERF_TEST', + ], + }, + 'conditions': [ + ['toolkit_uses_gtk==1', { + 'dependencies': [ + # Needed to handle the #include chain: + # base/test/perf_test_suite.h + # base/test/test_suite.h + # gtk/gtk.h + '../build/linux/system.gyp:gtk', + ], + }], + ['OS=="starboard"', { + 'sources!': [ + 'perftimer.cc', + ], + 'sources': [ + 'perftimer_starboard.cc', + ], + }], + ], + }, + ], + 'conditions': [ + ['OS!="ios"', { + 'targets': [ + { + 'target_name': 'check_example', + 'type': 'executable', + 'sources': [ + 'check_example.cc', + ], + 'dependencies': [ + 'base', + ], + }, + ], + }], + ['OS == "win"', { + 'targets': [ + { + 'target_name': 'base_nacl_win64', + 'type': '<(component)', + 'variables': { + 'base_target': 1, + }, + 'dependencies': [ + 'base_static_win64', + 'allocator/allocator.gyp:allocator_extension_thunks_win64', + 'third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations_win64', + ], + # TODO(gregoryd): direct_dependent_settings should be shared with the + # 32-bit target, but it doesn't work due to a bug in gyp + 'direct_dependent_settings': { + 'include_dirs': [ + '..', + ], + }, + 'defines': [ + '<@(nacl_win64_defines)', + ], + 'sources!': [ + # base64.cc depends on modp_b64. + 'base64.cc', + ], + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + 'conditions': [ + ['component == "shared_library"', { + 'sources!': [ + 'debug/debug_on_start_win.cc', + ], + }], + ], + }, + { + 'target_name': 'base_i18n_nacl_win64', + 'type': '<(component)', + # TODO(gregoryd): direct_dependent_settings should be shared with the + # 32-bit target, but it doesn't work due to a bug in gyp + 'direct_dependent_settings': { + 'include_dirs': [ + '..', + ], + }, + 'defines': [ + '<@(nacl_win64_defines)', + 'BASE_I18N_IMPLEMENTATION', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'i18n/icu_util_nacl_win64.cc', + ], + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + }, + ], + }], + ['os_posix==1 and OS!="mac" and OS!="ios" and (OS!="lb_shell" or target_arch in ("android", "linux"))', { + 'targets': [ + { + 'target_name': 'symbolize', + 'type': 'static_library', + 'toolsets': ['host', 'target'], + 'variables': { + 'chromium_code': 0, + }, + 'conditions': [ + ['OS == "solaris"', { + 'include_dirs': [ + '/usr/gnu/include', + '/usr/gnu/include/libelf', + ], + },], + ], + 'cflags': [ + '-Wno-sign-compare', + ], + 'cflags!': [ + '-Wextra', + ], + 'sources': [ + 'third_party/symbolize/config.h', + 'third_party/symbolize/demangle.cc', + 'third_party/symbolize/demangle.h', + 'third_party/symbolize/glog/logging.h', + 'third_party/symbolize/glog/raw_logging.h', + 'third_party/symbolize/symbolize.cc', + 'third_party/symbolize/symbolize.h', + 'third_party/symbolize/utilities.h', + ], + 'include_dirs': [ + '..', + ], + }, + { + 'target_name': 'xdg_mime', + 'type': 'static_library', + 'toolsets': ['host', 'target'], + 'variables': { + 'chromium_code': 0, + }, + 'cflags!': [ + '-Wextra', + ], + 'sources': [ + 'third_party/xdg_mime/xdgmime.c', + 'third_party/xdg_mime/xdgmime.h', + 'third_party/xdg_mime/xdgmimealias.c', + 'third_party/xdg_mime/xdgmimealias.h', + 'third_party/xdg_mime/xdgmimecache.c', + 'third_party/xdg_mime/xdgmimecache.h', + 'third_party/xdg_mime/xdgmimeglob.c', + 'third_party/xdg_mime/xdgmimeglob.h', + 'third_party/xdg_mime/xdgmimeicon.c', + 'third_party/xdg_mime/xdgmimeicon.h', + 'third_party/xdg_mime/xdgmimeint.c', + 'third_party/xdg_mime/xdgmimeint.h', + 'third_party/xdg_mime/xdgmimemagic.c', + 'third_party/xdg_mime/xdgmimemagic.h', + 'third_party/xdg_mime/xdgmimeparent.c', + 'third_party/xdg_mime/xdgmimeparent.h', + ], + }, + ], + }], + ['OS == "android" or ((OS == "lb_shell" or OS == "starboard") and target_arch == "android")', { + 'targets': [ + { + 'target_name': 'base_jni_headers', + 'type': 'none', + 'sources': [ + 'android/java/src/org/chromium/base/BuildInfo.java', + 'android/java/src/org/chromium/base/CpuFeatures.java', + 'android/java/src/org/chromium/base/LocaleUtils.java', + 'android/java/src/org/chromium/base/PathService.java', + 'android/java/src/org/chromium/base/PathUtils.java', + 'android/java/src/org/chromium/base/SystemMessageHandler.java', + 'android/java/src/org/chromium/base/SystemMonitor.java', + ], + 'variables': { + 'jni_gen_dir': 'base', + }, + 'includes': [ '../build/jni_generator.gypi' ], + }, + { + 'target_name': 'base_java', + 'type': 'none', + 'variables': { + 'package_name': 'base', + 'java_in_dir': '../base/android/java', + }, + 'includes': [ '../build/java.gypi' ], + }, + { + 'target_name': 'base_java_test_support', + 'type': 'none', + 'dependencies': [ + 'base_java', + ], + 'variables': { + 'package_name': 'base_javatests', + 'java_in_dir': '../base/test/android/javatests', + }, + 'includes': [ '../build/java.gypi' ], + }, + ], + }], + ['OS == "win"', { + 'targets': [ + { + 'target_name': 'debug_message', + 'type': 'executable', + 'sources': [ + 'debug_message.cc', + ], + 'msvs_settings': { + 'VCLinkerTool': { + 'SubSystem': '2', # Set /SUBSYSTEM:WINDOWS + }, + }, + }, + ], + }], + ['cobalt==1', { + 'targets': [ + { + 'target_name': 'base_unittests_deploy', + 'type': 'none', + 'dependencies': [ + 'base_unittests', + ], + 'variables': { + 'executable_name': 'base_unittests', + }, + 'includes': [ '../cobalt/build/deploy.gypi' ], + }, + ], + }], + # Special target to wrap a gtest_target_type == shared_library + # base_unittests into an android apk for execution. + # TODO(jrg): lib.target comes from _InstallableTargetInstallPath() + # in the gyp make generator. What is the correct way to extract + # this path from gyp and into 'raw' for input to antfiles? + # Hard-coding in the gypfile seems a poor choice. + ['(OS == "android" and gtest_target_type == "shared_library") or ((OS == "lb_shell" or OS == "starboard") and target_arch == "android")', { + 'targets': [ + { + 'target_name': 'base_unittests_apk', + 'type': 'none', + 'dependencies': [ + 'base_java', + 'base_unittests', + ], + 'variables': { + 'test_suite_name': 'base_unittests', + 'input_shlib_path': '<(SHARED_LIB_DIR)/<(SHARED_LIB_PREFIX)base_unittests<(SHARED_LIB_SUFFIX)', + }, + 'includes': [ '../build/apk_test.gypi' ], + }, + ], + }], + ['test_isolation_mode != "noop"', { + 'targets': [ + { + 'target_name': 'base_unittests_run', + 'type': 'none', + 'dependencies': [ + 'base_unittests', + ], + 'includes': [ + '../build/isolate.gypi', + 'base_unittests.isolate', + ], + 'sources': [ + 'base_unittests.isolate', + ], + }, + ], + }], + ], +}
diff --git a/src/base/base.gypi b/src/base/base.gypi new file mode 100644 index 0000000..fde1a96 --- /dev/null +++ b/src/base/base.gypi
@@ -0,0 +1,858 @@ +# 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. + +{ + 'target_defaults': { + 'variables': { + 'base_target': 0, + }, + 'target_conditions': [ + # This part is shared between the targets defined below. + ['base_target==1', { + 'sources': [ + '../build/build_config.h', + 'third_party/dmg_fp/dmg_fp.h', + 'third_party/dmg_fp/g_fmt.cc', + 'third_party/dmg_fp/dtoa_wrapper.cc', + 'third_party/icu/icu_utf.cc', + 'third_party/icu/icu_utf.h', + 'third_party/nspr/prtime.cc', + 'third_party/nspr/prtime.h', + 'third_party/nspr/prcpucfg_linux.h', + 'third_party/xdg_mime/xdgmime.h', + 'allocator/allocator_extension.cc', + 'allocator/allocator_extension.h', + 'allocator/type_profiler_control.cc', + 'allocator/type_profiler_control.h', + 'android/base_jni_registrar.cc', + 'android/base_jni_registrar.h', + 'android/build_info.cc', + 'android/build_info.h', + 'android/cpu_features.cc', + 'android/scoped_java_ref.cc', + 'android/scoped_java_ref.h', + 'android/jni_android.cc', + 'android/jni_android.h', + 'android/jni_array.cc', + 'android/jni_array.h', + 'android/jni_helper.cc', + 'android/jni_helper.h', + 'android/jni_registrar.cc', + 'android/jni_registrar.h', + 'android/jni_string.cc', + 'android/jni_string.h', + 'android/locale_utils.cc', + 'android/locale_utils.h', + 'android/path_service_android.cc', + 'android/path_service_android.h', + 'android/path_utils.cc', + 'android/path_utils.h', + 'at_exit.cc', + 'at_exit.h', + 'atomic_ref_count.h', + 'atomic_sequence_num.h', + 'atomicops.h', + 'atomicops_internals_gcc.h', + 'atomicops_internals_mac.h', + 'atomicops_internals_tsan.h', + 'atomicops_internals_x86_gcc.cc', + 'atomicops_internals_x86_gcc.h', + 'atomicops_internals_x86_msvc.h', + 'base_export.h', + 'base_paths.cc', + 'base_paths.h', + 'base_paths_android.cc', + 'base_paths_android.h', + 'base_paths_mac.h', + 'base_paths_mac.mm', + 'base_paths_posix.cc', + 'base_paths_posix.h', + 'base_paths_win.cc', + 'base_paths_win.h', + 'base_switches.h', + 'base64.cc', + 'base64.h', + 'basictypes.h', + 'bind.h', + 'bind_helpers.cc', + 'bind_helpers.h', + 'bind_internal.h', + 'bind_internal_functor.h', + 'bind_internal_win.h', + 'bits.h', + 'build_time.cc', + 'build_time.h', + 'callback.h', + 'callback_helpers.h', + 'callback_internal.cc', + 'callback_internal.h', + 'cancelable_callback.h', + 'chromeos/chromeos_version.cc', + 'chromeos/chromeos_version.h', + 'command_line.cc', + 'command_line.h', + 'compiler_specific.h', + 'containers/linked_hash_map.h', + 'containers/linked_list.h', + 'containers/mru_cache.h', + 'containers/small_map.h', + 'containers/stack_container.h', + 'cpu.cc', + 'cpu.h', + 'critical_closure.h', + 'critical_closure_ios.mm', + 'debug/alias.cc', + 'debug/alias.h', + 'debug/debug_on_start_win.cc', + 'debug/debug_on_start_win.h', + 'debug/debugger.cc', + 'debug/debugger.h', + 'debug/debugger_posix.cc', + 'debug/debugger_win.cc', + # This file depends on files from the 'allocator' target, + # but this target does not depend on 'allocator' (see + # allocator.gyp for details). + 'debug/leak_annotations.h', + 'debug/leak_tracker.h', + 'debug/profiler.cc', + 'debug/profiler.h', + 'debug/stack_trace.cc', + 'debug/stack_trace.h', + 'debug/stack_trace_android.cc', + 'debug/stack_trace_ios.mm', + 'debug/stack_trace_posix.cc', + 'debug/stack_trace_win.cc', + 'debug/trace_event.cc', + 'debug/trace_event.h', + 'debug/trace_event_android.cc', + 'debug/trace_event_impl.cc', + 'debug/trace_event_impl.h', + 'debug/trace_event_win.cc', + 'environment.cc', + 'environment.h', + 'file_descriptor_posix.h', + 'file_path.cc', + 'file_path.h', + 'file_util.cc', + 'file_util.h', + 'file_util_android.cc', + 'file_util_linux.cc', + 'file_util_mac.mm', + 'file_util_posix.cc', + 'file_util_win.cc', + 'file_util_proxy.cc', + 'file_util_proxy.h', + 'file_version_info.h', + 'file_version_info_mac.h', + 'file_version_info_mac.mm', + 'file_version_info_win.cc', + 'file_version_info_win.h', + 'files/dir_reader_fallback.h', + 'files/dir_reader_dirent.h', + 'files/dir_reader_linux.h', + 'files/dir_reader_posix.h', + 'files/file_path_watcher.cc', + 'files/file_path_watcher.h', + 'files/file_path_watcher_kqueue.cc', + 'files/file_path_watcher_linux.cc', + 'files/file_path_watcher_stub.cc', + 'files/file_path_watcher_win.cc', + 'files/important_file_writer.h', + 'files/important_file_writer.cc', + 'files/scoped_temp_dir.cc', + 'files/scoped_temp_dir.h', + 'float_util.h', + 'format_macros.h', + 'gtest_prod_util.h', + 'guid.cc', + 'guid.h', + 'guid_posix.cc', + 'guid_win.cc', + 'hash.cc', + 'hash.h', + 'hash_tables.h', + 'hi_res_timer_manager_posix.cc', + 'hi_res_timer_manager_win.cc', + 'hi_res_timer_manager.h', + 'id_map.h', + 'ios/device_util.h', + 'ios/device_util.mm', + 'ios/ios_util.h', + 'ios/ios_util.mm', + 'ios/scoped_critical_action.h', + 'ios/scoped_critical_action.mm', + 'json/json_file_value_serializer.cc', + 'json/json_file_value_serializer.h', + 'json/json_parser.cc', + 'json/json_parser.h', + 'json/json_reader.cc', + 'json/json_reader.h', + 'json/json_string_value_serializer.cc', + 'json/json_string_value_serializer.h', + 'json/json_value_converter.h', + 'json/json_writer.cc', + 'json/json_writer.h', + 'json/string_escape.cc', + 'json/string_escape.h', + 'lazy_instance.cc', + 'lazy_instance.h', + 'location.cc', + 'location.h', + 'logging.cc', + 'logging.h', + 'logging_win.cc', + 'logging_win.h', + 'mac/authorization_util.h', + 'mac/authorization_util.mm', + 'mac/bind_objc_block.h', + 'mac/bind_objc_block.mm', + 'mac/bundle_locations.h', + 'mac/bundle_locations.mm', + 'mac/cocoa_protocols.h', + 'mac/crash_logging.h', + 'mac/crash_logging.mm', + 'mac/foundation_util.h', + 'mac/foundation_util.mm', + 'mac/launchd.cc', + 'mac/launchd.h', + 'mac/libdispatch_task_runner.cc', + 'mac/libdispatch_task_runner.h', + 'mac/mac_logging.h', + 'mac/mac_logging.cc', + 'mac/mac_util.h', + 'mac/mac_util.mm', + 'mac/objc_property_releaser.h', + 'mac/objc_property_releaser.mm', + 'mac/os_crash_dumps.cc', + 'mac/os_crash_dumps.h', + 'mac/scoped_aedesc.h', + 'mac/scoped_authorizationref.h', + 'mac/scoped_cftyperef.h', + 'mac/scoped_ioobject.h', + 'mac/scoped_launch_data.h', + 'mac/scoped_mach_port.cc', + 'mac/scoped_mach_port.h', + 'mac/scoped_nsautorelease_pool.h', + 'mac/scoped_nsautorelease_pool.mm', + 'mac/scoped_nsexception_enabler.h', + 'mac/scoped_nsexception_enabler.mm', + 'mac/scoped_sending_event.h', + 'mac/scoped_sending_event.mm', + 'mach_ipc_mac.h', + 'mach_ipc_mac.mm', + 'memory/aligned_memory.cc', + 'memory/aligned_memory.h', + 'memory/linked_ptr.h', + 'memory/manual_constructor.h', + 'memory/raw_scoped_refptr_mismatch_checker.h', + 'memory/ref_counted.cc', + 'memory/ref_counted.h', + 'memory/ref_counted_memory.cc', + 'memory/ref_counted_memory.h', + 'memory/scoped_handle.h', + 'memory/scoped_nsobject.h', + 'memory/scoped_open_process.h', + 'memory/scoped_policy.h', + 'memory/scoped_ptr.h', + 'memory/scoped_vector.h', + 'memory/singleton.cc', + 'memory/singleton.h', + 'memory/weak_ptr.cc', + 'memory/weak_ptr.h', + 'message_loop.cc', + 'message_loop.h', + 'message_loop_proxy.cc', + 'message_loop_proxy.h', + 'message_loop_proxy_impl.cc', + 'message_loop_proxy_impl.h', + 'message_pump.cc', + 'message_pump.h', + 'message_pump_android.cc', + 'message_pump_android.h', + 'message_pump_default.cc', + 'message_pump_default.h', + 'message_pump_win.cc', + 'message_pump_win.h', + 'metrics/sample_map.cc', + 'metrics/sample_map.h', + 'metrics/sample_vector.cc', + 'metrics/sample_vector.h', + 'metrics/bucket_ranges.cc', + 'metrics/bucket_ranges.h', + 'metrics/histogram.cc', + 'metrics/histogram.h', + 'metrics/histogram_base.cc', + 'metrics/histogram_base.h', + 'metrics/histogram_flattener.h', + 'metrics/histogram_samples.cc', + 'metrics/histogram_samples.h', + 'metrics/histogram_snapshot_manager.cc', + 'metrics/histogram_snapshot_manager.h', + 'metrics/sparse_histogram.cc', + 'metrics/sparse_histogram.h', + 'metrics/statistics_recorder.cc', + 'metrics/statistics_recorder.h', + 'metrics/stats_counters.cc', + 'metrics/stats_counters.h', + 'metrics/stats_table.cc', + 'metrics/stats_table.h', + 'move.h', + 'native_library.h', + 'native_library_mac.mm', + 'native_library_posix.cc', + 'native_library_win.cc', + 'observer_list.h', + 'observer_list_threadsafe.h', + 'os_compat_android.cc', + 'os_compat_android.h', + 'os_compat_nacl.cc', + 'os_compat_nacl.h', + 'path_service.cc', + 'path_service.h', + 'pending_task.cc', + 'pending_task.h', + 'pickle.cc', + 'pickle.h', + 'platform_file.cc', + 'platform_file.h', + 'platform_file_posix.cc', + 'platform_file_win.cc', + 'port.h', + 'posix/eintr_wrapper.h', + 'posix/global_descriptors.cc', + 'posix/global_descriptors.h', + 'posix/unix_domain_socket.cc', + 'posix/unix_domain_socket.h', + 'process.h', + 'process_info.h', + 'process_info_mac.cc', + 'process_info_win.cc', + 'process_linux.cc', + 'process_posix.cc', + 'process_util.cc', + 'process_util.h', + 'process_util_freebsd.cc', + 'process_util_ios.mm', + 'process_util_linux.cc', + 'process_util_mac.mm', + 'process_util_openbsd.cc', + 'process_util_posix.cc', + 'process_util_win.cc', + 'process_win.cc', + 'profiler/scoped_profile.cc', + 'profiler/scoped_profile.h', + 'profiler/alternate_timer.cc', + 'profiler/alternate_timer.h', + 'profiler/tracked_time.cc', + 'profiler/tracked_time.h', + 'rand_util.cc', + 'rand_util.h', + 'rand_util_nacl.cc', + 'rand_util_posix.cc', + 'rand_util_win.cc', + 'run_loop.cc', + 'run_loop.h', + 'safe_strerror_posix.cc', + 'safe_strerror_posix.h', + 'scoped_native_library.cc', + 'scoped_native_library.h', + 'sequenced_task_runner.cc', + 'sequenced_task_runner.h', + 'sequenced_task_runner_helpers.h', + 'sha1.h', + 'sha1_portable.cc', + 'sha1_win.cc', + 'shared_memory.h', + 'shared_memory_android.cc', + 'shared_memory_nacl.cc', + 'shared_memory_posix.cc', + 'shared_memory_win.cc', + 'single_thread_task_runner.h', + 'stl_util.h', + 'string_number_conversions.cc', + 'string_number_conversions.h', + 'string_piece.cc', + 'string_piece.h', + 'string_split.cc', + 'string_split.h', + 'string_tokenizer.h', + 'string_util.cc', + 'string_util.h', + 'string_util_posix.h', + 'string_util_win.h', + 'string16.cc', + 'string16.h', + 'stringize_macros.h', + 'stringprintf.cc', + 'stringprintf.h', + 'supports_user_data.cc', + 'supports_user_data.h', + 'synchronization/cancellation_flag.cc', + 'synchronization/cancellation_flag.h', + 'synchronization/condition_variable.h', + 'synchronization/condition_variable_posix.cc', + 'synchronization/condition_variable_win.cc', + 'synchronization/lock.cc', + 'synchronization/lock.h', + 'synchronization/lock_impl.h', + 'synchronization/lock_impl_posix.cc', + 'synchronization/lock_impl_win.cc', + 'synchronization/waitable_event.h', + 'synchronization/waitable_event_posix.cc', + 'synchronization/waitable_event_watcher.h', + 'synchronization/waitable_event_watcher_posix.cc', + 'synchronization/waitable_event_watcher_win.cc', + 'synchronization/waitable_event_win.cc', + 'system_monitor/system_monitor.cc', + 'system_monitor/system_monitor.h', + 'system_monitor/system_monitor_android.cc', + 'system_monitor/system_monitor_android.h', + 'system_monitor/system_monitor_ios.mm', + 'system_monitor/system_monitor_mac.mm', + 'system_monitor/system_monitor_posix.cc', + 'system_monitor/system_monitor_win.cc', + 'sys_byteorder.h', + 'sys_info.cc', + 'sys_info.h', + 'sys_info_android.cc', + 'sys_info_chromeos.cc', + 'sys_info_freebsd.cc', + 'sys_info_ios.mm', + 'sys_info_linux.cc', + 'sys_info_mac.cc', + 'sys_info_openbsd.cc', + 'sys_info_posix.cc', + 'sys_info_win.cc', + 'sys_string_conversions.h', + 'sys_string_conversions_mac.mm', + 'sys_string_conversions_posix.cc', + 'sys_string_conversions_win.cc', + 'task_runner.cc', + 'task_runner.h', + 'task_runner_util.h', + 'template_util.h', + 'thread_task_runner_handle.cc', + 'thread_task_runner_handle.h', + 'threading/non_thread_safe.h', + 'threading/non_thread_safe_impl.cc', + 'threading/non_thread_safe_impl.h', + 'threading/platform_thread.h', + 'threading/platform_thread_mac.mm', + 'threading/platform_thread_posix.cc', + 'threading/platform_thread_win.cc', + 'threading/post_task_and_reply_impl.cc', + 'threading/post_task_and_reply_impl.h', + 'threading/sequenced_worker_pool.cc', + 'threading/sequenced_worker_pool.h', + 'threading/simple_thread.cc', + 'threading/simple_thread.h', + 'threading/thread.cc', + 'threading/thread.h', + 'threading/thread_checker.h', + 'threading/thread_checker_impl.cc', + 'threading/thread_checker_impl.h', + 'threading/thread_collision_warner.cc', + 'threading/thread_collision_warner.h', + 'threading/thread_local.h', + 'threading/thread_local_posix.cc', + 'threading/thread_local_storage.h', + 'threading/thread_local_storage_posix.cc', + 'threading/thread_local_storage_win.cc', + 'threading/thread_local_win.cc', + 'threading/thread_restrictions.h', + 'threading/thread_restrictions.cc', + 'threading/watchdog.cc', + 'threading/watchdog.h', + 'threading/worker_pool.h', + 'threading/worker_pool.cc', + 'threading/worker_pool_posix.cc', + 'threading/worker_pool_posix.h', + 'threading/worker_pool_win.cc', + 'time.cc', + 'time.h', + 'time_mac.cc', + 'time_posix.cc', + 'time_win.cc', + 'timer.cc', + 'timer.h', + 'tracked_objects.cc', + 'tracked_objects.h', + 'tracking_info.cc', + 'tracking_info.h', + 'tuple.h', + 'utf_offset_string_conversions.cc', + 'utf_offset_string_conversions.h', + 'utf_string_conversion_utils.cc', + 'utf_string_conversion_utils.h', + 'utf_string_conversions.cc', + 'utf_string_conversions.h', + 'values.cc', + 'values.h', + 'value_conversions.cc', + 'value_conversions.h', + 'version.cc', + 'version.h', + 'vlog.cc', + 'vlog.h', + 'nix/mime_util_xdg.cc', + 'nix/mime_util_xdg.h', + 'nix/xdg_util.cc', + 'nix/xdg_util.h', + 'win/enum_variant.h', + 'win/enum_variant.cc', + 'win/event_trace_consumer.h', + 'win/event_trace_controller.cc', + 'win/event_trace_controller.h', + 'win/event_trace_provider.cc', + 'win/event_trace_provider.h', + 'win/i18n.cc', + 'win/i18n.h', + 'win/iat_patch_function.cc', + 'win/iat_patch_function.h', + 'win/iunknown_impl.h', + 'win/iunknown_impl.cc', + 'win/metro.cc', + 'win/metro.h', + 'win/object_watcher.cc', + 'win/object_watcher.h', + 'win/registry.cc', + 'win/registry.h', + 'win/resource_util.cc', + 'win/resource_util.h', + 'win/sampling_profiler.cc', + 'win/sampling_profiler.h', + 'win/scoped_bstr.cc', + 'win/scoped_bstr.h', + 'win/scoped_co_mem.h', + 'win/scoped_com_initializer.h', + 'win/scoped_comptr.h', + 'win/scoped_gdi_object.h', + 'win/scoped_handle.cc', + 'win/scoped_handle.h', + 'win/scoped_hdc.h', + 'win/scoped_hglobal.h', + 'win/scoped_process_information.cc', + 'win/scoped_process_information.h', + 'win/scoped_select_object.h', + 'win/shortcut.cc', + 'win/shortcut.h', + 'win/startup_information.cc', + 'win/startup_information.h', + 'win/scoped_variant.cc', + 'win/scoped_variant.h', + 'win/text_services_message_filter.cc', + 'win/text_services_message_filter.h', + 'win/windows_version.cc', + 'win/windows_version.h', + 'win/win_util.cc', + 'win/win_util.h', + 'win/wrapped_window_proc.cc', + 'win/wrapped_window_proc.h', + ], + 'defines': [ + 'BASE_IMPLEMENTATION', + ], + 'include_dirs': [ + '..', + ], + 'msvs_disabled_warnings': [ + 4018, + ], + 'conditions': [ + ['OS=="lb_shell"', { + 'sources': [ + 'base_paths_shell.cc', + 'circular_buffer_shell.h', + 'circular_buffer_shell.cc', + 'message_pump_shell.cc', + 'message_pump_shell.h', + 'native_library_shell.cc', # Stub implementation + 'object_watcher_shell.cc', + 'object_watcher_shell.h', + 'optional.cc', + 'optional.h', + 'optional_internal.h', + 'shared_memory_shell.cc', # Stub implementation + 'state_machine_shell.h', + 'state_machine_shell.cc', + 'synchronization/condition_variable_shell.cc', + 'synchronization/lock_impl_shell.cc', + 'synchronization/waitable_event_shell.cc', + 'threading/thread_checker_impl_atomic.cc', + '<!@(find <(lbshell_root)/src/platform/<(target_arch)/chromium/base -type f)', + ], + 'sources!': [ + 'threading/thread_checker_impl.cc', + ], + 'sources/': [ + ['include', 'sys_string_conversions_linux.cc'], + ], + }], + [ 'OS=="lb_shell" and target_arch!="linux" and target_arch!="android"', { + 'sources!': [ + 'string16.cc', # wchar_t is 2-bytes wide, string16 == wstring here. + ], + }], + [ 'OS=="lb_shell" and target_arch=="android"', { + 'sources/' : [ + ['exclude', 'message_pump_shell.cc'], + ['exclude', 'message_pump_shell.h'], + ['exclude', 'native_library_shell.cc'], + ['exclude', 'process_util.cc'], + ['exclude', '<(lbshell_root)/src/object_watcher_shell.cc'], + ['exclude', '<(lbshell_root)/src/object_watcher_shell.h'], + ['include', 'message_pump_libevent.cc'], + ['include', 'message_pump_libevent.h'], + ['include', 'native_library_posix.cc'], + ], + }], + # For Starboard, we are going to build this back up as necessary. + ['OS=="starboard"', { + 'sources': [ + 'base_paths_starboard.cc', + 'circular_buffer_shell.cc', + 'circular_buffer_shell.h', + 'debug/debugger_starboard.cc', + 'debug/stack_trace_starboard.cc', + 'file_util_starboard.cc', + 'guid_starboard.cc', + 'message_pump_io_starboard.cc', + 'message_pump_io_starboard.h', + 'message_pump_ui_starboard.cc', + 'message_pump_ui_starboard.h', + 'optional.cc', + 'optional.h', + 'optional_internal.h', + 'platform_file_starboard.cc', + 'process_starboard.cc', + 'process_util_starboard.cc', + 'rand_util_starboard.cc', + 'state_machine_shell.cc', + 'state_machine_shell.h', + 'synchronization/condition_variable_starboard.cc', + 'synchronization/lock_impl_starboard.cc', + 'synchronization/waitable_event_starboard.cc', + 'sys_info_starboard.cc', + 'sys_string_conversions_starboard.cc', + 'system_monitor/system_monitor_starboard.cc', + 'threading/platform_thread_starboard.cc', + 'threading/thread_checker_impl_atomic.cc', + 'threading/thread_local_starboard.cc', + 'threading/thread_local_storage_starboard.cc', + 'threading/worker_pool_starboard.cc', + 'time_starboard.cc', + ], + 'sources!': [ + # Uses file_util::ReplaceFile, which isn't implemented in + # Starboard, and isn't otherwise used in Cobalt. + 'files/important_file_writer.cc', + 'files/important_file_writer.h', + + # All the common functions have been removed for Starboard, so we + # don't even want to compile this. + 'process_util.cc', + + # Tricky to support cross-platform, and not used by Cobalt. + 'scoped_native_library.cc', + 'scoped_native_library.h', + + # We use thread_checker_impl_atomic.cc instead. + 'threading/thread_checker_impl.cc', + ], + 'conditions': [ + ['target_os!="linux" and target_arch!="android"', { + 'sources!': [ + # Since wchar_t is 2-bytes wide, string16 == wstring here. + 'string16.cc', + ], + }], + ], + }], # OS == "starboard" + [ 'OS=="lb_shell" or OS=="starboard"', { + 'sources!': [ + 'environment.cc', + 'file_descriptor_shuffle.cc', + 'files/file_path_watcher.cc', + 'files/file_path_watcher_kqueue.cc', + 'files/file_path_watcher_stub.cc', + 'message_pump_libevent.cc', + 'message_pump_libevent.h', + 'metrics/stats_table.cc', + 'metrics/stats_table.h', + ], + }], # OS == "lb_shell" or OS == "starboard" + ], + 'target_conditions': [ + ['<(use_glib)==0 or >(nacl_untrusted_build)==1', { + 'sources/': [ + ['exclude', '^nix/'], + ], + 'sources!': [ + 'atomicops_internals_x86_gcc.cc', + 'message_pump_glib.cc', + 'message_pump_aurax11.cc', + ], + }], + ['<(toolkit_uses_gtk)==0 or >(nacl_untrusted_build)==1', { + 'sources!': ['message_pump_gtk.cc'], + }], + ['(OS != "linux" and <(os_bsd) != 1 and OS != "android") or >(nacl_untrusted_build)==1', { + 'sources!': [ + # Not automatically excluded by the *linux.cc rules. + 'linux_util.cc', + ], + }, + ], + ['>(nacl_untrusted_build)==1', { + 'sources!': [ + 'allocator/type_profiler_control.cc', + 'allocator/type_profiler_control.h', + 'base_paths.cc', + 'command_line.cc', + 'cpu.cc', + 'debug/stack_trace_posix.cc', + 'file_util.cc', + 'file_util_posix.cc', + 'file_util_proxy.cc', + 'files/file_path_watcher_kqueue.cc', + 'native_library_posix.cc', + 'path_service.cc', + 'platform_file_posix.cc', + 'posix/unix_domain_socket.cc', + 'process_posix.cc', + 'process_util.cc', + 'process_util_posix.cc', + 'rand_util_posix.cc', + 'scoped_native_library.cc', + 'files/scoped_temp_dir.cc', + 'shared_memory_posix.cc', + 'sys_info_posix.cc', + 'threading/sequenced_worker_pool.cc', + 'third_party/dynamic_annotations/dynamic_annotations.c', + ], + # Metrics won't work in the NaCl sandbox. + 'sources/': [ ['exclude', '^metrics/'] ], + }], + ['OS == "android" and >(nacl_untrusted_build)==0', { + 'sources!': [ + 'base_paths_posix.cc', + 'files/file_path_watcher_kqueue.cc', + 'files/file_path_watcher_stub.cc', + 'system_monitor/system_monitor_posix.cc', + ], + 'sources/': [ + ['include', '^files/file_path_watcher_linux\\.cc$'], + ['include', '^process_util_linux\\.cc$'], + ['include', '^sys_info_linux\\.cc$'], + ['include', '^sys_string_conversions_posix\\.cc$'], + ['include', '^worker_pool_linux\\.cc$'], + ], + }], + ['OS == "ios"', { + 'sources/': [ + # Pull in specific Mac files for iOS (which have been filtered out + # by file name rules). + ['include', '^atomicops_internals_mac\\.'], + ['include', '^base_paths_mac\\.'], + ['include', '^file_util_mac\\.'], + ['include', '^file_version_info_mac\\.'], + ['include', '^mac/bind_objc_block\\.'], + ['include', '^mac/bundle_locations\\.'], + ['include', '^mac/foundation_util\\.'], + ['include', '^mac/mac_logging\\.'], + ['include', '^mac/objc_property_releaser\\.'], + ['include', '^mac/scoped_mach_port\\.'], + ['include', '^mac/scoped_nsautorelease_pool\\.'], + ['include', '^message_pump_mac\\.'], + ['include', '^threading/platform_thread_mac\\.'], + ['include', '^sys_string_conversions_mac\\.'], + ['include', '^time_mac\\.'], + ['include', '^worker_pool_mac\\.'], + # Exclude all process_util except the minimal implementation + # needed on iOS (mostly for unit tests). + ['exclude', '^process_util'], + ['include', '^process_util_ios\\.mm$'], + ], + 'sources!': [ + 'message_pump_libevent.cc' + ], + }], + ['OS != "mac" or >(nacl_untrusted_build)==1', { + 'sources!': [ + 'mac/scoped_aedesc.h' + ], + }], + # For now, just test the *BSD platforms enough to exclude them. + # Subsequent changes will include them further. + ['OS != "freebsd" or >(nacl_untrusted_build)==1', { + 'sources/': [ ['exclude', '_freebsd\\.cc$'] ], + }, + ], + ['OS != "openbsd" or >(nacl_untrusted_build)==1', { + 'sources/': [ ['exclude', '_openbsd\\.cc$'] ], + }, + ], + ['OS != "win" or >(nacl_untrusted_build)==1', { + 'sources/': [ ['exclude', '^win/'] ], + }, + ], + ['(OS != "android" or >(nacl_untrusted_build)==1) and (OS != "lb_shell" or "<(target_arch)" != "android")', { + 'sources/': [ ['exclude', '^android/'] ], + }, + ], + ['OS == "win" and >(nacl_untrusted_build)==0', { + 'include_dirs': [ + '<(DEPTH)/third_party/wtl/include', + ], + 'sources!': [ + 'event_recorder_stubs.cc', + 'files/file_path_watcher_kqueue.cc', + 'files/file_path_watcher_stub.cc', + 'message_pump_libevent.cc', + 'posix/file_descriptor_shuffle.cc', + # Not using sha1_win.cc because it may have caused a + # regression to page cycler moz. + 'sha1_win.cc', + 'string16.cc', + ], + },], + ['OS == "linux" and >(nacl_untrusted_build)==0', { + 'sources!': [ + 'files/file_path_watcher_kqueue.cc', + 'files/file_path_watcher_stub.cc', + ], + }], + ['(OS == "mac" or OS == "ios") and >(nacl_untrusted_build)==0', { + 'sources/': [ + ['exclude', '^files/file_path_watcher_stub\\.cc$'], + ['exclude', '^base_paths_posix\\.cc$'], + ['exclude', '^native_library_posix\\.cc$'], + ['exclude', '^sys_string_conversions_posix\\.cc$'], + ], + }], + ['<(os_bsd)==1 and >(nacl_untrusted_build)==0', { + 'sources/': [ + ['exclude', '^files/file_path_watcher_linux\\.cc$'], + ['exclude', '^files/file_path_watcher_stub\\.cc$'], + ['exclude', '^file_util_linux\\.cc$'], + ['exclude', '^process_linux\\.cc$'], + ['exclude', '^process_util_linux\\.cc$'], + ['exclude', '^sys_info_linux\\.cc$'], + ], + }], + ['<(chromeos)!=1 or >(nacl_untrusted_build)==1', { + 'sources/': [ + ['exclude', '^chromeos/'], + ], + }], + # Remove all unnecessary files for build_nexe.py to avoid exceeding + # command-line-string limitation when building NaCl on Windows. + ['OS == "win" and >(nacl_untrusted_build)==1', { + 'sources/': [ ['exclude', '\\.h$'] ], + }], + ], + }], + ], + }, +}
diff --git a/src/base/base64.cc b/src/base/base64.cc new file mode 100644 index 0000000..1907978 --- /dev/null +++ b/src/base/base64.cc
@@ -0,0 +1,43 @@ +// 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/base64.h" + +#include "third_party/modp_b64/modp_b64.h" + +namespace base { + +bool 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. + int output_size = modp_b64_encode(&(temp[0]), input.data(), input_size); + if (output_size < 0) + return false; + + temp.resize(output_size); // strips off null byte + output->swap(temp); + return true; +} + +bool Base64Decode(const StringPiece& input, std::string* output) { + std::string temp; + temp.resize(modp_b64_decode_len(input.size())); + + // does not null terminate result since result is binary data! + int input_size = static_cast<int>(input.size()); + int output_size = modp_b64_decode(&(temp[0]), input.data(), input_size); + if (output_size < 0) + return false; + + temp.resize(output_size); + output->swap(temp); + return true; +} + +} // namespace base
diff --git a/src/base/base64.h b/src/base/base64.h new file mode 100644 index 0000000..983d5e2 --- /dev/null +++ b/src/base/base64.h
@@ -0,0 +1,25 @@ +// 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. + +#ifndef BASE_BASE64_H__ +#define BASE_BASE64_H__ + +#include <string> + +#include "base/base_export.h" +#include "base/string_piece.h" + +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); + +// Decodes the base64 input string. Returns true if successful and false +// otherwise. The output string is only modified if successful. +BASE_EXPORT bool Base64Decode(const StringPiece& input, std::string* output); + +} // namespace base + +#endif // BASE_BASE64_H__
diff --git a/src/base/base64_unittest.cc b/src/base/base64_unittest.cc new file mode 100644 index 0000000..9a5dd81 --- /dev/null +++ b/src/base/base64_unittest.cc
@@ -0,0 +1,28 @@ +// 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/base64.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(Base64Test, Basic) { + const std::string kText = "hello world"; + const std::string kBase64Text = "aGVsbG8gd29ybGQ="; + + std::string encoded; + std::string decoded; + bool ok; + + ok = Base64Encode(kText, &encoded); + EXPECT_TRUE(ok); + EXPECT_EQ(kBase64Text, encoded); + + ok = Base64Decode(encoded, &decoded); + EXPECT_TRUE(ok); + EXPECT_EQ(kText, decoded); +} + +} // namespace base
diff --git a/src/base/base_export.h b/src/base/base_export.h new file mode 100644 index 0000000..fdfa5f9 --- /dev/null +++ b/src/base/base_export.h
@@ -0,0 +1,33 @@ +// 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_BASE_EXPORT_H_ +#define BASE_BASE_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(_MSC_VER) +#if defined(BASE_IMPLEMENTATION) +#define BASE_EXPORT __declspec(dllexport) +#define BASE_EXPORT_PRIVATE __declspec(dllexport) +#else +#define BASE_EXPORT __declspec(dllimport) +#define BASE_EXPORT_PRIVATE __declspec(dllimport) +#endif // defined(BASE_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(BASE_IMPLEMENTATION) +#define BASE_EXPORT __attribute__((visibility("default"))) +#define BASE_EXPORT_PRIVATE __attribute__((visibility("default"))) +#else +#define BASE_EXPORT +#define BASE_EXPORT_PRIVATE +#endif // defined(BASE_IMPLEMENTATION) +#endif + +#else // defined(COMPONENT_BUILD) +#define BASE_EXPORT +#define BASE_EXPORT_PRIVATE +#endif + +#endif // BASE_BASE_EXPORT_H_
diff --git a/src/base/base_paths.cc b/src/base/base_paths.cc new file mode 100644 index 0000000..80105b6 --- /dev/null +++ b/src/base/base_paths.cc
@@ -0,0 +1,38 @@ +// Copyright (c) 2006-2008 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/base_paths.h" + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/path_service.h" + +namespace base { + +bool PathProvider(int key, FilePath* result) { + // NOTE: DIR_CURRENT is a special case in PathService::Get + + FilePath cur; + switch (key) { + case base::DIR_EXE: + PathService::Get(base::FILE_EXE, &cur); + cur = cur.DirName(); + break; + case base::DIR_MODULE: + PathService::Get(base::FILE_MODULE, &cur); + cur = cur.DirName(); + break; + case base::DIR_TEMP: + if (!file_util::GetTempDir(&cur)) + return false; + break; + default: + return false; + } + + *result = cur; + return true; +} + +} // namespace base
diff --git a/src/base/base_paths.h b/src/base/base_paths.h new file mode 100644 index 0000000..7bc23a8 --- /dev/null +++ b/src/base/base_paths.h
@@ -0,0 +1,50 @@ +// 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_BASE_PATHS_H_ +#define BASE_BASE_PATHS_H_ + +// This file declares path keys for the base module. These can be used with +// the PathService to access various special directories and files. + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include "base/base_paths_win.h" +#elif defined(OS_MACOSX) +#include "base/base_paths_mac.h" +#elif defined(OS_ANDROID) || defined(__LB_ANDROID__) +#include "base/base_paths_android.h" +#elif defined(OS_STARBOARD) +#include "base/base_paths_starboard.h" +#endif + +#if defined(OS_POSIX) +#include "base/base_paths_posix.h" +#endif + +namespace base { + +enum BasePathKey { + PATH_START = 0, + + DIR_CURRENT, // current directory + DIR_EXE, // directory containing FILE_EXE + DIR_MODULE, // directory containing FILE_MODULE + DIR_TEMP, // temporary directory + FILE_EXE, // Path and filename of the current executable. + FILE_MODULE, // Path and filename of the module containing the code for the + // PathService (which could differ from FILE_EXE if the + // PathService were compiled into a shared object, for example). + DIR_SOURCE_ROOT, // Returns the root of the source tree. This key is useful + // for tests that need to locate various resources. It + // should not be used outside of test code. + DIR_USER_DESKTOP, // The current user's Desktop. + + PATH_END +}; + +} // namespace base + +#endif // BASE_BASE_PATHS_H_
diff --git a/src/base/base_paths_android.cc b/src/base/base_paths_android.cc new file mode 100644 index 0000000..9905200 --- /dev/null +++ b/src/base/base_paths_android.cc
@@ -0,0 +1,63 @@ +// 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. + +// Defines base::PathProviderAndroid which replaces base::PathProviderPosix for +// Android in base/path_service.cc. + +#include <unistd.h> + +#include "base/android/jni_android.h" +#include "base/android/path_utils.h" +#include "base/base_paths.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/process_util.h" + +namespace base { + +bool PathProviderAndroid(int key, FilePath* result) { + switch (key) { + case base::FILE_EXE: { + char bin_dir[PATH_MAX + 1]; + int bin_dir_size = readlink(kProcSelfExe, bin_dir, PATH_MAX); + if (bin_dir_size < 0 || bin_dir_size > PATH_MAX) { + NOTREACHED() << "Unable to resolve " << kProcSelfExe << "."; + return false; + } + bin_dir[bin_dir_size] = 0; + *result = FilePath(bin_dir); + return true; + } + case base::FILE_MODULE: + // dladdr didn't work in Android as only the file name was returned. + NOTIMPLEMENTED(); + return false; + case base::DIR_MODULE: + return base::android::GetNativeLibraryDirectory(result); + case base::DIR_SOURCE_ROOT: + // This const is only used for tests. + return base::android::GetExternalStorageDirectory(result); + case base::DIR_USER_DESKTOP: + // Android doesn't support GetUserDesktop. + NOTIMPLEMENTED(); + return false; + case base::DIR_CACHE: + return base::android::GetCacheDirectory(result); + case base::DIR_ANDROID_APP_DATA: + return base::android::GetDataDirectory(result); + case base::DIR_HOME: + *result = file_util::GetHomeDir(); + return true; + case base::DIR_ANDROID_EXTERNAL_STORAGE: + return base::android::GetExternalStorageDirectory(result); + default: + // Note: the path system expects this function to override the default + // behavior. So no need to log an error if we don't support a given + // path. The system will just use the default. + return false; + } +} + +} // namespace base
diff --git a/src/base/base_paths_android.h b/src/base/base_paths_android.h new file mode 100644 index 0000000..7a9ac4a --- /dev/null +++ b/src/base/base_paths_android.h
@@ -0,0 +1,25 @@ +// 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_BASE_PATHS_ANDROID_H_ +#define BASE_BASE_PATHS_ANDROID_H_ + +// This file declares Android-specific path keys for the base module. +// These can be used with the PathService to access various special +// directories and files. + +namespace base { + +enum { + PATH_ANDROID_START = 300, + + DIR_ANDROID_APP_DATA, // Directory where to put Android app's data. + DIR_ANDROID_EXTERNAL_STORAGE, // Android external storage directory. + + PATH_ANDROID_END +}; + +} // namespace base + +#endif // BASE_BASE_PATHS_ANDROID_H_
diff --git a/src/base/base_paths_mac.h b/src/base/base_paths_mac.h new file mode 100644 index 0000000..ac75402 --- /dev/null +++ b/src/base/base_paths_mac.h
@@ -0,0 +1,24 @@ +// Copyright (c) 2006-2008 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_BASE_PATHS_MAC_H_ +#define BASE_BASE_PATHS_MAC_H_ + +// This file declares Mac-specific path keys for the base module. +// These can be used with the PathService to access various special +// directories and files. + +namespace base { + +enum { + PATH_MAC_START = 200, + + DIR_APP_DATA, // ~/Library/Application Support + + PATH_MAC_END +}; + +} // namespace base + +#endif // BASE_BASE_PATHS_MAC_H_
diff --git a/src/base/base_paths_mac.mm b/src/base/base_paths_mac.mm new file mode 100644 index 0000000..630f742 --- /dev/null +++ b/src/base/base_paths_mac.mm
@@ -0,0 +1,112 @@ +// 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. + +// Defines base::PathProviderMac which replaces base::PathProviderPosix for Mac +// in base/path_service.cc. + +#include <dlfcn.h> +#import <Foundation/Foundation.h> +#include <mach-o/dyld.h> + +#include "base/base_paths.h" +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/mac/foundation_util.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "build/build_config.h" + +namespace { + +void GetNSExecutablePath(FilePath* path) { + DCHECK(path); + // Executable path can have relative references ("..") depending on + // how the app was launched. + uint32_t executable_length = 0; + _NSGetExecutablePath(NULL, &executable_length); + DCHECK_GT(executable_length, 1u); + std::string executable_path; + int rv = _NSGetExecutablePath(WriteInto(&executable_path, executable_length), + &executable_length); + DCHECK_EQ(rv, 0); + *path = FilePath(executable_path); +} + +// Returns true if the module for |address| is found. |path| will contain +// the path to the module. Note that |path| may not be absolute. +bool GetModulePathForAddress(FilePath* path, + const void* address) WARN_UNUSED_RESULT; + +bool GetModulePathForAddress(FilePath* path, const void* address) { + Dl_info info; + if (dladdr(address, &info) == 0) + return false; + *path = FilePath(info.dli_fname); + return true; +} + +} // namespace + +namespace base { + +bool PathProviderMac(int key, FilePath* result) { + switch (key) { + case base::FILE_EXE: + GetNSExecutablePath(result); + return true; + case base::FILE_MODULE: + return GetModulePathForAddress(result, + reinterpret_cast<const void*>(&base::PathProviderMac)); + case base::DIR_APP_DATA: { + bool success = base::mac::GetUserDirectory(NSApplicationSupportDirectory, + result); +#if defined(OS_IOS) + // On IOS, this directory does not exist unless it is created explicitly. + if (success && !file_util::PathExists(*result)) + success = file_util::CreateDirectory(*result); +#endif // defined(OS_IOS) + return success; + } + case base::DIR_SOURCE_ROOT: + // Go through PathService to catch overrides. + if (!PathService::Get(base::FILE_EXE, result)) + return false; + + // Start with the executable's directory. + *result = result->DirName(); + +#if !defined(OS_IOS) + if (base::mac::AmIBundled()) { + // The bundled app executables (Chromium, TestShell, etc) live five + // levels down, eg: + // src/xcodebuild/{Debug|Release}/Chromium.app/Contents/MacOS/Chromium + *result = result->DirName().DirName().DirName().DirName().DirName(); + } else { + // Unit tests execute two levels deep from the source root, eg: + // src/xcodebuild/{Debug|Release}/base_unittests + *result = result->DirName().DirName(); + } +#endif + return true; + case base::DIR_USER_DESKTOP: +#if defined(OS_IOS) + // iOS does not have desktop directories. + NOTIMPLEMENTED(); + return false; +#else + return base::mac::GetUserDirectory(NSDesktopDirectory, result); +#endif + case base::DIR_CACHE: + return base::mac::GetUserDirectory(NSCachesDirectory, result); + case base::DIR_HOME: + *result = base::mac::NSStringToFilePath(NSHomeDirectory()); + return true; + default: + return false; + } +} + +} // namespace base
diff --git a/src/base/base_paths_posix.cc b/src/base/base_paths_posix.cc new file mode 100644 index 0000000..834dee3 --- /dev/null +++ b/src/base/base_paths_posix.cc
@@ -0,0 +1,119 @@ +// 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. + +// Defines base::PathProviderPosix, default path provider on POSIX OSes that +// don't have their own base_paths_OS.cc implementation (i.e. all but Mac and +// Android). + +#include <ostream> +#include <string> + +#include "base/base_paths.h" +#include "base/environment.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/process_util.h" +#include "base/nix/xdg_util.h" +#include "build/build_config.h" + +#if defined(OS_FREEBSD) +#include <sys/param.h> +#include <sys/sysctl.h> +#elif defined(OS_SOLARIS) +#include <stdlib.h> +#endif + +namespace base { + +bool PathProviderPosix(int key, FilePath* result) { + FilePath path; + switch (key) { + case base::FILE_EXE: + case base::FILE_MODULE: { // TODO(evanm): is this correct? +#if defined(OS_LINUX) + FilePath bin_dir; + if (!file_util::ReadSymbolicLink(FilePath(kProcSelfExe), &bin_dir)) { + NOTREACHED() << "Unable to resolve " << kProcSelfExe << "."; + return false; + } + *result = bin_dir; + return true; +#elif defined(OS_FREEBSD) + int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; + char bin_dir[PATH_MAX + 1]; + size_t length = sizeof(bin_dir); + // Upon return, |length| is the number of bytes written to |bin_dir| + // including the string terminator. + int error = sysctl(name, 4, bin_dir, &length, NULL, 0); + if (error < 0 || length <= 1) { + NOTREACHED() << "Unable to resolve path."; + return false; + } + *result = FilePath(FilePath::StringType(bin_dir, length - 1)); + return true; +#elif defined(OS_SOLARIS) + char bin_dir[PATH_MAX + 1]; + if (realpath(getexecname(), bin_dir) == NULL) { + NOTREACHED() << "Unable to resolve " << getexecname() << "."; + return false; + } + *result = FilePath(bin_dir); + return true; +#elif defined(OS_OPENBSD) + // There is currently no way to get the executable path on OpenBSD + char* cpath; + if ((cpath = getenv("CHROME_EXE_PATH")) != NULL) + *result = FilePath(cpath); + else + *result = FilePath("/usr/local/chrome/chrome"); + return true; +#endif + } + case base::DIR_SOURCE_ROOT: { + // Allow passing this in the environment, for more flexibility in build + // tree configurations (sub-project builds, gyp --output_dir, etc.) + scoped_ptr<base::Environment> env(base::Environment::Create()); + std::string cr_source_root; + if (env->GetVar("CR_SOURCE_ROOT", &cr_source_root)) { + path = FilePath(cr_source_root); + if (file_util::PathExists(path)) { + *result = path; + return true; + } else { + DLOG(WARNING) << "CR_SOURCE_ROOT is set, but it appears to not " + << "point to a directory."; + } + } + // On POSIX, unit tests execute two levels deep from the source root. + // For example: out/{Debug|Release}/net_unittest + if (PathService::Get(base::DIR_EXE, &path)) { + *result = path.DirName().DirName(); + return true; + } + + DLOG(ERROR) << "Couldn't find your source root. " + << "Try running from your chromium/src directory."; + return false; + } + case base::DIR_USER_DESKTOP: + *result = base::nix::GetXDGUserDirectory("DESKTOP", "Desktop"); + return true; + case base::DIR_CACHE: { + scoped_ptr<base::Environment> env(base::Environment::Create()); + FilePath cache_dir(base::nix::GetXDGDirectory(env.get(), "XDG_CACHE_HOME", + ".cache")); + *result = cache_dir; + return true; + } + case base::DIR_HOME: + *result = file_util::GetHomeDir(); + return true; + } + return false; +} + +} // namespace base
diff --git a/src/base/base_paths_posix.h b/src/base/base_paths_posix.h new file mode 100644 index 0000000..811c8cb --- /dev/null +++ b/src/base/base_paths_posix.h
@@ -0,0 +1,29 @@ +// 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_BASE_PATHS_POSIX_H_ +#define BASE_BASE_PATHS_POSIX_H_ + +// This file declares windows-specific path keys for the base module. +// These can be used with the PathService to access various special +// directories and files. + +namespace base { + +enum { + PATH_POSIX_START = 400, + + DIR_CACHE, // Directory where to put cache data. Note this is + // *not* where the browser cache lives, but the + // browser cache can be a subdirectory. + // This is $XDG_CACHE_HOME on Linux and + // ~/Library/Caches on Mac. + DIR_HOME, // $HOME on POSIX-like systems. + + PATH_POSIX_END +}; + +} // namespace base + +#endif // BASE_BASE_PATHS_POSIX_H_
diff --git a/src/base/base_paths_shell.cc b/src/base/base_paths_shell.cc new file mode 100644 index 0000000..aae1d80 --- /dev/null +++ b/src/base/base_paths_shell.cc
@@ -0,0 +1,76 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "base/base_paths.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "cobalt/deprecated/platform_delegate.h" + +namespace base { + +// this is where we can control the path for placement +// of a lot of file resources for lbshell. +bool PathProviderShell(int key, FilePath* result) { + cobalt::deprecated::PlatformDelegate* plat = + cobalt::deprecated::PlatformDelegate::Get(); + switch (key) { + case base::DIR_EXE: + case base::DIR_MODULE: + DCHECK(!plat->game_content_path().empty()); + *result = FilePath(plat->game_content_path()); + return true; + +#if defined(ENABLE_DIR_SOURCE_ROOT_ACCESS) + case base::DIR_SOURCE_ROOT: + if (plat->dir_source_root().length() > 0) { + *result = FilePath(plat->dir_source_root()); + return true; + } else { + DLOG(INFO) << "DIR_SOURCE_ROOT not defined - skipping."; + return false; + } +#endif // ENABLE_DIR_SOURCE_ROOT_ACCESS + + case base::DIR_TEMP: + DCHECK(!plat->temp_path().empty()); + *result = FilePath(plat->temp_path()); + return true; + + case base::DIR_CACHE: + DCHECK(!plat->temp_path().empty()); + *result = FilePath(plat->temp_path()).Append("cache"); + return true; + case base::DIR_HOME: { +#if defined(COBALT_LINUX) + const char* home_dir = getenv("HOME"); + if (home_dir && home_dir[0]) { + *result = FilePath(home_dir); + return true; + } else { + return PathProviderShell(base::DIR_TEMP, result); + } +#else + return PathProviderShell(base::DIR_EXE, result); +#endif // defined(COBALT_LINUX) + } + } + + return false; +} + +} // namespace base +
diff --git a/src/base/base_paths_starboard.cc b/src/base/base_paths_starboard.cc new file mode 100644 index 0000000..2b8a14a --- /dev/null +++ b/src/base/base_paths_starboard.cc
@@ -0,0 +1,98 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/base_paths.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "starboard/system.h" + +namespace base { + +// This is where we can control the path for placement of a lot of file +// resources for cobalt. +bool PathProviderStarboard(int key, FilePath *result) { + char path[SB_FILE_MAX_PATH]; + switch (key) { + case base::FILE_EXE: { + bool success = SbSystemGetPath(kSbSystemPathExecutableFile, path, + SB_ARRAY_SIZE_INT(path)); + DCHECK(success); + if (success) { + *result = FilePath(path); + return true; + } + DLOG(ERROR) << "FILE_EXE not defined."; + return false; + } + + case base::DIR_EXE: + case base::DIR_MODULE: { + bool success = SbSystemGetPath(kSbSystemPathContentDirectory, path, + SB_ARRAY_SIZE_INT(path)); + DCHECK(success); + if (success) { + *result = FilePath(path); + return true; + } + DLOG(ERROR) << "DIR_EXE/DIR_MODULE not defined."; + return false; + } + +#if defined(ENABLE_DIR_SOURCE_ROOT_ACCESS) + case base::DIR_SOURCE_ROOT: { + bool success = SbSystemGetPath(kSbSystemPathSourceDirectory, path, + SB_ARRAY_SIZE_INT(path)); + DCHECK(success); + if (success) { + *result = FilePath(path); + return true; + } + DLOG(ERROR) << "DIR_SOURCE_ROOT not defined."; + return false; + } +#endif // ENABLE_DIR_SOURCE_ROOT_ACCESS + + case base::DIR_CACHE: { + bool success = SbSystemGetPath(kSbSystemPathCacheDirectory, path, + SB_ARRAY_SIZE_INT(path)); + if (success) { + *result = FilePath(path); + return true; + } + DLOG(INFO) << "DIR_CACHE not defined."; + return false; + } + + case base::DIR_TEMP: { + bool success = SbSystemGetPath(kSbSystemPathTempDirectory, path, + SB_ARRAY_SIZE_INT(path)); + DCHECK(success); + if (success) { + *result = FilePath(path); + return true; + } + DLOG(ERROR) << "DIR_TEMP not defined."; + return false; + } + + case base::DIR_HOME: + // TODO: Add a home directory to SbSystemPathId and get it from there. + return PathProviderStarboard(base::DIR_CACHE, result); + } + + NOTREACHED() << "key = " << key; + return false; +} + +}
diff --git a/src/base/base_paths_starboard.h b/src/base/base_paths_starboard.h new file mode 100644 index 0000000..cfa9099 --- /dev/null +++ b/src/base/base_paths_starboard.h
@@ -0,0 +1,39 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BASE_BASE_PATHS_STARBOARD_H_ +#define BASE_BASE_PATHS_STARBOARD_H_ + +// This file declares Starboard-specific path keys for the base module. These +// can be used with the PathService to access various special directories and +// files. + +namespace base { + +enum { + PATH_STARBOARD_START = 500, + + DIR_CACHE, // Directory where to put cache data. Note this is + // *not* where the browser cache lives, but the + // browser cache can be a subdirectory. + + DIR_HOME, // The root of the primary directory for a user (and their + // programs) to place their personal files. + + PATH_STARBOARD_END +}; + +} // namespace base + +#endif // BASE_BASE_PATHS_STARBOARD_H_
diff --git a/src/base/base_paths_win.cc b/src/base/base_paths_win.cc new file mode 100644 index 0000000..a06d908 --- /dev/null +++ b/src/base/base_paths_win.cc
@@ -0,0 +1,209 @@ +// 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 <windows.h> +#include <shlobj.h> + +#include "base/base_paths.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/win/scoped_co_mem.h" +#include "base/win/windows_version.h" + +// http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx +extern "C" IMAGE_DOS_HEADER __ImageBase; + +namespace { + +bool GetQuickLaunchPath(bool default_user, FilePath* result) { + if (default_user) { + wchar_t system_buffer[MAX_PATH]; + system_buffer[0] = 0; + // As per MSDN, passing -1 for |hToken| indicates the Default user: + // http://msdn.microsoft.com/library/windows/desktop/bb762181.aspx + if (FAILED(SHGetFolderPath(NULL, CSIDL_APPDATA, + reinterpret_cast<HANDLE>(-1), SHGFP_TYPE_CURRENT, + system_buffer))) { + return false; + } + *result = FilePath(system_buffer); + } else if (!PathService::Get(base::DIR_APP_DATA, result)) { + // For the current user, grab the APPDATA directory directly from the + // PathService cache. + return false; + } + // According to various sources, appending + // "Microsoft\Internet Explorer\Quick Launch" to %appdata% is the only + // reliable way to get the quick launch folder across all versions of Windows. + // http://stackoverflow.com/questions/76080/how-do-you-reliably-get-the-quick- + // http://www.microsoft.com/technet/scriptcenter/resources/qanda/sept05/hey0901.mspx + *result = result->AppendASCII("Microsoft"); + *result = result->AppendASCII("Internet Explorer"); + *result = result->AppendASCII("Quick Launch"); + return true; +} + +} // namespace + +namespace base { + +bool PathProviderWin(int key, FilePath* result) { + + // We need to go compute the value. It would be nice to support paths with + // names longer than MAX_PATH, but the system functions don't seem to be + // designed for it either, with the exception of GetTempPath (but other + // things will surely break if the temp path is too long, so we don't bother + // handling it. + wchar_t system_buffer[MAX_PATH]; + system_buffer[0] = 0; + + FilePath cur; + switch (key) { + case base::FILE_EXE: + GetModuleFileName(NULL, system_buffer, MAX_PATH); + cur = FilePath(system_buffer); + break; + case base::FILE_MODULE: { + // the resource containing module is assumed to be the one that + // this code lives in, whether that's a dll or exe + HMODULE this_module = reinterpret_cast<HMODULE>(&__ImageBase); + GetModuleFileName(this_module, system_buffer, MAX_PATH); + cur = FilePath(system_buffer); + break; + } + case base::DIR_WINDOWS: + GetWindowsDirectory(system_buffer, MAX_PATH); + cur = FilePath(system_buffer); + break; + case base::DIR_SYSTEM: + GetSystemDirectory(system_buffer, MAX_PATH); + cur = FilePath(system_buffer); + break; + case base::DIR_PROGRAM_FILESX86: + if (base::win::OSInfo::GetInstance()->architecture() != + base::win::OSInfo::X86_ARCHITECTURE) { + if (FAILED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILESX86, NULL, + SHGFP_TYPE_CURRENT, system_buffer))) + return false; + cur = FilePath(system_buffer); + break; + } + // Fall through to base::DIR_PROGRAM_FILES if we're on an X86 machine. + case base::DIR_PROGRAM_FILES: + if (FAILED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, + SHGFP_TYPE_CURRENT, system_buffer))) + return false; + cur = FilePath(system_buffer); + break; + case base::DIR_IE_INTERNET_CACHE: + if (FAILED(SHGetFolderPath(NULL, CSIDL_INTERNET_CACHE, NULL, + SHGFP_TYPE_CURRENT, system_buffer))) + return false; + cur = FilePath(system_buffer); + break; + case base::DIR_COMMON_START_MENU: + if (FAILED(SHGetFolderPath(NULL, CSIDL_COMMON_PROGRAMS, NULL, + SHGFP_TYPE_CURRENT, system_buffer))) + return false; + cur = FilePath(system_buffer); + break; + case base::DIR_START_MENU: + if (FAILED(SHGetFolderPath(NULL, CSIDL_PROGRAMS, NULL, + SHGFP_TYPE_CURRENT, system_buffer))) + return false; + cur = FilePath(system_buffer); + break; + case base::DIR_APP_DATA: + if (FAILED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, + system_buffer))) + return false; + cur = FilePath(system_buffer); + break; + case base::DIR_COMMON_APP_DATA: + if (FAILED(SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, + SHGFP_TYPE_CURRENT, system_buffer))) + return false; + cur = FilePath(system_buffer); + break; + case base::DIR_PROFILE: + if (FAILED(SHGetFolderPath(NULL, CSIDL_PROFILE, NULL, SHGFP_TYPE_CURRENT, + system_buffer))) + return false; + cur = FilePath(system_buffer); + break; + case base::DIR_LOCAL_APP_DATA_LOW: + if (win::GetVersion() < win::VERSION_VISTA) + return false; + + // TODO(nsylvain): We should use SHGetKnownFolderPath instead. Bug 1281128 + if (FAILED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, + system_buffer))) + return false; + cur = FilePath(system_buffer).DirName().AppendASCII("LocalLow"); + break; + case base::DIR_LOCAL_APP_DATA: + if (FAILED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, + SHGFP_TYPE_CURRENT, system_buffer))) + return false; + cur = FilePath(system_buffer); + break; + case base::DIR_SOURCE_ROOT: { + FilePath executableDir; + // On Windows, unit tests execute two levels deep from the source root. + // For example: chrome/{Debug|Release}/ui_tests.exe + PathService::Get(base::DIR_EXE, &executableDir); + cur = executableDir.DirName().DirName(); + break; + } + case base::DIR_APP_SHORTCUTS: { + if (win::GetVersion() < win::VERSION_WIN8) + return false; + + base::win::ScopedCoMem<wchar_t> path_buf; + if (FAILED(SHGetKnownFolderPath(FOLDERID_ApplicationShortcuts, 0, NULL, + &path_buf))) + return false; + + cur = FilePath(string16(path_buf)); + break; + } + case base::DIR_USER_DESKTOP: + if (FAILED(SHGetFolderPath(NULL, CSIDL_DESKTOPDIRECTORY, NULL, + SHGFP_TYPE_CURRENT, system_buffer))) { + return false; + } + cur = FilePath(system_buffer); + break; + case base::DIR_COMMON_DESKTOP: + if (FAILED(SHGetFolderPath(NULL, CSIDL_COMMON_DESKTOPDIRECTORY, NULL, + SHGFP_TYPE_CURRENT, system_buffer))) { + return false; + } + cur = FilePath(system_buffer); + break; + case base::DIR_USER_QUICK_LAUNCH: + if (!GetQuickLaunchPath(false, &cur)) + return false; + break; + case base::DIR_DEFAULT_USER_QUICK_LAUNCH: + if (!GetQuickLaunchPath(true, &cur)) + return false; + break; + case base::DIR_TASKBAR_PINS: + if (!PathService::Get(base::DIR_USER_QUICK_LAUNCH, &cur)) + return false; + cur = cur.AppendASCII("User Pinned"); + cur = cur.AppendASCII("TaskBar"); + break; + default: + return false; + } + + *result = cur; + return true; +} + +} // namespace base
diff --git a/src/base/base_paths_win.h b/src/base/base_paths_win.h new file mode 100644 index 0000000..11bc111 --- /dev/null +++ b/src/base/base_paths_win.h
@@ -0,0 +1,51 @@ +// 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_BASE_PATHS_WIN_H__ +#define BASE_BASE_PATHS_WIN_H__ + +// This file declares windows-specific path keys for the base module. +// These can be used with the PathService to access various special +// directories and files. + +namespace base { + +enum { + PATH_WIN_START = 100, + + DIR_WINDOWS, // Windows directory, usually "c:\windows" + DIR_SYSTEM, // Usually c:\windows\system32" + DIR_PROGRAM_FILES, // Usually c:\program files + DIR_PROGRAM_FILESX86, // Usually c:\program files or c:\program files (x86) + + DIR_IE_INTERNET_CACHE, // Temporary Internet Files directory. + DIR_COMMON_START_MENU, // Usually "C:\Documents and Settings\All Users\ + // Start Menu\Programs" + DIR_START_MENU, // Usually "C:\Documents and Settings\<user>\ + // Start Menu\Programs" + DIR_APP_DATA, // Application Data directory under the user profile. + DIR_PROFILE, // Usually "C:\Documents and settings\<user>. + DIR_LOCAL_APP_DATA_LOW, // Local AppData directory for low integrity level. + DIR_LOCAL_APP_DATA, // "Local Settings\Application Data" directory under + // the user profile. + DIR_COMMON_APP_DATA, // W2K, XP, W2K3: "C:\Documents and Settings\ + // All Users\Application Data". + // Vista, W2K8 and above: "C:\ProgramData". + DIR_APP_SHORTCUTS, // Where tiles on the start screen are stored, only + // for Windows 8. Maps to "Local\AppData\Microsoft\ + // Windows\Application Shortcuts\". + DIR_COMMON_DESKTOP, // Directory for the common desktop (visible + // on all user's Desktop). + DIR_USER_QUICK_LAUNCH, // Directory for the quick launch shortcuts. + DIR_DEFAULT_USER_QUICK_LAUNCH, // Directory for the quick launch shortcuts + // of the Default user. + DIR_TASKBAR_PINS, // Directory for the shortcuts pinned to taskbar via + // base::win::TaskbarPinShortcutLink(). + + PATH_WIN_END +}; + +} // namespace base + +#endif // BASE_BASE_PATHS_WIN_H__
diff --git a/src/base/base_switches.cc b/src/base/base_switches.cc new file mode 100644 index 0000000..7c7b061 --- /dev/null +++ b/src/base/base_switches.cc
@@ -0,0 +1,49 @@ +// 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/base_switches.h" + +namespace switches { + +// If the program includes base/debug/debug_on_start_win.h, the process will +// (on Windows only) start the JIT system-registered debugger on itself and +// will wait for 60 seconds for the debugger to attach to itself. Then a break +// point will be hit. +const char kDebugOnStart[] = "debug-on-start"; + +// Disables the crash reporting. +const char kDisableBreakpad[] = "disable-breakpad"; + +// Enable DCHECKs in release mode. +const char kEnableDCHECK[] = "enable-dcheck"; + +// Generates full memory crash dump. +const char kFullMemoryCrashReport[] = "full-memory-crash-report"; + +// Suppresses all error dialogs when present. +const char kNoErrorDialogs[] = "noerrdialogs"; + +// When running certain tests that spawn child processes, this switch indicates +// to the test framework that the current process is a child process. +const char kTestChildProcess[] = "test-child-process"; + +// Gives the default maximal active V-logging level; 0 is the default. +// Normally positive values are used for V-logging levels. +const char kV[] = "v"; + +// Gives the per-module maximal V-logging levels to override the value +// given by --v. E.g. "my_module=2,foo*=3" would change the logging +// level for all code in source files "my_module.*" and "foo*.*" +// ("-inl" suffixes are also disregarded for this matching). +// +// Any pattern containing a forward or backward slash will be tested +// against the whole pathname and not just the module. E.g., +// "*/foo/bar/*=2" would change the logging level for all code in +// source files under a "foo/bar" directory. +const char kVModule[] = "vmodule"; + +// Will wait for 60 seconds for a debugger to come to attach to the process. +const char kWaitForDebugger[] = "wait-for-debugger"; + +} // namespace switches
diff --git a/src/base/base_switches.h b/src/base/base_switches.h new file mode 100644 index 0000000..558a5cf --- /dev/null +++ b/src/base/base_switches.h
@@ -0,0 +1,24 @@ +// 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. + +// Defines all the "base" command-line switches. + +#ifndef BASE_BASE_SWITCHES_H_ +#define BASE_BASE_SWITCHES_H_ + +namespace switches { + +extern const char kDebugOnStart[]; +extern const char kDisableBreakpad[]; +extern const char kEnableDCHECK[]; +extern const char kFullMemoryCrashReport[]; +extern const char kNoErrorDialogs[]; +extern const char kTestChildProcess[]; +extern const char kV[]; +extern const char kVModule[]; +extern const char kWaitForDebugger[]; + +} // namespace switches + +#endif // BASE_BASE_SWITCHES_H_
diff --git a/src/base/base_unittests.isolate b/src/base/base_unittests.isolate new file mode 100644 index 0000000..f13e66b --- /dev/null +++ b/src/base/base_unittests.isolate
@@ -0,0 +1,50 @@ +# 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. +{ + 'variables': { + 'isolate_dependency_tracked': [ + '../testing/test_env.py', + '../tools/swarm_client/run_isolated.py', + '../tools/swarm_client/run_test_cases.py', + '<(PRODUCT_DIR)/base_unittests<(EXECUTABLE_SUFFIX)', + 'data/json/bom_feff.json', + ], + 'isolate_dependency_untracked': [ + 'data/file_util_unittest/', + ], + }, + 'conditions': [ + ['OS=="linux"', { + 'variables': { + 'command': [ + '../testing/xvfb.py', + '<(PRODUCT_DIR)', + '../tools/swarm_client/run_test_cases.py', + '<(PRODUCT_DIR)/base_unittests<(EXECUTABLE_SUFFIX)', + ], + 'isolate_dependency_tracked': [ + '../testing/xvfb.py', + '<(PRODUCT_DIR)/xdisplaycheck<(EXECUTABLE_SUFFIX)', + ], + }, + }, { + 'variables': { + 'command': [ + '../testing/test_env.py', + '../tools/swarm_client/run_test_cases.py', + '<(PRODUCT_DIR)/base_unittests<(EXECUTABLE_SUFFIX)', + ], + }, + }], + ['OS=="win"', { + 'variables': { + 'isolate_dependency_tracked': [ + '<(PRODUCT_DIR)/icudt.dll', + 'data/file_version_info_unittest/FileVersionInfoTest1.dll', + 'data/file_version_info_unittest/FileVersionInfoTest2.dll', + ], + }, + }], + ], +}
diff --git a/src/base/base_untrusted.gyp b/src/base/base_untrusted.gyp new file mode 100644 index 0000000..8b64c7d --- /dev/null +++ b/src/base/base_untrusted.gyp
@@ -0,0 +1,39 @@ +# 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'includes': [ + '../build/common_untrusted.gypi', + 'base.gypi', + ], + 'conditions': [ + ['disable_nacl==0 and disable_nacl_untrusted==0', { + 'targets': [ + { + 'target_name': 'base_untrusted', + 'type': 'none', + 'variables': { + 'base_target': 1, + 'nacl_untrusted_build': 1, + 'nlib_target': 'libbase_untrusted.a', + 'build_glibc': 1, + 'build_newlib': 1, + 'sources': [ + 'string16.cc', + 'sync_socket_nacl.cc', + 'third_party/nspr/prtime.cc', + 'time_posix.cc', + ], + }, + 'dependencies': [ + '<(DEPTH)/native_client/tools.gyp:prep_toolchain', + ], + }, + ], + }], + ], +}
diff --git a/src/base/basictypes.h b/src/base/basictypes.h new file mode 100644 index 0000000..c393fd7 --- /dev/null +++ b/src/base/basictypes.h
@@ -0,0 +1,365 @@ +// 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. + +#ifndef BASE_BASICTYPES_H_ +#define BASE_BASICTYPES_H_ + +#include <limits.h> // So we can set the bounds of our types +#include <stddef.h> // For size_t +#include <string.h> // for memcpy + +#include "base/port.h" // Types that only need exist on certain systems + +#ifndef COMPILER_MSVC +// stdint.h is part of C99 but MSVC doesn't have it. +#include <stdint.h> // For intptr_t. +#endif + +typedef signed char schar; +typedef signed char int8; +typedef short int16; +typedef int int32; + +// The NSPR system headers define 64-bit as |long| when possible, except on +// Mac OS X. In order to not have typedef mismatches, we do the same on LP64. +// +// On Mac OS X, |long long| is used for 64-bit types for compatibility with +// <inttypes.h> format macros even in the LP64 model. +#if defined(__LP64__) && !defined(OS_MACOSX) && !defined(OS_OPENBSD) +typedef long int64; +#else +typedef long long int64; +#endif + +// NOTE: unsigned types are DANGEROUS in loops and other arithmetical +// places. Use the signed types unless your variable represents a bit +// pattern (eg a hash value) or you really need the extra bit. Do NOT +// use 'unsigned' to express "this value should always be positive"; +// use assertions for this. + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; + +// See the comment above about NSPR and 64-bit. +#if defined(__LP64__) && !defined(OS_MACOSX) && !defined(OS_OPENBSD) +typedef unsigned long uint64; +#else +typedef unsigned long long uint64; +#endif + +// A type to represent a Unicode code-point value. As of Unicode 4.0, +// such values require up to 21 bits. +// (For type-checking on pointers, make this explicitly signed, +// and it should always be the signed version of whatever int32 is.) +typedef signed int char32; + +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4310) // Cast truncates constant value. +#endif + +const uint8 kuint8max = (( uint8) 0xFF); +const uint16 kuint16max = ((uint16) 0xFFFF); +const uint32 kuint32max = ((uint32) 0xFFFFFFFF); +const uint64 kuint64max = ((uint64) GG_LONGLONG(0xFFFFFFFFFFFFFFFF)); +const int8 kint8min = (( int8) 0x80); +const int8 kint8max = (( int8) 0x7F); +const int16 kint16min = (( int16) 0x8000); +const int16 kint16max = (( int16) 0x7FFF); +const int32 kint32min = (( int32) 0x80000000); +const int32 kint32max = (( int32) 0x7FFFFFFF); +const int64 kint64min = (( int64) GG_LONGLONG(0x8000000000000000)); +const int64 kint64max = (( int64) GG_LONGLONG(0x7FFFFFFFFFFFFFFF)); + +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + +// A macro to disallow the copy constructor and operator= functions +// This should be used in the private: declarations for a class +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +// An older, deprecated, politically incorrect name for the above. +// NOTE: The usage of this macro was baned from our code base, but some +// third_party libraries are yet using it. +// TODO(tfarina): Figure out how to fix the usage of this macro in the +// third_party libraries and get rid of it. +#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) DISALLOW_COPY_AND_ASSIGN(TypeName) + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +// +// This should be used in the private: declarations for a class +// that wants to prevent anyone from instantiating it. This is +// especially useful for classes containing only static methods. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName(); \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// The arraysize(arr) macro returns the # of elements in an array arr. +// The expression is a compile-time constant, and therefore can be +// used in defining new arrays, for example. If you use arraysize on +// a pointer by mistake, you will get a compile-time error. +// +// One caveat is that arraysize() doesn't accept any array of an +// anonymous type or a type defined inside a function. In these rare +// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is +// due to a limitation in C++'s template system. The limitation might +// eventually be removed, but it hasn't happened yet. + +// This template function declaration is used in defining arraysize. +// Note that the function doesn't need an implementation, as we only +// use its type. +template <typename T, size_t N> +char (&ArraySizeHelper(T (&array)[N]))[N]; + +// That gcc wants both of these prototypes seems mysterious. VC, for +// its part, can't decide which to use (another mystery). Matching of +// template overloads: the final frontier. +#ifndef _MSC_VER +template <typename T, size_t N> +char (&ArraySizeHelper(const T (&array)[N]))[N]; +#endif + +#if defined(COMPILER_GHS) +// GHS does not support local types as template arguments, so we must fall back to the unsafe version +#define arraysize ARRAYSIZE_UNSAFE +#else +#define arraysize(array) (sizeof(ArraySizeHelper(array))) +#endif + +// ARRAYSIZE_UNSAFE performs essentially the same calculation as arraysize, +// but can be used on anonymous types or types defined inside +// functions. It's less safe than arraysize as it accepts some +// (although not all) pointers. Therefore, you should use arraysize +// whenever possible. +// +// The expression ARRAYSIZE_UNSAFE(a) is a compile-time constant of type +// size_t. +// +// ARRAYSIZE_UNSAFE catches a few type errors. If you see a compiler error +// +// "warning: division by zero in ..." +// +// when using ARRAYSIZE_UNSAFE, you are (wrongfully) giving it a pointer. +// You should only use ARRAYSIZE_UNSAFE on statically allocated arrays. +// +// The following comments are on the implementation details, and can +// be ignored by the users. +// +// ARRAYSIZE_UNSAFE(arr) works by inspecting sizeof(arr) (the # of bytes in +// the array) and sizeof(*(arr)) (the # of bytes in one array +// element). If the former is divisible by the latter, perhaps arr is +// indeed an array, in which case the division result is the # of +// elements in the array. Otherwise, arr cannot possibly be an array, +// and we generate a compiler error to prevent the code from +// compiling. +// +// Since the size of bool is implementation-defined, we need to cast +// !(sizeof(a) & sizeof(*(a))) to size_t in order to ensure the final +// result has type size_t. +// +// This macro is not perfect as it wrongfully accepts certain +// pointers, namely where the pointer size is divisible by the pointee +// size. Since all our code has to go through a 32-bit compiler, +// where a pointer is 4 bytes, this means all pointers to a type whose +// size is 3 or greater than 4 will be (righteously) rejected. + +#define ARRAYSIZE_UNSAFE(a) \ + ((sizeof(a) / sizeof(*(a))) / \ + static_cast<size_t>(!(sizeof(a) % sizeof(*(a))))) + + +// Use implicit_cast as a safe version of static_cast or const_cast +// for upcasting in the type hierarchy (i.e. casting a pointer to Foo +// to a pointer to SuperclassOfFoo or casting a pointer to Foo to +// a const pointer to Foo). +// When you use implicit_cast, the compiler checks that the cast is safe. +// Such explicit implicit_casts are necessary in surprisingly many +// situations where C++ demands an exact type match instead of an +// argument type convertable to a target type. +// +// The From type can be inferred, so the preferred syntax for using +// implicit_cast is the same as for static_cast etc.: +// +// implicit_cast<ToType>(expr) +// +// implicit_cast would have been part of the C++ standard library, +// but the proposal was submitted too late. It will probably make +// its way into the language in the future. +template<typename To, typename From> +inline To implicit_cast(From const &f) { + return f; +} + +// The COMPILE_ASSERT macro can be used to verify that a compile time +// expression is true. For example, you could use it to verify the +// size of a static array: +// +// COMPILE_ASSERT(ARRAYSIZE_UNSAFE(content_type_names) == CONTENT_NUM_TYPES, +// content_type_names_incorrect_size); +// +// or to make sure a struct is smaller than a certain size: +// +// COMPILE_ASSERT(sizeof(foo) < 128, foo_too_large); +// +// The second argument to the macro is the name of the variable. If +// the expression is false, most compilers will issue a warning/error +// containing the name of the variable. + +template <bool> +struct CompileAssert { +}; + +#undef COMPILE_ASSERT +#define COMPILE_ASSERT(expr, msg) \ + typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] + +// Implementation details of COMPILE_ASSERT: +// +// - COMPILE_ASSERT works by defining an array type that has -1 +// elements (and thus is invalid) when the expression is false. +// +// - The simpler definition +// +// #define COMPILE_ASSERT(expr, msg) typedef char msg[(expr) ? 1 : -1] +// +// does not work, as gcc supports variable-length arrays whose sizes +// are determined at run-time (this is gcc's extension and not part +// of the C++ standard). As a result, gcc fails to reject the +// following code with the simple definition: +// +// int foo; +// COMPILE_ASSERT(foo, msg); // not supposed to compile as foo is +// // not a compile-time constant. +// +// - By using the type CompileAssert<(bool(expr))>, we ensures that +// expr is a compile-time constant. (Template arguments must be +// determined at compile-time.) +// +// - The outer parentheses in CompileAssert<(bool(expr))> are necessary +// to work around a bug in gcc 3.4.4 and 4.0.1. If we had written +// +// CompileAssert<bool(expr)> +// +// instead, these compilers will refuse to compile +// +// COMPILE_ASSERT(5 > 0, some_message); +// +// (They seem to think the ">" in "5 > 0" marks the end of the +// template argument list.) +// +// - The array size is (bool(expr) ? 1 : -1), instead of simply +// +// ((expr) ? 1 : -1). +// +// This is to avoid running into a bug in MS VC 7.1, which +// causes ((0.0) ? 1 : -1) to incorrectly evaluate to 1. + + +// bit_cast<Dest,Source> is a template function that implements the +// equivalent of "*reinterpret_cast<Dest*>(&source)". We need this in +// very low-level functions like the protobuf library and fast math +// support. +// +// float f = 3.14159265358979; +// int i = bit_cast<int32>(f); +// // i = 0x40490fdb +// +// The classical address-casting method is: +// +// // WRONG +// float f = 3.14159265358979; // WRONG +// int i = * reinterpret_cast<int*>(&f); // WRONG +// +// The address-casting method actually produces undefined behavior +// according to ISO C++ specification section 3.10 -15 -. Roughly, this +// section says: if an object in memory has one type, and a program +// accesses it with a different type, then the result is undefined +// behavior for most values of "different type". +// +// This is true for any cast syntax, either *(int*)&f or +// *reinterpret_cast<int*>(&f). And it is particularly true for +// conversions betweeen integral lvalues and floating-point lvalues. +// +// The purpose of 3.10 -15- is to allow optimizing compilers to assume +// that expressions with different types refer to different memory. gcc +// 4.0.1 has an optimizer that takes advantage of this. So a +// non-conforming program quietly produces wildly incorrect output. +// +// The problem is not the use of reinterpret_cast. The problem is type +// punning: holding an object in memory of one type and reading its bits +// back using a different type. +// +// The C++ standard is more subtle and complex than this, but that +// is the basic idea. +// +// Anyways ... +// +// bit_cast<> calls memcpy() which is blessed by the standard, +// especially by the example in section 3.9 . Also, of course, +// bit_cast<> wraps up the nasty logic in one place. +// +// Fortunately memcpy() is very fast. In optimized mode, with a +// constant size, gcc 2.95.3, gcc 4.0.1, and msvc 7.1 produce inline +// code with the minimal amount of data movement. On a 32-bit system, +// memcpy(d,s,4) compiles to one load and one store, and memcpy(d,s,8) +// compiles to two loads and two stores. +// +// I tested this code with gcc 2.95.3, gcc 4.0.1, icc 8.1, and msvc 7.1. +// +// WARNING: if Dest or Source is a non-POD type, the result of the memcpy +// is likely to surprise you. + +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]; + + Dest dest; + memcpy(&dest, &source, sizeof(dest)); + return dest; +} + +// Used to explicitly mark the return value of a function as unused. If you are +// really sure you don't want to do anything with the return value of a function +// that has been marked WARN_UNUSED_RESULT, wrap it with this. Example: +// +// scoped_ptr<MyType> my_var = ...; +// if (TakeOwnership(my_var.get()) == SUCCESS) +// ignore_result(my_var.release()); +// +template<typename T> +inline void ignore_result(const T&) { +} + +// The following enum should be used only as a constructor argument to indicate +// that the variable has static storage class, and that the constructor should +// do nothing to its state. It indicates to the reader that it is legal to +// declare a static instance of the class, provided the constructor is given +// the base::LINKER_INITIALIZED argument. Normally, it is unsafe to declare a +// static variable that has a constructor or a destructor because invocation +// order is undefined. However, IF the type can be initialized by filling with +// zeroes (which the loader does for static variables), AND the destructor also +// does nothing to the storage, AND there are no virtual methods, then a +// constructor declared as +// explicit MyClass(base::LinkerInitialized x) {} +// and invoked as +// static MyClass my_variable_name(base::LINKER_INITIALIZED); +namespace base { +enum LinkerInitialized { LINKER_INITIALIZED }; + +// Use these to declare and define a static local variable (static T;) so that +// it is leaked so that its destructors are not called at exit. If you need +// thread-safe initialization, use base/lazy_instance.h instead. +#define CR_DEFINE_STATIC_LOCAL(type, name, arguments) \ + static type& name = *new type arguments + +} // base + +#endif // BASE_BASICTYPES_H_
diff --git a/src/base/bind.h b/src/base/bind.h new file mode 100644 index 0000000..5cf124d --- /dev/null +++ b/src/base/bind.h
@@ -0,0 +1,517 @@ +// This file was GENERATED by command: +// pump.py bind.h.pump +// DO NOT EDIT BY HAND!!! + + +// 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. + +#ifndef BASE_BIND_H_ +#define BASE_BIND_H_ + +#include "base/bind_internal.h" +#include "base/callback_internal.h" + +// ----------------------------------------------------------------------------- +// Usage documentation +// ----------------------------------------------------------------------------- +// +// See base/callback.h for documentation. +// +// +// ----------------------------------------------------------------------------- +// Implementation notes +// ----------------------------------------------------------------------------- +// +// If you're reading the implementation, before proceeding further, you should +// read the top comment of base/bind_internal.h for a definition of common +// terms and concepts. +// +// RETURN TYPES +// +// Though Bind()'s result is meant to be stored in a Callback<> type, it +// cannot actually return the exact type without requiring a large amount +// of extra template specializations. The problem is that in order to +// discern the correct specialization of Callback<>, Bind would need to +// unwrap the function signature to determine the signature's arity, and +// whether or not it is a method. +// +// Each unique combination of (arity, function_type, num_prebound) where +// function_type is one of {function, method, const_method} would require +// one specialization. We eventually have to do a similar number of +// specializations anyways in the implementation (see the Invoker<>, +// classes). However, it is avoidable in Bind if we return the result +// via an indirection like we do below. +// +// TODO(ajwong): We might be able to avoid this now, but need to test. +// +// It is possible to move most of the COMPILE_ASSERT asserts into BindState<>, +// but it feels a little nicer to have the asserts here so people do not +// need to crack open bind_internal.h. On the other hand, it makes Bind() +// harder to read. + +namespace base { + +template <typename Functor> +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits<Functor>::RunnableType, + typename internal::FunctorTraits<Functor>::RunType, + void()> + ::UnboundRunType> +Bind(Functor functor) { + // Typedefs for how to store and run the functor. + typedef typename internal::FunctorTraits<Functor>::RunnableType RunnableType; + typedef typename internal::FunctorTraits<Functor>::RunType RunType; + + // Use RunnableType::RunType instead of RunType above because our + // checks should below for bound references need to know what the actual + // functor is going to interpret the argument as. + typedef internal::FunctionTraits<typename RunnableType::RunType> + BoundFunctorTraits; + + typedef internal::BindState<RunnableType, RunType, void()> BindState; + + + return Callback<typename BindState::UnboundRunType>( + new BindState(internal::MakeRunnable(functor))); +} + +template <typename Functor, typename P1> +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits<Functor>::RunnableType, + typename internal::FunctorTraits<Functor>::RunType, + void(typename internal::CallbackParamTraits<P1>::StorageType)> + ::UnboundRunType> +Bind(Functor functor, const P1& p1) { + // Typedefs for how to store and run the functor. + typedef typename internal::FunctorTraits<Functor>::RunnableType RunnableType; + typedef typename internal::FunctorTraits<Functor>::RunType RunType; + + // Use RunnableType::RunType instead of RunType above because our + // checks should below for bound references need to know what the actual + // functor is going to interpret the argument as. + typedef internal::FunctionTraits<typename RunnableType::RunType> + BoundFunctorTraits; + + // Do not allow binding a non-const reference parameter. Non-const reference + // parameters are disallowed by the Google style guide. Also, binding a + // non-const reference parameter can make for subtle bugs because the + // invoked function will receive a reference to the stored copy of the + // argument and not the original. + COMPILE_ASSERT( + !(is_non_const_reference<typename BoundFunctorTraits::A1Type>::value ), + do_not_bind_functions_with_nonconst_ref); + + // For methods, we need to be careful for parameter 1. We do not require + // a scoped_refptr because BindState<> itself takes care of AddRef() for + // methods. We also disallow binding of an array as the method's target + // object. + COMPILE_ASSERT( + internal::HasIsMethodTag<RunnableType>::value || + !internal::NeedsScopedRefptrButGetsRawPtr<P1>::value, + p1_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag<RunnableType>::value || + !is_array<P1>::value, + first_bound_argument_to_method_cannot_be_array); + typedef internal::BindState<RunnableType, RunType, + void(typename internal::CallbackParamTraits<P1>::StorageType)> BindState; + + + return Callback<typename BindState::UnboundRunType>( + new BindState(internal::MakeRunnable(functor), p1)); +} + +template <typename Functor, typename P1, typename P2> +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits<Functor>::RunnableType, + typename internal::FunctorTraits<Functor>::RunType, + void(typename internal::CallbackParamTraits<P1>::StorageType, + typename internal::CallbackParamTraits<P2>::StorageType)> + ::UnboundRunType> +Bind(Functor functor, const P1& p1, const P2& p2) { + // Typedefs for how to store and run the functor. + typedef typename internal::FunctorTraits<Functor>::RunnableType RunnableType; + typedef typename internal::FunctorTraits<Functor>::RunType RunType; + + // Use RunnableType::RunType instead of RunType above because our + // checks should below for bound references need to know what the actual + // functor is going to interpret the argument as. + typedef internal::FunctionTraits<typename RunnableType::RunType> + BoundFunctorTraits; + + // Do not allow binding a non-const reference parameter. Non-const reference + // parameters are disallowed by the Google style guide. Also, binding a + // non-const reference parameter can make for subtle bugs because the + // invoked function will receive a reference to the stored copy of the + // argument and not the original. + COMPILE_ASSERT( + !(is_non_const_reference<typename BoundFunctorTraits::A1Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A2Type>::value ), + do_not_bind_functions_with_nonconst_ref); + + // For methods, we need to be careful for parameter 1. We do not require + // a scoped_refptr because BindState<> itself takes care of AddRef() for + // methods. We also disallow binding of an array as the method's target + // object. + COMPILE_ASSERT( + internal::HasIsMethodTag<RunnableType>::value || + !internal::NeedsScopedRefptrButGetsRawPtr<P1>::value, + p1_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag<RunnableType>::value || + !is_array<P1>::value, + first_bound_argument_to_method_cannot_be_array); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P2>::value, + p2_is_refcounted_type_and_needs_scoped_refptr); + typedef internal::BindState<RunnableType, RunType, + void(typename internal::CallbackParamTraits<P1>::StorageType, + typename internal::CallbackParamTraits<P2>::StorageType)> BindState; + + + return Callback<typename BindState::UnboundRunType>( + new BindState(internal::MakeRunnable(functor), p1, p2)); +} + +template <typename Functor, typename P1, typename P2, typename P3> +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits<Functor>::RunnableType, + typename internal::FunctorTraits<Functor>::RunType, + void(typename internal::CallbackParamTraits<P1>::StorageType, + typename internal::CallbackParamTraits<P2>::StorageType, + typename internal::CallbackParamTraits<P3>::StorageType)> + ::UnboundRunType> +Bind(Functor functor, const P1& p1, const P2& p2, const P3& p3) { + // Typedefs for how to store and run the functor. + typedef typename internal::FunctorTraits<Functor>::RunnableType RunnableType; + typedef typename internal::FunctorTraits<Functor>::RunType RunType; + + // Use RunnableType::RunType instead of RunType above because our + // checks should below for bound references need to know what the actual + // functor is going to interpret the argument as. + typedef internal::FunctionTraits<typename RunnableType::RunType> + BoundFunctorTraits; + + // Do not allow binding a non-const reference parameter. Non-const reference + // parameters are disallowed by the Google style guide. Also, binding a + // non-const reference parameter can make for subtle bugs because the + // invoked function will receive a reference to the stored copy of the + // argument and not the original. + COMPILE_ASSERT( + !(is_non_const_reference<typename BoundFunctorTraits::A1Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A2Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A3Type>::value ), + do_not_bind_functions_with_nonconst_ref); + + // For methods, we need to be careful for parameter 1. We do not require + // a scoped_refptr because BindState<> itself takes care of AddRef() for + // methods. We also disallow binding of an array as the method's target + // object. + COMPILE_ASSERT( + internal::HasIsMethodTag<RunnableType>::value || + !internal::NeedsScopedRefptrButGetsRawPtr<P1>::value, + p1_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag<RunnableType>::value || + !is_array<P1>::value, + first_bound_argument_to_method_cannot_be_array); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P2>::value, + p2_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P3>::value, + p3_is_refcounted_type_and_needs_scoped_refptr); + typedef internal::BindState<RunnableType, RunType, + void(typename internal::CallbackParamTraits<P1>::StorageType, + typename internal::CallbackParamTraits<P2>::StorageType, + typename internal::CallbackParamTraits<P3>::StorageType)> BindState; + + + return Callback<typename BindState::UnboundRunType>( + new BindState(internal::MakeRunnable(functor), p1, p2, p3)); +} + +template <typename Functor, typename P1, typename P2, typename P3, typename P4> +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits<Functor>::RunnableType, + typename internal::FunctorTraits<Functor>::RunType, + void(typename internal::CallbackParamTraits<P1>::StorageType, + typename internal::CallbackParamTraits<P2>::StorageType, + typename internal::CallbackParamTraits<P3>::StorageType, + typename internal::CallbackParamTraits<P4>::StorageType)> + ::UnboundRunType> +Bind(Functor functor, const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + // Typedefs for how to store and run the functor. + typedef typename internal::FunctorTraits<Functor>::RunnableType RunnableType; + typedef typename internal::FunctorTraits<Functor>::RunType RunType; + + // Use RunnableType::RunType instead of RunType above because our + // checks should below for bound references need to know what the actual + // functor is going to interpret the argument as. + typedef internal::FunctionTraits<typename RunnableType::RunType> + BoundFunctorTraits; + + // Do not allow binding a non-const reference parameter. Non-const reference + // parameters are disallowed by the Google style guide. Also, binding a + // non-const reference parameter can make for subtle bugs because the + // invoked function will receive a reference to the stored copy of the + // argument and not the original. + COMPILE_ASSERT( + !(is_non_const_reference<typename BoundFunctorTraits::A1Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A2Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A3Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A4Type>::value ), + do_not_bind_functions_with_nonconst_ref); + + // For methods, we need to be careful for parameter 1. We do not require + // a scoped_refptr because BindState<> itself takes care of AddRef() for + // methods. We also disallow binding of an array as the method's target + // object. + COMPILE_ASSERT( + internal::HasIsMethodTag<RunnableType>::value || + !internal::NeedsScopedRefptrButGetsRawPtr<P1>::value, + p1_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag<RunnableType>::value || + !is_array<P1>::value, + first_bound_argument_to_method_cannot_be_array); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P2>::value, + p2_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P3>::value, + p3_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P4>::value, + p4_is_refcounted_type_and_needs_scoped_refptr); + typedef internal::BindState<RunnableType, RunType, + void(typename internal::CallbackParamTraits<P1>::StorageType, + typename internal::CallbackParamTraits<P2>::StorageType, + typename internal::CallbackParamTraits<P3>::StorageType, + typename internal::CallbackParamTraits<P4>::StorageType)> BindState; + + + return Callback<typename BindState::UnboundRunType>( + new BindState(internal::MakeRunnable(functor), p1, p2, p3, p4)); +} + +template <typename Functor, typename P1, typename P2, typename P3, typename P4, + typename P5> +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits<Functor>::RunnableType, + typename internal::FunctorTraits<Functor>::RunType, + void(typename internal::CallbackParamTraits<P1>::StorageType, + typename internal::CallbackParamTraits<P2>::StorageType, + typename internal::CallbackParamTraits<P3>::StorageType, + typename internal::CallbackParamTraits<P4>::StorageType, + typename internal::CallbackParamTraits<P5>::StorageType)> + ::UnboundRunType> +Bind(Functor functor, const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5) { + // Typedefs for how to store and run the functor. + typedef typename internal::FunctorTraits<Functor>::RunnableType RunnableType; + typedef typename internal::FunctorTraits<Functor>::RunType RunType; + + // Use RunnableType::RunType instead of RunType above because our + // checks should below for bound references need to know what the actual + // functor is going to interpret the argument as. + typedef internal::FunctionTraits<typename RunnableType::RunType> + BoundFunctorTraits; + + // Do not allow binding a non-const reference parameter. Non-const reference + // parameters are disallowed by the Google style guide. Also, binding a + // non-const reference parameter can make for subtle bugs because the + // invoked function will receive a reference to the stored copy of the + // argument and not the original. + COMPILE_ASSERT( + !(is_non_const_reference<typename BoundFunctorTraits::A1Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A2Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A3Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A4Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A5Type>::value ), + do_not_bind_functions_with_nonconst_ref); + + // For methods, we need to be careful for parameter 1. We do not require + // a scoped_refptr because BindState<> itself takes care of AddRef() for + // methods. We also disallow binding of an array as the method's target + // object. + COMPILE_ASSERT( + internal::HasIsMethodTag<RunnableType>::value || + !internal::NeedsScopedRefptrButGetsRawPtr<P1>::value, + p1_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag<RunnableType>::value || + !is_array<P1>::value, + first_bound_argument_to_method_cannot_be_array); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P2>::value, + p2_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P3>::value, + p3_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P4>::value, + p4_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P5>::value, + p5_is_refcounted_type_and_needs_scoped_refptr); + typedef internal::BindState<RunnableType, RunType, + void(typename internal::CallbackParamTraits<P1>::StorageType, + typename internal::CallbackParamTraits<P2>::StorageType, + typename internal::CallbackParamTraits<P3>::StorageType, + typename internal::CallbackParamTraits<P4>::StorageType, + typename internal::CallbackParamTraits<P5>::StorageType)> BindState; + + + return Callback<typename BindState::UnboundRunType>( + new BindState(internal::MakeRunnable(functor), p1, p2, p3, p4, p5)); +} + +template <typename Functor, typename P1, typename P2, typename P3, typename P4, + typename P5, typename P6> +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits<Functor>::RunnableType, + typename internal::FunctorTraits<Functor>::RunType, + void(typename internal::CallbackParamTraits<P1>::StorageType, + typename internal::CallbackParamTraits<P2>::StorageType, + typename internal::CallbackParamTraits<P3>::StorageType, + typename internal::CallbackParamTraits<P4>::StorageType, + typename internal::CallbackParamTraits<P5>::StorageType, + typename internal::CallbackParamTraits<P6>::StorageType)> + ::UnboundRunType> +Bind(Functor functor, const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5, const P6& p6) { + // Typedefs for how to store and run the functor. + typedef typename internal::FunctorTraits<Functor>::RunnableType RunnableType; + typedef typename internal::FunctorTraits<Functor>::RunType RunType; + + // Use RunnableType::RunType instead of RunType above because our + // checks should below for bound references need to know what the actual + // functor is going to interpret the argument as. + typedef internal::FunctionTraits<typename RunnableType::RunType> + BoundFunctorTraits; + + // Do not allow binding a non-const reference parameter. Non-const reference + // parameters are disallowed by the Google style guide. Also, binding a + // non-const reference parameter can make for subtle bugs because the + // invoked function will receive a reference to the stored copy of the + // argument and not the original. + COMPILE_ASSERT( + !(is_non_const_reference<typename BoundFunctorTraits::A1Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A2Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A3Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A4Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A5Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A6Type>::value ), + do_not_bind_functions_with_nonconst_ref); + + // For methods, we need to be careful for parameter 1. We do not require + // a scoped_refptr because BindState<> itself takes care of AddRef() for + // methods. We also disallow binding of an array as the method's target + // object. + COMPILE_ASSERT( + internal::HasIsMethodTag<RunnableType>::value || + !internal::NeedsScopedRefptrButGetsRawPtr<P1>::value, + p1_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag<RunnableType>::value || + !is_array<P1>::value, + first_bound_argument_to_method_cannot_be_array); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P2>::value, + p2_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P3>::value, + p3_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P4>::value, + p4_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P5>::value, + p5_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P6>::value, + p6_is_refcounted_type_and_needs_scoped_refptr); + typedef internal::BindState<RunnableType, RunType, + void(typename internal::CallbackParamTraits<P1>::StorageType, + typename internal::CallbackParamTraits<P2>::StorageType, + typename internal::CallbackParamTraits<P3>::StorageType, + typename internal::CallbackParamTraits<P4>::StorageType, + typename internal::CallbackParamTraits<P5>::StorageType, + typename internal::CallbackParamTraits<P6>::StorageType)> BindState; + + + return Callback<typename BindState::UnboundRunType>( + new BindState(internal::MakeRunnable(functor), p1, p2, p3, p4, p5, p6)); +} + +template <typename Functor, typename P1, typename P2, typename P3, typename P4, + typename P5, typename P6, typename P7> +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits<Functor>::RunnableType, + typename internal::FunctorTraits<Functor>::RunType, + void(typename internal::CallbackParamTraits<P1>::StorageType, + typename internal::CallbackParamTraits<P2>::StorageType, + typename internal::CallbackParamTraits<P3>::StorageType, + typename internal::CallbackParamTraits<P4>::StorageType, + typename internal::CallbackParamTraits<P5>::StorageType, + typename internal::CallbackParamTraits<P6>::StorageType, + typename internal::CallbackParamTraits<P7>::StorageType)> + ::UnboundRunType> +Bind(Functor functor, const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5, const P6& p6, const P7& p7) { + // Typedefs for how to store and run the functor. + typedef typename internal::FunctorTraits<Functor>::RunnableType RunnableType; + typedef typename internal::FunctorTraits<Functor>::RunType RunType; + + // Use RunnableType::RunType instead of RunType above because our + // checks should below for bound references need to know what the actual + // functor is going to interpret the argument as. + typedef internal::FunctionTraits<typename RunnableType::RunType> + BoundFunctorTraits; + + // Do not allow binding a non-const reference parameter. Non-const reference + // parameters are disallowed by the Google style guide. Also, binding a + // non-const reference parameter can make for subtle bugs because the + // invoked function will receive a reference to the stored copy of the + // argument and not the original. + COMPILE_ASSERT( + !(is_non_const_reference<typename BoundFunctorTraits::A1Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A2Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A3Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A4Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A5Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A6Type>::value || + is_non_const_reference<typename BoundFunctorTraits::A7Type>::value ), + do_not_bind_functions_with_nonconst_ref); + + // For methods, we need to be careful for parameter 1. We do not require + // a scoped_refptr because BindState<> itself takes care of AddRef() for + // methods. We also disallow binding of an array as the method's target + // object. + COMPILE_ASSERT( + internal::HasIsMethodTag<RunnableType>::value || + !internal::NeedsScopedRefptrButGetsRawPtr<P1>::value, + p1_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag<RunnableType>::value || + !is_array<P1>::value, + first_bound_argument_to_method_cannot_be_array); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P2>::value, + p2_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P3>::value, + p3_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P4>::value, + p4_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P5>::value, + p5_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P6>::value, + p6_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P7>::value, + p7_is_refcounted_type_and_needs_scoped_refptr); + typedef internal::BindState<RunnableType, RunType, + void(typename internal::CallbackParamTraits<P1>::StorageType, + typename internal::CallbackParamTraits<P2>::StorageType, + typename internal::CallbackParamTraits<P3>::StorageType, + typename internal::CallbackParamTraits<P4>::StorageType, + typename internal::CallbackParamTraits<P5>::StorageType, + typename internal::CallbackParamTraits<P6>::StorageType, + typename internal::CallbackParamTraits<P7>::StorageType)> BindState; + + + return Callback<typename BindState::UnboundRunType>( + new BindState(internal::MakeRunnable(functor), p1, p2, p3, p4, p5, p6, + p7)); +} + +} // namespace base + +#endif // BASE_BIND_H_
diff --git a/src/base/bind.h.pump b/src/base/bind.h.pump new file mode 100644 index 0000000..b321649 --- /dev/null +++ b/src/base/bind.h.pump
@@ -0,0 +1,153 @@ +$$ 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 +$$ + +$$ +$$ MAX_ARITY controls the number of arguments that Bind() supports. +$$ The amount of code, and more importantly, the number of template types +$$ generated by pump grows at O(MAX_ARITY^2). +$$ +$$ We tried going to 11 and found it imposed an extra 10 penalty on windows +$$ cycle times compared to our original baseline of 6. +$$ +$$ Currently 7 is chosen as a compromise between supporting a convenient +$$ number of arguments and keeping compile times low. At 7, we have 115 +$$ templates being generated by pump. +$$ +$$ Be careful when adjusting this number. If people find a need to bind +$$ a larger number of arguments, consider refactoring the function to use +$$ a param struct instead of raising the MAX_ARITY. +$$ +$$ See http://crbug.com/98542 for more context. +$$ +$var MAX_ARITY = 7 + +// 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. + +#ifndef BASE_BIND_H_ +#define BASE_BIND_H_ + +#include "base/bind_internal.h" +#include "base/callback_internal.h" + +// ----------------------------------------------------------------------------- +// Usage documentation +// ----------------------------------------------------------------------------- +// +// See base/callback.h for documentation. +// +// +// ----------------------------------------------------------------------------- +// Implementation notes +// ----------------------------------------------------------------------------- +// +// If you're reading the implementation, before proceeding further, you should +// read the top comment of base/bind_internal.h for a definition of common +// terms and concepts. +// +// RETURN TYPES +// +// Though Bind()'s result is meant to be stored in a Callback<> type, it +// cannot actually return the exact type without requiring a large amount +// of extra template specializations. The problem is that in order to +// discern the correct specialization of Callback<>, Bind would need to +// unwrap the function signature to determine the signature's arity, and +// whether or not it is a method. +// +// Each unique combination of (arity, function_type, num_prebound) where +// function_type is one of {function, method, const_method} would require +// one specialization. We eventually have to do a similar number of +// specializations anyways in the implementation (see the Invoker<>, +// classes). However, it is avoidable in Bind if we return the result +// via an indirection like we do below. +// +// TODO(ajwong): We might be able to avoid this now, but need to test. +// +// It is possible to move most of the COMPILE_ASSERT asserts into BindState<>, +// but it feels a little nicer to have the asserts here so people do not +// need to crack open bind_internal.h. On the other hand, it makes Bind() +// harder to read. + +namespace base { + +$range ARITY 0..MAX_ARITY +$for ARITY [[ +$range ARG 1..ARITY + +template <typename Functor[[]] +$if ARITY > 0 [[, ]] $for ARG , [[typename P$(ARG)]]> +base::Callback< + typename internal::BindState< + typename internal::FunctorTraits<Functor>::RunnableType, + typename internal::FunctorTraits<Functor>::RunType, + void($for ARG , [[typename internal::CallbackParamTraits<P$(ARG)>::StorageType]])> + ::UnboundRunType> +Bind(Functor functor +$if ARITY > 0 [[, ]] $for ARG , [[const P$(ARG)& p$(ARG)]]) { + // Typedefs for how to store and run the functor. + typedef typename internal::FunctorTraits<Functor>::RunnableType RunnableType; + typedef typename internal::FunctorTraits<Functor>::RunType RunType; + + // Use RunnableType::RunType instead of RunType above because our + // checks should below for bound references need to know what the actual + // functor is going to interpret the argument as. + typedef internal::FunctionTraits<typename RunnableType::RunType> + BoundFunctorTraits; + +$if ARITY > 0 [[ + + // Do not allow binding a non-const reference parameter. Non-const reference + // parameters are disallowed by the Google style guide. Also, binding a + // non-const reference parameter can make for subtle bugs because the + // invoked function will receive a reference to the stored copy of the + // argument and not the original. + COMPILE_ASSERT( + !($for ARG || [[ +is_non_const_reference<typename BoundFunctorTraits::A$(ARG)Type>::value ]]), + do_not_bind_functions_with_nonconst_ref); + +]] + + +$for ARG [[ + + +$if ARG == 1 [[ + // For methods, we need to be careful for parameter 1. We do not require + // a scoped_refptr because BindState<> itself takes care of AddRef() for + // methods. We also disallow binding of an array as the method's target + // object. + COMPILE_ASSERT( + internal::HasIsMethodTag<RunnableType>::value || + !internal::NeedsScopedRefptrButGetsRawPtr<P$(ARG)>::value, + p$(ARG)_is_refcounted_type_and_needs_scoped_refptr); + COMPILE_ASSERT(!internal::HasIsMethodTag<RunnableType>::value || + !is_array<P$(ARG)>::value, + first_bound_argument_to_method_cannot_be_array); +]] $else [[ + COMPILE_ASSERT(!internal::NeedsScopedRefptrButGetsRawPtr<P$(ARG)>::value, + p$(ARG)_is_refcounted_type_and_needs_scoped_refptr); +]] $$ $if ARG + +]] $$ $for ARG + + typedef internal::BindState<RunnableType, RunType, [[]] +void($for ARG , [[typename internal::CallbackParamTraits<P$(ARG)>::StorageType]])> [[]] +BindState; + + + return Callback<typename BindState::UnboundRunType>( + new BindState(internal::MakeRunnable(functor)[[]] +$if ARITY > 0 [[, ]] $for ARG , [[p$(ARG)]])); +} + +]] $$ for ARITY + +} // namespace base + +#endif // BASE_BIND_H_
diff --git a/src/base/bind_helpers.cc b/src/base/bind_helpers.cc new file mode 100644 index 0000000..f2fc3bb --- /dev/null +++ b/src/base/bind_helpers.cc
@@ -0,0 +1,29 @@ +// 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/bind_helpers.h" + +#include "base/callback.h" + +namespace base { + +void DoNothing() { +} + +ScopedClosureRunner::ScopedClosureRunner(const Closure& closure) + : closure_(closure) { +} + +ScopedClosureRunner::~ScopedClosureRunner() { + if (!closure_.is_null()) + closure_.Run(); +} + +Closure ScopedClosureRunner::Release() { + Closure result = closure_; + closure_.Reset(); + return result; +} + +} // namespace base
diff --git a/src/base/bind_helpers.h b/src/base/bind_helpers.h new file mode 100644 index 0000000..b60a56e --- /dev/null +++ b/src/base/bind_helpers.h
@@ -0,0 +1,563 @@ +// 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 defines a set of argument wrappers and related factory methods that +// can be used specify the refcounting and reference semantics of arguments +// that are bound by the Bind() function in base/bind.h. +// +// It also defines a set of simple functions and utilities that people want +// when using Callback<> and Bind(). +// +// +// ARGUMENT BINDING WRAPPERS +// +// The wrapper functions are base::Unretained(), base::Owned(), bass::Passed(), +// base::ConstRef(), and base::IgnoreResult(). +// +// Unretained() allows Bind() to bind a non-refcounted class, and to disable +// refcounting on arguments that are refcounted objects. +// +// Owned() transfers ownership of an object to the Callback resulting from +// bind; the object will be deleted when the Callback is deleted. +// +// Passed() is for transferring movable-but-not-copyable types (eg. scoped_ptr) +// through a Callback. Logically, this signifies a destructive transfer of +// the state of the argument into the target function. Invoking +// Callback::Run() twice on a Callback that was created with a Passed() +// argument will CHECK() because the first invocation would have already +// transferred ownership to the target function. +// +// ConstRef() allows binding a constant reference to an argument rather +// than a copy. +// +// IgnoreResult() is used to adapt a function or Callback with a return type to +// one with a void return. This is most useful if you have a function with, +// say, a pesky ignorable bool return that you want to use with PostTask or +// something else that expect a Callback with a void return. +// +// EXAMPLE OF Unretained(): +// +// class Foo { +// public: +// void func() { cout << "Foo:f" << endl; } +// }; +// +// // In some function somewhere. +// Foo foo; +// Closure foo_callback = +// Bind(&Foo::func, Unretained(&foo)); +// foo_callback.Run(); // Prints "Foo:f". +// +// Without the Unretained() wrapper on |&foo|, the above call would fail +// to compile because Foo does not support the AddRef() and Release() methods. +// +// +// EXAMPLE OF Owned(): +// +// void foo(int* arg) { cout << *arg << endl } +// +// int* pn = new int(1); +// Closure foo_callback = Bind(&foo, Owned(pn)); +// +// foo_callback.Run(); // Prints "1" +// foo_callback.Run(); // Prints "1" +// *n = 2; +// foo_callback.Run(); // Prints "2" +// +// foo_callback.Reset(); // |pn| is deleted. Also will happen when +// // |foo_callback| goes out of scope. +// +// Without Owned(), someone would have to know to delete |pn| when the last +// reference to the Callback is deleted. +// +// +// EXAMPLE OF ConstRef(): +// +// void foo(int arg) { cout << arg << endl } +// +// int n = 1; +// Closure no_ref = Bind(&foo, n); +// Closure has_ref = Bind(&foo, ConstRef(n)); +// +// no_ref.Run(); // Prints "1" +// has_ref.Run(); // Prints "1" +// +// n = 2; +// no_ref.Run(); // Prints "1" +// has_ref.Run(); // Prints "2" +// +// Note that because ConstRef() takes a reference on |n|, |n| must outlive all +// its bound callbacks. +// +// +// EXAMPLE OF IgnoreResult(): +// +// int DoSomething(int arg) { cout << arg << endl; } +// +// // Assign to a Callback with a void return type. +// Callback<void(int)> cb = Bind(IgnoreResult(&DoSomething)); +// cb->Run(1); // Prints "1". +// +// // Prints "1" on |ml|. +// ml->PostTask(FROM_HERE, Bind(IgnoreResult(&DoSomething), 1); +// +// +// EXAMPLE OF Passed(): +// +// void TakesOwnership(scoped_ptr<Foo> arg) { } +// scoped_ptr<Foo> CreateFoo() { return scoped_ptr<Foo>(new Foo()); } +// +// scoped_ptr<Foo> f(new Foo()); +// +// // |cb| is given ownership of Foo(). |f| is now NULL. +// // You can use f.Pass() in place of &f, but it's more verbose. +// Closure cb = Bind(&TakesOwnership, Passed(&f)); +// +// // Run was never called so |cb| still owns Foo() and deletes +// // it on Reset(). +// cb.Reset(); +// +// // |cb| is given a new Foo created by CreateFoo(). +// cb = Bind(&TakesOwnership, Passed(CreateFoo())); +// +// // |arg| in TakesOwnership() is given ownership of Foo(). |cb| +// // no longer owns Foo() and, if reset, would not delete Foo(). +// cb.Run(); // Foo() is now transferred to |arg| and deleted. +// cb.Run(); // This CHECK()s since Foo() already been used once. +// +// Passed() is particularly useful with PostTask() when you are transferring +// ownership of an argument into a task, but don't necessarily know if the +// task will always be executed. This can happen if the task is cancellable +// or if it is posted to a MessageLoopProxy. +// +// +// SIMPLE FUNCTIONS AND UTILITIES. +// +// DoNothing() - Useful for creating a Closure that does nothing when called. +// DeletePointer<T>() - Useful for creating a Closure that will delete a +// pointer when invoked. Only use this when necessary. +// In most cases MessageLoop::DeleteSoon() is a better +// fit. +// ScopedClosureRunner - Scoper object that runs the wrapped closure when it +// goes out of scope. It's conceptually similar to +// scoped_ptr<> but calls Run() instead of deleting +// the pointer. + +#ifndef BASE_BIND_HELPERS_H_ +#define BASE_BIND_HELPERS_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/template_util.h" + +namespace base { +namespace internal { + +// Use the Substitution Failure Is Not An Error (SFINAE) trick to inspect T +// for the existence of AddRef() and Release() functions of the correct +// signature. +// +// http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error +// http://stackoverflow.com/questions/257288/is-it-possible-to-write-a-c-template-to-check-for-a-functions-existence +// http://stackoverflow.com/questions/4358584/sfinae-approach-comparison +// http://stackoverflow.com/questions/1966362/sfinae-to-check-for-inherited-member-functions +// +// The last link in particular show the method used below. +// +// For SFINAE to work with inherited methods, we need to pull some extra tricks +// with multiple inheritance. In the more standard formulation, the overloads +// of Check would be: +// +// template <typename C> +// Yes NotTheCheckWeWant(Helper<&C::TargetFunc>*); +// +// template <typename C> +// No NotTheCheckWeWant(...); +// +// static const bool value = sizeof(NotTheCheckWeWant<T>(0)) == sizeof(Yes); +// +// The problem here is that template resolution will not match +// C::TargetFunc if TargetFunc does not exist directly in C. That is, if +// TargetFunc in inherited from an ancestor, &C::TargetFunc will not match, +// |value| will be false. This formulation only checks for whether or +// not TargetFunc exist directly in the class being introspected. +// +// To get around this, we play a dirty trick with multiple inheritance. +// First, We create a class BaseMixin that declares each function that we +// want to probe for. Then we create a class Base that inherits from both T +// (the class we wish to probe) and BaseMixin. Note that the function +// signature in BaseMixin does not need to match the signature of the function +// we are probing for; thus it's easiest to just use void(void). +// +// Now, if TargetFunc exists somewhere in T, then &Base::TargetFunc has an +// ambiguous resolution between BaseMixin and T. This lets us write the +// following: +// +// template <typename C> +// No GoodCheck(Helper<&C::TargetFunc>*); +// +// template <typename C> +// Yes GoodCheck(...); +// +// static const bool value = sizeof(GoodCheck<Base>(0)) == sizeof(Yes); +// +// Notice here that the variadic version of GoodCheck() returns Yes here +// instead of No like the previous one. Also notice that we calculate |value| +// by specializing GoodCheck() on Base instead of T. +// +// We've reversed the roles of the variadic, and Helper overloads. +// GoodCheck(Helper<&C::TargetFunc>*), when C = Base, fails to be a valid +// substitution if T::TargetFunc exists. Thus GoodCheck<Base>(0) will resolve +// to the variadic version if T has TargetFunc. If T::TargetFunc does not +// exist, then &C::TargetFunc is not ambiguous, and the overload resolution +// will prefer GoodCheck(Helper<&C::TargetFunc>*). +// +// This method of SFINAE will correctly probe for inherited names, but it cannot +// typecheck those names. It's still a good enough sanity check though. +// +// Works on gcc-4.2, gcc-4.4, and Visual Studio 2008. +// +// TODO(ajwong): Move to ref_counted.h or template_util.h when we've vetted +// this works well. +// +// TODO(ajwong): Make this check for Release() as well. +// See http://crbug.com/82038. +template <typename T> +class SupportsAddRefAndRelease { + typedef char Yes[1]; + typedef char No[2]; + + struct BaseMixin { + void AddRef(); + }; + +// MSVC warns when you try to use Base if T has a private destructor, the +// common pattern for refcounted types. It does this even though no attempt to +// instantiate Base is made. We disable the warning for this definition. +#if defined(OS_WIN) || defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4624) +#endif + struct Base : public T, public BaseMixin { + }; +#if defined(OS_WIN) || defined(COBALT_WIN) +#pragma warning(pop) +#endif + + template <void(BaseMixin::*)(void)> struct Helper {}; + + template <typename C> + static No& Check(Helper<&C::AddRef>*); + + template <typename > + static Yes& Check(...); + + public: + static const bool value = sizeof(Check<Base>(0)) == sizeof(Yes); +}; + +// Helpers to assert that arguments of a recounted type are bound with a +// scoped_refptr. +template <bool IsClasstype, typename T> +struct UnsafeBindtoRefCountedArgHelper : false_type { +}; + +template <typename T> +struct UnsafeBindtoRefCountedArgHelper<true, T> + : integral_constant<bool, SupportsAddRefAndRelease<T>::value> { +}; + +template <typename T> +struct UnsafeBindtoRefCountedArg : false_type { +}; + +template <typename T> +struct UnsafeBindtoRefCountedArg<T*> + : UnsafeBindtoRefCountedArgHelper<is_class<T>::value, T> { +}; + +template <typename T> +class HasIsMethodTag { + typedef char Yes[1]; + typedef char No[2]; + + template <typename U> + static Yes& Check(typename U::IsMethod*); + + template <typename U> + static No& Check(...); + + public: + static const bool value = sizeof(Check<T>(0)) == sizeof(Yes); +}; + +template <typename T> +class UnretainedWrapper { + public: + explicit UnretainedWrapper(T* o) : ptr_(o) {} + T* get() const { return ptr_; } + private: + T* ptr_; +}; + +template <typename T> +class ConstRefWrapper { + public: + explicit ConstRefWrapper(const T& o) : ptr_(&o) {} + const T& get() const { return *ptr_; } + private: + const T* ptr_; +}; + +template <typename T> +struct IgnoreResultHelper { + explicit IgnoreResultHelper(T functor) : functor_(functor) {} + + T functor_; +}; + +template <typename T> +struct IgnoreResultHelper<Callback<T> > { + explicit IgnoreResultHelper(const Callback<T>& functor) : functor_(functor) {} + + const Callback<T>& functor_; +}; + +// An alternate implementation is to avoid the destructive copy, and instead +// specialize ParamTraits<> for OwnedWrapper<> to change the StorageType to +// a class that is essentially a scoped_ptr<>. +// +// The current implementation has the benefit though of leaving ParamTraits<> +// fully in callback_internal.h as well as avoiding type conversions during +// storage. +template <typename T> +class OwnedWrapper { + public: + explicit OwnedWrapper(T* o) : ptr_(o) {} + ~OwnedWrapper() { delete ptr_; } + T* get() const { return ptr_; } + OwnedWrapper(const OwnedWrapper& other) { + ptr_ = other.ptr_; + other.ptr_ = NULL; + } + + private: + mutable T* ptr_; +}; + +// PassedWrapper is a copyable adapter for a scoper that ignores const. +// +// It is needed to get around the fact that Bind() takes a const reference to +// all its arguments. Because Bind() takes a const reference to avoid +// unnecessary copies, it is incompatible with movable-but-not-copyable +// types; doing a destructive "move" of the type into Bind() would violate +// the const correctness. +// +// This conundrum cannot be solved without either C++11 rvalue references or +// a O(2^n) blowup of Bind() templates to handle each combination of regular +// types and movable-but-not-copyable types. Thus we introduce a wrapper type +// that is copyable to transmit the correct type information down into +// BindState<>. Ignoring const in this type makes sense because it is only +// created when we are explicitly trying to do a destructive move. +// +// Two notes: +// 1) PassedWrapper supports any type that has a "Pass()" function. +// This is intentional. The whitelisting of which specific types we +// support is maintained by CallbackParamTraits<>. +// 2) is_valid_ is distinct from NULL because it is valid to bind a "NULL" +// scoper to a Callback and allow the Callback to execute once. +template <typename T> +class PassedWrapper { + public: + explicit PassedWrapper(T scoper) : is_valid_(true), scoper_(scoper.Pass()) {} + PassedWrapper(const PassedWrapper& other) + : is_valid_(other.is_valid_), scoper_(other.scoper_.Pass()) { + } + T Pass() const { + CHECK(is_valid_); + is_valid_ = false; + return scoper_.Pass(); + } + + private: + mutable bool is_valid_; + mutable T scoper_; +}; + +// Unwrap the stored parameters for the wrappers above. +template <typename T> +struct UnwrapTraits { + typedef const T& ForwardType; + static ForwardType Unwrap(const T& o) { return o; } +}; + +template <typename T> +struct UnwrapTraits<UnretainedWrapper<T> > { + typedef T* ForwardType; + static ForwardType Unwrap(UnretainedWrapper<T> unretained) { + return unretained.get(); + } +}; + +template <typename T> +struct UnwrapTraits<ConstRefWrapper<T> > { + typedef const T& ForwardType; + static ForwardType Unwrap(ConstRefWrapper<T> const_ref) { + return const_ref.get(); + } +}; + +template <typename T> +struct UnwrapTraits<scoped_refptr<T> > { + typedef T* ForwardType; + static ForwardType Unwrap(const scoped_refptr<T>& o) { return o.get(); } +}; + +template <typename T> +struct UnwrapTraits<WeakPtr<T> > { + typedef const WeakPtr<T>& ForwardType; + static ForwardType Unwrap(const WeakPtr<T>& o) { return o; } +}; + +template <typename T> +struct UnwrapTraits<OwnedWrapper<T> > { + typedef T* ForwardType; + static ForwardType Unwrap(const OwnedWrapper<T>& o) { + return o.get(); + } +}; + +template <typename T> +struct UnwrapTraits<PassedWrapper<T> > { + typedef T ForwardType; + static T Unwrap(PassedWrapper<T>& o) { + return o.Pass(); + } +}; + +// Utility for handling different refcounting semantics in the Bind() +// function. +template <bool is_method, typename T> +struct MaybeRefcount; + +template <typename T> +struct MaybeRefcount<false, T> { + static void AddRef(const T&) {} + static void Release(const T&) {} +}; + +template <typename T, size_t n> +struct MaybeRefcount<false, T[n]> { + static void AddRef(const T*) {} + static void Release(const T*) {} +}; + +template <typename T> +struct MaybeRefcount<true, T> { + static void AddRef(const T&) {} + static void Release(const T&) {} +}; + +template <typename T> +struct MaybeRefcount<true, T*> { + static void AddRef(T* o) { o->AddRef(); } + static void Release(T* o) { o->Release(); } +}; + +// No need to additionally AddRef() and Release() since we are storing a +// scoped_refptr<> inside the storage object already. +template <typename T> +struct MaybeRefcount<true, scoped_refptr<T> > { + static void AddRef(const scoped_refptr<T>&) {} + static void Release(const scoped_refptr<T>&) {} +}; + +template <typename T> +struct MaybeRefcount<true, const T*> { + static void AddRef(const T* o) { o->AddRef(); } + static void Release(const T* o) { o->Release(); } +}; + +// IsWeakMethod is a helper that determine if we are binding a WeakPtr<> to a +// method. It is used internally by Bind() to select the correct +// InvokeHelper that will no-op itself in the event the WeakPtr<> for +// the target object is invalidated. +// +// P1 should be the type of the object that will be received of the method. +template <bool IsMethod, typename P1> +struct IsWeakMethod : public false_type {}; + +template <typename T> +struct IsWeakMethod<true, WeakPtr<T> > : public true_type {}; + +template <typename T> +struct IsWeakMethod<true, ConstRefWrapper<WeakPtr<T> > > : public true_type {}; + +} // namespace internal + +template <typename T> +static inline internal::UnretainedWrapper<T> Unretained(T* o) { + return internal::UnretainedWrapper<T>(o); +} + +template <typename T> +static inline internal::ConstRefWrapper<T> ConstRef(const T& o) { + return internal::ConstRefWrapper<T>(o); +} + +template <typename T> +static inline internal::OwnedWrapper<T> Owned(T* o) { + return internal::OwnedWrapper<T>(o); +} + +// We offer 2 syntaxes for calling Passed(). The first takes a temporary and +// is best suited for use with the return value of a function. The second +// takes a pointer to the scoper and is just syntactic sugar to avoid having +// to write Passed(scoper.Pass()). +template <typename T> +static inline internal::PassedWrapper<T> Passed(T scoper) { + return internal::PassedWrapper<T>(scoper.Pass()); +} +template <typename T> +static inline internal::PassedWrapper<T> Passed(T* scoper) { + return internal::PassedWrapper<T>(scoper->Pass()); +} + +template <typename T> +static inline internal::IgnoreResultHelper<T> IgnoreResult(T data) { + return internal::IgnoreResultHelper<T>(data); +} + +template <typename T> +static inline internal::IgnoreResultHelper<Callback<T> > +IgnoreResult(const Callback<T>& data) { + return internal::IgnoreResultHelper<Callback<T> >(data); +} + +BASE_EXPORT void DoNothing(); + +template<typename T> +void DeletePointer(T* obj) { + delete obj; +} + +// ScopedClosureRunner is akin to scoped_ptr for Closures. It ensures that the +// Closure is executed and deleted no matter how the current scope exits. +class BASE_EXPORT ScopedClosureRunner { + public: + explicit ScopedClosureRunner(const Closure& closure); + ~ScopedClosureRunner(); + + Closure Release(); + + private: + Closure closure_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(ScopedClosureRunner); +}; + +} // namespace base + +#endif // BASE_BIND_HELPERS_H_
diff --git a/src/base/bind_helpers_unittest.cc b/src/base/bind_helpers_unittest.cc new file mode 100644 index 0000000..3ef2d75 --- /dev/null +++ b/src/base/bind_helpers_unittest.cc
@@ -0,0 +1,39 @@ +// 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/bind_helpers.h" + +#include "base/callback.h" +#include "base/bind.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +void Increment(int* value) { + (*value)++; +} + +TEST(BindHelpersTest, TestScopedClosureRunnerExitScope) { + int run_count = 0; + { + base::ScopedClosureRunner runner(base::Bind(&Increment, &run_count)); + EXPECT_EQ(0, run_count); + } + EXPECT_EQ(1, run_count); +} + +TEST(BindHelpersTest, TestScopedClosureRunnerRelease) { + int run_count = 0; + base::Closure c; + { + base::ScopedClosureRunner runner(base::Bind(&Increment, &run_count)); + c = runner.Release(); + EXPECT_EQ(0, run_count); + } + EXPECT_EQ(0, run_count); + c.Run(); + EXPECT_EQ(1, run_count); +} + +} // namespace
diff --git a/src/base/bind_internal.h b/src/base/bind_internal.h new file mode 100644 index 0000000..b87879f --- /dev/null +++ b/src/base/bind_internal.h
@@ -0,0 +1,2800 @@ +// This file was GENERATED by command: +// pump.py bind_internal.h.pump +// DO NOT EDIT BY HAND!!! + + +// 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. + +#ifndef BASE_BIND_INTERNAL_H_ +#define BASE_BIND_INTERNAL_H_ + +#include "base/bind_helpers.h" +#include "base/callback_internal.h" +#include "base/memory/raw_scoped_refptr_mismatch_checker.h" +#include "base/memory/weak_ptr.h" +#include "base/template_util.h" +#include "build/build_config.h" + +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) +// Check for C++11 support +#if (_MSC_VER >= 1700) || (__cplusplus > 199711L) +#include "base/bind_internal_functor.h" +#endif +#endif + +#if defined(OS_WIN) +#include "base/bind_internal_win.h" +#endif + +namespace base { +namespace internal { + +// See base/callback.h for user documentation. +// +// +// CONCEPTS: +// Runnable -- A type (really a type class) that has a single Run() method +// and a RunType typedef that corresponds to the type of Run(). +// A Runnable can declare that it should treated like a method +// call by including a typedef named IsMethod. The value of +// this typedef is NOT inspected, only the existence. When a +// Runnable declares itself a method, Bind() will enforce special +// refcounting + WeakPtr handling semantics for the first +// parameter which is expected to be an object. +// Functor -- A copyable type representing something that should be called. +// All function pointers, Callback<>, and Runnables are functors +// even if the invocation syntax differs. +// RunType -- A function type (as opposed to function _pointer_ type) for +// a Run() function. Usually just a convenience typedef. +// (Bound)ArgsType -- A function type that is being (ab)used to store the +// types of set of arguments. The "return" type is always +// void here. We use this hack so that we do not need +// a new type name for each arity of type. (eg., +// BindState1, BindState2). This makes forward +// declarations and friending much much easier. +// +// Types: +// RunnableAdapter<> -- Wraps the various "function" pointer types into an +// object that adheres to the Runnable interface. +// There are |3*ARITY| RunnableAdapter types. +// FunctionTraits<> -- Type traits that unwrap a function signature into a +// a set of easier to use typedefs. Used mainly for +// compile time asserts. +// There are |ARITY| FunctionTraits types. +// ForceVoidReturn<> -- Helper class for translating function signatures to +// equivalent forms with a "void" return type. +// There are |ARITY| ForceVoidReturn types. +// FunctorTraits<> -- Type traits used determine the correct RunType and +// RunnableType for a Functor. This is where function +// signature adapters are applied. +// There are |ARITY| ForceVoidReturn types. +// MakeRunnable<> -- Takes a Functor and returns an object in the Runnable +// type class that represents the underlying Functor. +// There are |O(1)| MakeRunnable types. +// InvokeHelper<> -- Take a Runnable + arguments and actully invokes it. +// Handle the differing syntaxes needed for WeakPtr<> support, +// and for ignoring return values. This is separate from +// Invoker to avoid creating multiple version of Invoker<> +// which grows at O(n^2) with the arity. +// There are |k*ARITY| InvokeHelper types. +// Invoker<> -- Unwraps the curried parameters and executes the Runnable. +// There are |(ARITY^2 + ARITY)/2| Invoketypes. +// BindState<> -- Stores the curried parameters, and is the main entry point +// into the Bind() system, doing most of the type resolution. +// There are ARITY BindState types. + +// RunnableAdapter<> +// +// The RunnableAdapter<> templates provide a uniform interface for invoking +// a function pointer, method pointer, or const method pointer. The adapter +// exposes a Run() method with an appropriate signature. Using this wrapper +// allows for writing code that supports all three pointer types without +// undue repetition. Without it, a lot of code would need to be repeated 3 +// times. +// +// For method pointers and const method pointers the first argument to Run() +// is considered to be the received of the method. This is similar to STL's +// mem_fun(). +// +// This class also exposes a RunType typedef that is the function type of the +// Run() function. +// +// If and only if the wrapper contains a method or const method pointer, an +// IsMethod typedef is exposed. The existence of this typedef (NOT the value) +// marks that the wrapper should be considered a method wrapper. + +template <typename Functor> +class RunnableAdapter; + +// Function: Arity 0. +template <typename R> +class RunnableAdapter<R(*)()> { + public: + typedef R (RunType)(); + + explicit RunnableAdapter(R(*function)()) + : function_(function) { + } + + R Run() { + return function_(); + } + + private: + R (*function_)(); +}; + +// Method: Arity 0. +template <typename R, typename T> +class RunnableAdapter<R(T::*)()> { + public: + typedef R (RunType)(T*); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)()) + : method_(method) { + } + + R Run(T* object) { + return (object->*method_)(); + } + + private: + R (T::*method_)(); +}; + +// Const Method: Arity 0. +template <typename R, typename T> +class RunnableAdapter<R(T::*)() const> { + public: + typedef R (RunType)(const T*); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)() const) + : method_(method) { + } + + R Run(const T* object) { + return (object->*method_)(); + } + + private: + R (T::*method_)() const; +}; + +// Function: Arity 1. +template <typename R, typename A1> +class RunnableAdapter<R(*)(A1)> { + public: + typedef R (RunType)(A1); + + explicit RunnableAdapter(R(*function)(A1)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1) { + return function_(CallbackForward(a1)); + } + + private: + R (*function_)(A1); +}; + +// Method: Arity 1. +template <typename R, typename T, typename A1> +class RunnableAdapter<R(T::*)(A1)> { + public: + typedef R (RunType)(T*, A1); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)(A1)) + : method_(method) { + } + + R Run(T* object, typename CallbackParamTraits<A1>::ForwardType a1) { + return (object->*method_)(CallbackForward(a1)); + } + + private: + R (T::*method_)(A1); +}; + +// Const Method: Arity 1. +template <typename R, typename T, typename A1> +class RunnableAdapter<R(T::*)(A1) const> { + public: + typedef R (RunType)(const T*, A1); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)(A1) const) + : method_(method) { + } + + R Run(const T* object, typename CallbackParamTraits<A1>::ForwardType a1) { + return (object->*method_)(CallbackForward(a1)); + } + + private: + R (T::*method_)(A1) const; +}; + +// Function: Arity 2. +template <typename R, typename A1, typename A2> +class RunnableAdapter<R(*)(A1, A2)> { + public: + typedef R (RunType)(A1, A2); + + explicit RunnableAdapter(R(*function)(A1, A2)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2) { + return function_(CallbackForward(a1), CallbackForward(a2)); + } + + private: + R (*function_)(A1, A2); +}; + +// Method: Arity 2. +template <typename R, typename T, typename A1, typename A2> +class RunnableAdapter<R(T::*)(A1, A2)> { + public: + typedef R (RunType)(T*, A1, A2); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)(A1, A2)) + : method_(method) { + } + + R Run(T* object, typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2)); + } + + private: + R (T::*method_)(A1, A2); +}; + +// Const Method: Arity 2. +template <typename R, typename T, typename A1, typename A2> +class RunnableAdapter<R(T::*)(A1, A2) const> { + public: + typedef R (RunType)(const T*, A1, A2); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)(A1, A2) const) + : method_(method) { + } + + R Run(const T* object, typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2)); + } + + private: + R (T::*method_)(A1, A2) const; +}; + +// Function: Arity 3. +template <typename R, typename A1, typename A2, typename A3> +class RunnableAdapter<R(*)(A1, A2, A3)> { + public: + typedef R (RunType)(A1, A2, A3); + + explicit RunnableAdapter(R(*function)(A1, A2, A3)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3) { + return function_(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3)); + } + + private: + R (*function_)(A1, A2, A3); +}; + +// Method: Arity 3. +template <typename R, typename T, typename A1, typename A2, typename A3> +class RunnableAdapter<R(T::*)(A1, A2, A3)> { + public: + typedef R (RunType)(T*, A1, A2, A3); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)(A1, A2, A3)) + : method_(method) { + } + + R Run(T* object, typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3)); + } + + private: + R (T::*method_)(A1, A2, A3); +}; + +// Const Method: Arity 3. +template <typename R, typename T, typename A1, typename A2, typename A3> +class RunnableAdapter<R(T::*)(A1, A2, A3) const> { + public: + typedef R (RunType)(const T*, A1, A2, A3); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)(A1, A2, A3) const) + : method_(method) { + } + + R Run(const T* object, typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3)); + } + + private: + R (T::*method_)(A1, A2, A3) const; +}; + +// Function: Arity 4. +template <typename R, typename A1, typename A2, typename A3, typename A4> +class RunnableAdapter<R(*)(A1, A2, A3, A4)> { + public: + typedef R (RunType)(A1, A2, A3, A4); + + explicit RunnableAdapter(R(*function)(A1, A2, A3, A4)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4) { + return function_(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4)); + } + + private: + R (*function_)(A1, A2, A3, A4); +}; + +// Method: Arity 4. +template <typename R, typename T, typename A1, typename A2, typename A3, + typename A4> +class RunnableAdapter<R(T::*)(A1, A2, A3, A4)> { + public: + typedef R (RunType)(T*, A1, A2, A3, A4); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)(A1, A2, A3, A4)) + : method_(method) { + } + + R Run(T* object, typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4)); + } + + private: + R (T::*method_)(A1, A2, A3, A4); +}; + +// Const Method: Arity 4. +template <typename R, typename T, typename A1, typename A2, typename A3, + typename A4> +class RunnableAdapter<R(T::*)(A1, A2, A3, A4) const> { + public: + typedef R (RunType)(const T*, A1, A2, A3, A4); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)(A1, A2, A3, A4) const) + : method_(method) { + } + + R Run(const T* object, typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4)); + } + + private: + R (T::*method_)(A1, A2, A3, A4) const; +}; + +// Function: Arity 5. +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5> +class RunnableAdapter<R(*)(A1, A2, A3, A4, A5)> { + public: + typedef R (RunType)(A1, A2, A3, A4, A5); + + explicit RunnableAdapter(R(*function)(A1, A2, A3, A4, A5)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5) { + return function_(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4), CallbackForward(a5)); + } + + private: + R (*function_)(A1, A2, A3, A4, A5); +}; + +// Method: Arity 5. +template <typename R, typename T, typename A1, typename A2, typename A3, + typename A4, typename A5> +class RunnableAdapter<R(T::*)(A1, A2, A3, A4, A5)> { + public: + typedef R (RunType)(T*, A1, A2, A3, A4, A5); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)(A1, A2, A3, A4, A5)) + : method_(method) { + } + + R Run(T* object, typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4), CallbackForward(a5)); + } + + private: + R (T::*method_)(A1, A2, A3, A4, A5); +}; + +// Const Method: Arity 5. +template <typename R, typename T, typename A1, typename A2, typename A3, + typename A4, typename A5> +class RunnableAdapter<R(T::*)(A1, A2, A3, A4, A5) const> { + public: + typedef R (RunType)(const T*, A1, A2, A3, A4, A5); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)(A1, A2, A3, A4, A5) const) + : method_(method) { + } + + R Run(const T* object, typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4), CallbackForward(a5)); + } + + private: + R (T::*method_)(A1, A2, A3, A4, A5) const; +}; + +// Function: Arity 6. +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6> +class RunnableAdapter<R(*)(A1, A2, A3, A4, A5, A6)> { + public: + typedef R (RunType)(A1, A2, A3, A4, A5, A6); + + explicit RunnableAdapter(R(*function)(A1, A2, A3, A4, A5, A6)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5, + typename CallbackParamTraits<A6>::ForwardType a6) { + return function_(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4), CallbackForward(a5), + CallbackForward(a6)); + } + + private: + R (*function_)(A1, A2, A3, A4, A5, A6); +}; + +// Method: Arity 6. +template <typename R, typename T, typename A1, typename A2, typename A3, + typename A4, typename A5, typename A6> +class RunnableAdapter<R(T::*)(A1, A2, A3, A4, A5, A6)> { + public: + typedef R (RunType)(T*, A1, A2, A3, A4, A5, A6); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)(A1, A2, A3, A4, A5, A6)) + : method_(method) { + } + + R Run(T* object, typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5, + typename CallbackParamTraits<A6>::ForwardType a6) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4), CallbackForward(a5), + CallbackForward(a6)); + } + + private: + R (T::*method_)(A1, A2, A3, A4, A5, A6); +}; + +// Const Method: Arity 6. +template <typename R, typename T, typename A1, typename A2, typename A3, + typename A4, typename A5, typename A6> +class RunnableAdapter<R(T::*)(A1, A2, A3, A4, A5, A6) const> { + public: + typedef R (RunType)(const T*, A1, A2, A3, A4, A5, A6); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)(A1, A2, A3, A4, A5, A6) const) + : method_(method) { + } + + R Run(const T* object, typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5, + typename CallbackParamTraits<A6>::ForwardType a6) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4), CallbackForward(a5), + CallbackForward(a6)); + } + + private: + R (T::*method_)(A1, A2, A3, A4, A5, A6) const; +}; + +// Function: Arity 7. +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6, typename A7> +class RunnableAdapter<R(*)(A1, A2, A3, A4, A5, A6, A7)> { + public: + typedef R (RunType)(A1, A2, A3, A4, A5, A6, A7); + + explicit RunnableAdapter(R(*function)(A1, A2, A3, A4, A5, A6, A7)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5, + typename CallbackParamTraits<A6>::ForwardType a6, + typename CallbackParamTraits<A7>::ForwardType a7) { + return function_(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4), CallbackForward(a5), + CallbackForward(a6), CallbackForward(a7)); + } + + private: + R (*function_)(A1, A2, A3, A4, A5, A6, A7); +}; + +// Method: Arity 7. +template <typename R, typename T, typename A1, typename A2, typename A3, + typename A4, typename A5, typename A6, typename A7> +class RunnableAdapter<R(T::*)(A1, A2, A3, A4, A5, A6, A7)> { + public: + typedef R (RunType)(T*, A1, A2, A3, A4, A5, A6, A7); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)(A1, A2, A3, A4, A5, A6, A7)) + : method_(method) { + } + + R Run(T* object, typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5, + typename CallbackParamTraits<A6>::ForwardType a6, + typename CallbackParamTraits<A7>::ForwardType a7) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4), CallbackForward(a5), + CallbackForward(a6), CallbackForward(a7)); + } + + private: + R (T::*method_)(A1, A2, A3, A4, A5, A6, A7); +}; + +// Const Method: Arity 7. +template <typename R, typename T, typename A1, typename A2, typename A3, + typename A4, typename A5, typename A6, typename A7> +class RunnableAdapter<R(T::*)(A1, A2, A3, A4, A5, A6, A7) const> { + public: + typedef R (RunType)(const T*, A1, A2, A3, A4, A5, A6, A7); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)(A1, A2, A3, A4, A5, A6, A7) const) + : method_(method) { + } + + R Run(const T* object, typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5, + typename CallbackParamTraits<A6>::ForwardType a6, + typename CallbackParamTraits<A7>::ForwardType a7) { + return (object->*method_)(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4), CallbackForward(a5), + CallbackForward(a6), CallbackForward(a7)); + } + + private: + R (T::*method_)(A1, A2, A3, A4, A5, A6, A7) const; +}; + + +// FunctionTraits<> +// +// Breaks a function signature apart into typedefs for easier introspection. +template <typename Sig> +struct FunctionTraits; + +template <typename R> +struct FunctionTraits<R()> { + typedef R ReturnType; +}; + +template <typename R, typename A1> +struct FunctionTraits<R(A1)> { + typedef R ReturnType; + typedef A1 A1Type; +}; + +template <typename R, typename A1, typename A2> +struct FunctionTraits<R(A1, A2)> { + typedef R ReturnType; + typedef A1 A1Type; + typedef A2 A2Type; +}; + +template <typename R, typename A1, typename A2, typename A3> +struct FunctionTraits<R(A1, A2, A3)> { + typedef R ReturnType; + typedef A1 A1Type; + typedef A2 A2Type; + typedef A3 A3Type; +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4> +struct FunctionTraits<R(A1, A2, A3, A4)> { + typedef R ReturnType; + typedef A1 A1Type; + typedef A2 A2Type; + typedef A3 A3Type; + typedef A4 A4Type; +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5> +struct FunctionTraits<R(A1, A2, A3, A4, A5)> { + typedef R ReturnType; + typedef A1 A1Type; + typedef A2 A2Type; + typedef A3 A3Type; + typedef A4 A4Type; + typedef A5 A5Type; +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6> +struct FunctionTraits<R(A1, A2, A3, A4, A5, A6)> { + typedef R ReturnType; + typedef A1 A1Type; + typedef A2 A2Type; + typedef A3 A3Type; + typedef A4 A4Type; + typedef A5 A5Type; + typedef A6 A6Type; +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6, typename A7> +struct FunctionTraits<R(A1, A2, A3, A4, A5, A6, A7)> { + typedef R ReturnType; + typedef A1 A1Type; + typedef A2 A2Type; + typedef A3 A3Type; + typedef A4 A4Type; + typedef A5 A5Type; + typedef A6 A6Type; + typedef A7 A7Type; +}; + + +// ForceVoidReturn<> +// +// Set of templates that support forcing the function return type to void. +template <typename Sig> +struct ForceVoidReturn; + +template <typename R> +struct ForceVoidReturn<R()> { + typedef void(RunType)(); +}; + +template <typename R, typename A1> +struct ForceVoidReturn<R(A1)> { + typedef void(RunType)(A1); +}; + +template <typename R, typename A1, typename A2> +struct ForceVoidReturn<R(A1, A2)> { + typedef void(RunType)(A1, A2); +}; + +template <typename R, typename A1, typename A2, typename A3> +struct ForceVoidReturn<R(A1, A2, A3)> { + typedef void(RunType)(A1, A2, A3); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4> +struct ForceVoidReturn<R(A1, A2, A3, A4)> { + typedef void(RunType)(A1, A2, A3, A4); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5> +struct ForceVoidReturn<R(A1, A2, A3, A4, A5)> { + typedef void(RunType)(A1, A2, A3, A4, A5); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6> +struct ForceVoidReturn<R(A1, A2, A3, A4, A5, A6)> { + typedef void(RunType)(A1, A2, A3, A4, A5, A6); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6, typename A7> +struct ForceVoidReturn<R(A1, A2, A3, A4, A5, A6, A7)> { + typedef void(RunType)(A1, A2, A3, A4, A5, A6, A7); +}; + + +// FunctorTraits<> +// +// See description at top of file. +template <typename T> +struct FunctorTraits { + typedef RunnableAdapter<T> RunnableType; + typedef typename RunnableType::RunType RunType; +}; + +template <typename T> +struct FunctorTraits<IgnoreResultHelper<T> > { + typedef typename FunctorTraits<T>::RunnableType RunnableType; + typedef typename ForceVoidReturn< + typename RunnableType::RunType>::RunType RunType; +}; + +template <typename T> +struct FunctorTraits<Callback<T> > { + typedef Callback<T> RunnableType; + typedef typename Callback<T>::RunType RunType; +}; + + +// MakeRunnable<> +// +// Converts a passed in functor to a RunnableType using type inference. + +template <typename T> +typename FunctorTraits<T>::RunnableType MakeRunnable(const T& t) { + return RunnableAdapter<T>(t); +} + +template <typename T> +typename FunctorTraits<T>::RunnableType +MakeRunnable(const IgnoreResultHelper<T>& t) { + return MakeRunnable(t.functor_); +} + +template <typename T> +const typename FunctorTraits<Callback<T> >::RunnableType& +MakeRunnable(const Callback<T>& t) { + DCHECK(!t.is_null()); + return t; +} + + +// InvokeHelper<> +// +// There are 3 logical InvokeHelper<> specializations: normal, void-return, +// WeakCalls. +// +// The normal type just calls the underlying runnable. +// +// We need a InvokeHelper to handle void return types in order to support +// IgnoreResult(). Normally, if the Runnable's RunType had a void return, +// the template system would just accept "return functor.Run()" ignoring +// the fact that a void function is being used with return. This piece of +// sugar breaks though when the Runnable's RunType is not void. Thus, we +// need a partial specialization to change the syntax to drop the "return" +// from the invocation call. +// +// WeakCalls similarly need special syntax that is applied to the first +// argument to check if they should no-op themselves. +template <bool IsWeakCall, typename ReturnType, typename Runnable, + typename ArgsType> +struct InvokeHelper; + +template <typename ReturnType, typename Runnable> +struct InvokeHelper<false, ReturnType, Runnable, + void()> { + static ReturnType MakeItSo(Runnable runnable) { + return runnable.Run(); + } +}; + +template <typename Runnable> +struct InvokeHelper<false, void, Runnable, + void()> { + static void MakeItSo(Runnable runnable) { + runnable.Run(); + } +}; + +template <typename ReturnType, typename Runnable,typename A1> +struct InvokeHelper<false, ReturnType, Runnable, + void(A1)> { + static ReturnType MakeItSo(Runnable runnable, A1 a1) { + return runnable.Run(CallbackForward(a1)); + } +}; + +template <typename Runnable,typename A1> +struct InvokeHelper<false, void, Runnable, + void(A1)> { + static void MakeItSo(Runnable runnable, A1 a1) { + runnable.Run(CallbackForward(a1)); + } +}; + +template <typename Runnable, typename A1> +struct InvokeHelper<true, void, Runnable, + void(A1)> { + static void MakeItSo(Runnable runnable, A1 a1) { + if (!a1.get()) { + return; + } + + runnable.Run(CallbackForward(a1)); + } +}; + +template <typename ReturnType, typename Runnable,typename A1, typename A2> +struct InvokeHelper<false, ReturnType, Runnable, + void(A1, A2)> { + static ReturnType MakeItSo(Runnable runnable, A1 a1, A2 a2) { + return runnable.Run(CallbackForward(a1), CallbackForward(a2)); + } +}; + +template <typename Runnable,typename A1, typename A2> +struct InvokeHelper<false, void, Runnable, + void(A1, A2)> { + static void MakeItSo(Runnable runnable, A1 a1, A2 a2) { + runnable.Run(CallbackForward(a1), CallbackForward(a2)); + } +}; + +template <typename Runnable, typename A1, typename A2> +struct InvokeHelper<true, void, Runnable, + void(A1, A2)> { + static void MakeItSo(Runnable runnable, A1 a1, A2 a2) { + if (!a1.get()) { + return; + } + + runnable.Run(CallbackForward(a1), CallbackForward(a2)); + } +}; + +template <typename ReturnType, typename Runnable,typename A1, typename A2, + typename A3> +struct InvokeHelper<false, ReturnType, Runnable, + void(A1, A2, A3)> { + static ReturnType MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3) { + return runnable.Run(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3)); + } +}; + +template <typename Runnable,typename A1, typename A2, typename A3> +struct InvokeHelper<false, void, Runnable, + void(A1, A2, A3)> { + static void MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3) { + runnable.Run(CallbackForward(a1), CallbackForward(a2), CallbackForward(a3)); + } +}; + +template <typename Runnable, typename A1, typename A2, typename A3> +struct InvokeHelper<true, void, Runnable, + void(A1, A2, A3)> { + static void MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3) { + if (!a1.get()) { + return; + } + + runnable.Run(CallbackForward(a1), CallbackForward(a2), CallbackForward(a3)); + } +}; + +template <typename ReturnType, typename Runnable,typename A1, typename A2, + typename A3, typename A4> +struct InvokeHelper<false, ReturnType, Runnable, + void(A1, A2, A3, A4)> { + static ReturnType MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3, A4 a4) { + return runnable.Run(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4)); + } +}; + +template <typename Runnable,typename A1, typename A2, typename A3, typename A4> +struct InvokeHelper<false, void, Runnable, + void(A1, A2, A3, A4)> { + static void MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3, A4 a4) { + runnable.Run(CallbackForward(a1), CallbackForward(a2), CallbackForward(a3), + CallbackForward(a4)); + } +}; + +template <typename Runnable, typename A1, typename A2, typename A3, typename A4> +struct InvokeHelper<true, void, Runnable, + void(A1, A2, A3, A4)> { + static void MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3, A4 a4) { + if (!a1.get()) { + return; + } + + runnable.Run(CallbackForward(a1), CallbackForward(a2), CallbackForward(a3), + CallbackForward(a4)); + } +}; + +template <typename ReturnType, typename Runnable,typename A1, typename A2, + typename A3, typename A4, typename A5> +struct InvokeHelper<false, ReturnType, Runnable, + void(A1, A2, A3, A4, A5)> { + static ReturnType MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3, A4 a4, + A5 a5) { + return runnable.Run(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4), CallbackForward(a5)); + } +}; + +template <typename Runnable,typename A1, typename A2, typename A3, typename A4, + typename A5> +struct InvokeHelper<false, void, Runnable, + void(A1, A2, A3, A4, A5)> { + static void MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) { + runnable.Run(CallbackForward(a1), CallbackForward(a2), CallbackForward(a3), + CallbackForward(a4), CallbackForward(a5)); + } +}; + +template <typename Runnable, typename A1, typename A2, typename A3, + typename A4, typename A5> +struct InvokeHelper<true, void, Runnable, + void(A1, A2, A3, A4, A5)> { + static void MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) { + if (!a1.get()) { + return; + } + + runnable.Run(CallbackForward(a1), CallbackForward(a2), CallbackForward(a3), + CallbackForward(a4), CallbackForward(a5)); + } +}; + +template <typename ReturnType, typename Runnable,typename A1, typename A2, + typename A3, typename A4, typename A5, typename A6> +struct InvokeHelper<false, ReturnType, Runnable, + void(A1, A2, A3, A4, A5, A6)> { + static ReturnType MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3, A4 a4, + A5 a5, A6 a6) { + return runnable.Run(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4), CallbackForward(a5), + CallbackForward(a6)); + } +}; + +template <typename Runnable,typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6> +struct InvokeHelper<false, void, Runnable, + void(A1, A2, A3, A4, A5, A6)> { + static void MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, + A6 a6) { + runnable.Run(CallbackForward(a1), CallbackForward(a2), CallbackForward(a3), + CallbackForward(a4), CallbackForward(a5), CallbackForward(a6)); + } +}; + +template <typename Runnable, typename A1, typename A2, typename A3, + typename A4, typename A5, typename A6> +struct InvokeHelper<true, void, Runnable, + void(A1, A2, A3, A4, A5, A6)> { + static void MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, + A6 a6) { + if (!a1.get()) { + return; + } + + runnable.Run(CallbackForward(a1), CallbackForward(a2), CallbackForward(a3), + CallbackForward(a4), CallbackForward(a5), CallbackForward(a6)); + } +}; + +template <typename ReturnType, typename Runnable,typename A1, typename A2, + typename A3, typename A4, typename A5, typename A6, typename A7> +struct InvokeHelper<false, ReturnType, Runnable, + void(A1, A2, A3, A4, A5, A6, A7)> { + static ReturnType MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3, A4 a4, + A5 a5, A6 a6, A7 a7) { + return runnable.Run(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4), CallbackForward(a5), + CallbackForward(a6), CallbackForward(a7)); + } +}; + +template <typename Runnable,typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6, typename A7> +struct InvokeHelper<false, void, Runnable, + void(A1, A2, A3, A4, A5, A6, A7)> { + static void MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, + A6 a6, A7 a7) { + runnable.Run(CallbackForward(a1), CallbackForward(a2), CallbackForward(a3), + CallbackForward(a4), CallbackForward(a5), CallbackForward(a6), + CallbackForward(a7)); + } +}; + +template <typename Runnable, typename A1, typename A2, typename A3, + typename A4, typename A5, typename A6, typename A7> +struct InvokeHelper<true, void, Runnable, + void(A1, A2, A3, A4, A5, A6, A7)> { + static void MakeItSo(Runnable runnable, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, + A6 a6, A7 a7) { + if (!a1.get()) { + return; + } + + runnable.Run(CallbackForward(a1), CallbackForward(a2), CallbackForward(a3), + CallbackForward(a4), CallbackForward(a5), CallbackForward(a6), + CallbackForward(a7)); + } +}; + +#if !defined(_MSC_VER) + +template <typename ReturnType, typename Runnable, typename ArgsType> +struct InvokeHelper<true, ReturnType, Runnable, ArgsType> { + // WeakCalls are only supported for functions with a void return type. + // Otherwise, the function result would be undefined if the the WeakPtr<> + // is invalidated. + COMPILE_ASSERT(is_void<ReturnType>::value, + weak_ptrs_can_only_bind_to_methods_without_return_values); +}; + +#endif + +// Invoker<> +// +// See description at the top of the file. +template <int NumBound, typename Storage, typename RunType> +struct Invoker; + +// Arity 0 -> 0. +template <typename StorageType, typename R> +struct Invoker<0, StorageType, R()> { + typedef R(RunType)(BindStateBase*); + + typedef R(UnboundRunType)(); + + static R Run(BindStateBase* base) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void()> + ::MakeItSo(storage->runnable_); + } +}; + +// Arity 1 -> 1. +template <typename StorageType, typename R,typename X1> +struct Invoker<0, StorageType, R(X1)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X1>::ForwardType); + + typedef R(UnboundRunType)(X1); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X1>::ForwardType x1) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename CallbackParamTraits<X1>::ForwardType x1)> + ::MakeItSo(storage->runnable_, CallbackForward(x1)); + } +}; + +// Arity 1 -> 0. +template <typename StorageType, typename R,typename X1> +struct Invoker<1, StorageType, R(X1)> { + typedef R(RunType)(BindStateBase*); + + typedef R(UnboundRunType)(); + + static R Run(BindStateBase* base) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType)> + ::MakeItSo(storage->runnable_, CallbackForward(x1)); + } +}; + +// Arity 2 -> 2. +template <typename StorageType, typename R,typename X1, typename X2> +struct Invoker<0, StorageType, R(X1, X2)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X1>::ForwardType, + typename CallbackParamTraits<X2>::ForwardType); + + typedef R(UnboundRunType)(X1, X2); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X1>::ForwardType x1, + typename CallbackParamTraits<X2>::ForwardType x2) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename CallbackParamTraits<X1>::ForwardType x1, + typename CallbackParamTraits<X2>::ForwardType x2)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2)); + } +}; + +// Arity 2 -> 1. +template <typename StorageType, typename R,typename X1, typename X2> +struct Invoker<1, StorageType, R(X1, X2)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X2>::ForwardType); + + typedef R(UnboundRunType)(X2); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X2>::ForwardType x2) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename CallbackParamTraits<X2>::ForwardType x2)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2)); + } +}; + +// Arity 2 -> 0. +template <typename StorageType, typename R,typename X1, typename X2> +struct Invoker<2, StorageType, R(X1, X2)> { + typedef R(RunType)(BindStateBase*); + + typedef R(UnboundRunType)(); + + static R Run(BindStateBase* base) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2)); + } +}; + +// Arity 3 -> 3. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3> +struct Invoker<0, StorageType, R(X1, X2, X3)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X1>::ForwardType, + typename CallbackParamTraits<X2>::ForwardType, + typename CallbackParamTraits<X3>::ForwardType); + + typedef R(UnboundRunType)(X1, X2, X3); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X1>::ForwardType x1, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename CallbackParamTraits<X1>::ForwardType x1, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3)); + } +}; + +// Arity 3 -> 2. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3> +struct Invoker<1, StorageType, R(X1, X2, X3)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X2>::ForwardType, + typename CallbackParamTraits<X3>::ForwardType); + + typedef R(UnboundRunType)(X2, X3); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3)); + } +}; + +// Arity 3 -> 1. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3> +struct Invoker<2, StorageType, R(X1, X2, X3)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X3>::ForwardType); + + typedef R(UnboundRunType)(X3); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X3>::ForwardType x3) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename CallbackParamTraits<X3>::ForwardType x3)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3)); + } +}; + +// Arity 3 -> 0. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3> +struct Invoker<3, StorageType, R(X1, X2, X3)> { + typedef R(RunType)(BindStateBase*); + + typedef R(UnboundRunType)(); + + static R Run(BindStateBase* base) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + typedef typename StorageType::Bound3UnwrapTraits Bound3UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + typename Bound3UnwrapTraits::ForwardType x3 = + Bound3UnwrapTraits::Unwrap(storage->p3_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename Bound3UnwrapTraits::ForwardType)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3)); + } +}; + +// Arity 4 -> 4. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4> +struct Invoker<0, StorageType, R(X1, X2, X3, X4)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X1>::ForwardType, + typename CallbackParamTraits<X2>::ForwardType, + typename CallbackParamTraits<X3>::ForwardType, + typename CallbackParamTraits<X4>::ForwardType); + + typedef R(UnboundRunType)(X1, X2, X3, X4); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X1>::ForwardType x1, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename CallbackParamTraits<X1>::ForwardType x1, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4)); + } +}; + +// Arity 4 -> 3. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4> +struct Invoker<1, StorageType, R(X1, X2, X3, X4)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X2>::ForwardType, + typename CallbackParamTraits<X3>::ForwardType, + typename CallbackParamTraits<X4>::ForwardType); + + typedef R(UnboundRunType)(X2, X3, X4); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4)); + } +}; + +// Arity 4 -> 2. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4> +struct Invoker<2, StorageType, R(X1, X2, X3, X4)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X3>::ForwardType, + typename CallbackParamTraits<X4>::ForwardType); + + typedef R(UnboundRunType)(X3, X4); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4)); + } +}; + +// Arity 4 -> 1. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4> +struct Invoker<3, StorageType, R(X1, X2, X3, X4)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X4>::ForwardType); + + typedef R(UnboundRunType)(X4); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X4>::ForwardType x4) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + typedef typename StorageType::Bound3UnwrapTraits Bound3UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + typename Bound3UnwrapTraits::ForwardType x3 = + Bound3UnwrapTraits::Unwrap(storage->p3_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename Bound3UnwrapTraits::ForwardType, + typename CallbackParamTraits<X4>::ForwardType x4)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4)); + } +}; + +// Arity 4 -> 0. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4> +struct Invoker<4, StorageType, R(X1, X2, X3, X4)> { + typedef R(RunType)(BindStateBase*); + + typedef R(UnboundRunType)(); + + static R Run(BindStateBase* base) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + typedef typename StorageType::Bound3UnwrapTraits Bound3UnwrapTraits; + typedef typename StorageType::Bound4UnwrapTraits Bound4UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + typename Bound3UnwrapTraits::ForwardType x3 = + Bound3UnwrapTraits::Unwrap(storage->p3_); + typename Bound4UnwrapTraits::ForwardType x4 = + Bound4UnwrapTraits::Unwrap(storage->p4_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename Bound3UnwrapTraits::ForwardType, + typename Bound4UnwrapTraits::ForwardType)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4)); + } +}; + +// Arity 5 -> 5. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5> +struct Invoker<0, StorageType, R(X1, X2, X3, X4, X5)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X1>::ForwardType, + typename CallbackParamTraits<X2>::ForwardType, + typename CallbackParamTraits<X3>::ForwardType, + typename CallbackParamTraits<X4>::ForwardType, + typename CallbackParamTraits<X5>::ForwardType); + + typedef R(UnboundRunType)(X1, X2, X3, X4, X5); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X1>::ForwardType x1, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename CallbackParamTraits<X1>::ForwardType x1, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5)); + } +}; + +// Arity 5 -> 4. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5> +struct Invoker<1, StorageType, R(X1, X2, X3, X4, X5)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X2>::ForwardType, + typename CallbackParamTraits<X3>::ForwardType, + typename CallbackParamTraits<X4>::ForwardType, + typename CallbackParamTraits<X5>::ForwardType); + + typedef R(UnboundRunType)(X2, X3, X4, X5); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5)); + } +}; + +// Arity 5 -> 3. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5> +struct Invoker<2, StorageType, R(X1, X2, X3, X4, X5)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X3>::ForwardType, + typename CallbackParamTraits<X4>::ForwardType, + typename CallbackParamTraits<X5>::ForwardType); + + typedef R(UnboundRunType)(X3, X4, X5); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5)); + } +}; + +// Arity 5 -> 2. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5> +struct Invoker<3, StorageType, R(X1, X2, X3, X4, X5)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X4>::ForwardType, + typename CallbackParamTraits<X5>::ForwardType); + + typedef R(UnboundRunType)(X4, X5); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + typedef typename StorageType::Bound3UnwrapTraits Bound3UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + typename Bound3UnwrapTraits::ForwardType x3 = + Bound3UnwrapTraits::Unwrap(storage->p3_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename Bound3UnwrapTraits::ForwardType, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5)); + } +}; + +// Arity 5 -> 1. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5> +struct Invoker<4, StorageType, R(X1, X2, X3, X4, X5)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X5>::ForwardType); + + typedef R(UnboundRunType)(X5); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X5>::ForwardType x5) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + typedef typename StorageType::Bound3UnwrapTraits Bound3UnwrapTraits; + typedef typename StorageType::Bound4UnwrapTraits Bound4UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + typename Bound3UnwrapTraits::ForwardType x3 = + Bound3UnwrapTraits::Unwrap(storage->p3_); + typename Bound4UnwrapTraits::ForwardType x4 = + Bound4UnwrapTraits::Unwrap(storage->p4_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename Bound3UnwrapTraits::ForwardType, + typename Bound4UnwrapTraits::ForwardType, + typename CallbackParamTraits<X5>::ForwardType x5)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5)); + } +}; + +// Arity 5 -> 0. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5> +struct Invoker<5, StorageType, R(X1, X2, X3, X4, X5)> { + typedef R(RunType)(BindStateBase*); + + typedef R(UnboundRunType)(); + + static R Run(BindStateBase* base) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + typedef typename StorageType::Bound3UnwrapTraits Bound3UnwrapTraits; + typedef typename StorageType::Bound4UnwrapTraits Bound4UnwrapTraits; + typedef typename StorageType::Bound5UnwrapTraits Bound5UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + typename Bound3UnwrapTraits::ForwardType x3 = + Bound3UnwrapTraits::Unwrap(storage->p3_); + typename Bound4UnwrapTraits::ForwardType x4 = + Bound4UnwrapTraits::Unwrap(storage->p4_); + typename Bound5UnwrapTraits::ForwardType x5 = + Bound5UnwrapTraits::Unwrap(storage->p5_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename Bound3UnwrapTraits::ForwardType, + typename Bound4UnwrapTraits::ForwardType, + typename Bound5UnwrapTraits::ForwardType)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5)); + } +}; + +// Arity 6 -> 6. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5, typename X6> +struct Invoker<0, StorageType, R(X1, X2, X3, X4, X5, X6)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X1>::ForwardType, + typename CallbackParamTraits<X2>::ForwardType, + typename CallbackParamTraits<X3>::ForwardType, + typename CallbackParamTraits<X4>::ForwardType, + typename CallbackParamTraits<X5>::ForwardType, + typename CallbackParamTraits<X6>::ForwardType); + + typedef R(UnboundRunType)(X1, X2, X3, X4, X5, X6); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X1>::ForwardType x1, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename CallbackParamTraits<X1>::ForwardType x1, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6)); + } +}; + +// Arity 6 -> 5. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5, typename X6> +struct Invoker<1, StorageType, R(X1, X2, X3, X4, X5, X6)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X2>::ForwardType, + typename CallbackParamTraits<X3>::ForwardType, + typename CallbackParamTraits<X4>::ForwardType, + typename CallbackParamTraits<X5>::ForwardType, + typename CallbackParamTraits<X6>::ForwardType); + + typedef R(UnboundRunType)(X2, X3, X4, X5, X6); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6)); + } +}; + +// Arity 6 -> 4. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5, typename X6> +struct Invoker<2, StorageType, R(X1, X2, X3, X4, X5, X6)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X3>::ForwardType, + typename CallbackParamTraits<X4>::ForwardType, + typename CallbackParamTraits<X5>::ForwardType, + typename CallbackParamTraits<X6>::ForwardType); + + typedef R(UnboundRunType)(X3, X4, X5, X6); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6)); + } +}; + +// Arity 6 -> 3. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5, typename X6> +struct Invoker<3, StorageType, R(X1, X2, X3, X4, X5, X6)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X4>::ForwardType, + typename CallbackParamTraits<X5>::ForwardType, + typename CallbackParamTraits<X6>::ForwardType); + + typedef R(UnboundRunType)(X4, X5, X6); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + typedef typename StorageType::Bound3UnwrapTraits Bound3UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + typename Bound3UnwrapTraits::ForwardType x3 = + Bound3UnwrapTraits::Unwrap(storage->p3_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename Bound3UnwrapTraits::ForwardType, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6)); + } +}; + +// Arity 6 -> 2. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5, typename X6> +struct Invoker<4, StorageType, R(X1, X2, X3, X4, X5, X6)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X5>::ForwardType, + typename CallbackParamTraits<X6>::ForwardType); + + typedef R(UnboundRunType)(X5, X6); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + typedef typename StorageType::Bound3UnwrapTraits Bound3UnwrapTraits; + typedef typename StorageType::Bound4UnwrapTraits Bound4UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + typename Bound3UnwrapTraits::ForwardType x3 = + Bound3UnwrapTraits::Unwrap(storage->p3_); + typename Bound4UnwrapTraits::ForwardType x4 = + Bound4UnwrapTraits::Unwrap(storage->p4_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename Bound3UnwrapTraits::ForwardType, + typename Bound4UnwrapTraits::ForwardType, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6)); + } +}; + +// Arity 6 -> 1. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5, typename X6> +struct Invoker<5, StorageType, R(X1, X2, X3, X4, X5, X6)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X6>::ForwardType); + + typedef R(UnboundRunType)(X6); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X6>::ForwardType x6) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + typedef typename StorageType::Bound3UnwrapTraits Bound3UnwrapTraits; + typedef typename StorageType::Bound4UnwrapTraits Bound4UnwrapTraits; + typedef typename StorageType::Bound5UnwrapTraits Bound5UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + typename Bound3UnwrapTraits::ForwardType x3 = + Bound3UnwrapTraits::Unwrap(storage->p3_); + typename Bound4UnwrapTraits::ForwardType x4 = + Bound4UnwrapTraits::Unwrap(storage->p4_); + typename Bound5UnwrapTraits::ForwardType x5 = + Bound5UnwrapTraits::Unwrap(storage->p5_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename Bound3UnwrapTraits::ForwardType, + typename Bound4UnwrapTraits::ForwardType, + typename Bound5UnwrapTraits::ForwardType, + typename CallbackParamTraits<X6>::ForwardType x6)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6)); + } +}; + +// Arity 6 -> 0. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5, typename X6> +struct Invoker<6, StorageType, R(X1, X2, X3, X4, X5, X6)> { + typedef R(RunType)(BindStateBase*); + + typedef R(UnboundRunType)(); + + static R Run(BindStateBase* base) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + typedef typename StorageType::Bound3UnwrapTraits Bound3UnwrapTraits; + typedef typename StorageType::Bound4UnwrapTraits Bound4UnwrapTraits; + typedef typename StorageType::Bound5UnwrapTraits Bound5UnwrapTraits; + typedef typename StorageType::Bound6UnwrapTraits Bound6UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + typename Bound3UnwrapTraits::ForwardType x3 = + Bound3UnwrapTraits::Unwrap(storage->p3_); + typename Bound4UnwrapTraits::ForwardType x4 = + Bound4UnwrapTraits::Unwrap(storage->p4_); + typename Bound5UnwrapTraits::ForwardType x5 = + Bound5UnwrapTraits::Unwrap(storage->p5_); + typename Bound6UnwrapTraits::ForwardType x6 = + Bound6UnwrapTraits::Unwrap(storage->p6_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename Bound3UnwrapTraits::ForwardType, + typename Bound4UnwrapTraits::ForwardType, + typename Bound5UnwrapTraits::ForwardType, + typename Bound6UnwrapTraits::ForwardType)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6)); + } +}; + +// Arity 7 -> 7. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5, typename X6, typename X7> +struct Invoker<0, StorageType, R(X1, X2, X3, X4, X5, X6, X7)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X1>::ForwardType, + typename CallbackParamTraits<X2>::ForwardType, + typename CallbackParamTraits<X3>::ForwardType, + typename CallbackParamTraits<X4>::ForwardType, + typename CallbackParamTraits<X5>::ForwardType, + typename CallbackParamTraits<X6>::ForwardType, + typename CallbackParamTraits<X7>::ForwardType); + + typedef R(UnboundRunType)(X1, X2, X3, X4, X5, X6, X7); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X1>::ForwardType x1, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6, + typename CallbackParamTraits<X7>::ForwardType x7) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename CallbackParamTraits<X1>::ForwardType x1, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6, + typename CallbackParamTraits<X7>::ForwardType x7)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6), CallbackForward(x7)); + } +}; + +// Arity 7 -> 6. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5, typename X6, typename X7> +struct Invoker<1, StorageType, R(X1, X2, X3, X4, X5, X6, X7)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X2>::ForwardType, + typename CallbackParamTraits<X3>::ForwardType, + typename CallbackParamTraits<X4>::ForwardType, + typename CallbackParamTraits<X5>::ForwardType, + typename CallbackParamTraits<X6>::ForwardType, + typename CallbackParamTraits<X7>::ForwardType); + + typedef R(UnboundRunType)(X2, X3, X4, X5, X6, X7); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6, + typename CallbackParamTraits<X7>::ForwardType x7) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename CallbackParamTraits<X2>::ForwardType x2, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6, + typename CallbackParamTraits<X7>::ForwardType x7)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6), CallbackForward(x7)); + } +}; + +// Arity 7 -> 5. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5, typename X6, typename X7> +struct Invoker<2, StorageType, R(X1, X2, X3, X4, X5, X6, X7)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X3>::ForwardType, + typename CallbackParamTraits<X4>::ForwardType, + typename CallbackParamTraits<X5>::ForwardType, + typename CallbackParamTraits<X6>::ForwardType, + typename CallbackParamTraits<X7>::ForwardType); + + typedef R(UnboundRunType)(X3, X4, X5, X6, X7); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6, + typename CallbackParamTraits<X7>::ForwardType x7) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename CallbackParamTraits<X3>::ForwardType x3, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6, + typename CallbackParamTraits<X7>::ForwardType x7)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6), CallbackForward(x7)); + } +}; + +// Arity 7 -> 4. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5, typename X6, typename X7> +struct Invoker<3, StorageType, R(X1, X2, X3, X4, X5, X6, X7)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X4>::ForwardType, + typename CallbackParamTraits<X5>::ForwardType, + typename CallbackParamTraits<X6>::ForwardType, + typename CallbackParamTraits<X7>::ForwardType); + + typedef R(UnboundRunType)(X4, X5, X6, X7); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6, + typename CallbackParamTraits<X7>::ForwardType x7) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + typedef typename StorageType::Bound3UnwrapTraits Bound3UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + typename Bound3UnwrapTraits::ForwardType x3 = + Bound3UnwrapTraits::Unwrap(storage->p3_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename Bound3UnwrapTraits::ForwardType, + typename CallbackParamTraits<X4>::ForwardType x4, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6, + typename CallbackParamTraits<X7>::ForwardType x7)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6), CallbackForward(x7)); + } +}; + +// Arity 7 -> 3. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5, typename X6, typename X7> +struct Invoker<4, StorageType, R(X1, X2, X3, X4, X5, X6, X7)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X5>::ForwardType, + typename CallbackParamTraits<X6>::ForwardType, + typename CallbackParamTraits<X7>::ForwardType); + + typedef R(UnboundRunType)(X5, X6, X7); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6, + typename CallbackParamTraits<X7>::ForwardType x7) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + typedef typename StorageType::Bound3UnwrapTraits Bound3UnwrapTraits; + typedef typename StorageType::Bound4UnwrapTraits Bound4UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + typename Bound3UnwrapTraits::ForwardType x3 = + Bound3UnwrapTraits::Unwrap(storage->p3_); + typename Bound4UnwrapTraits::ForwardType x4 = + Bound4UnwrapTraits::Unwrap(storage->p4_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename Bound3UnwrapTraits::ForwardType, + typename Bound4UnwrapTraits::ForwardType, + typename CallbackParamTraits<X5>::ForwardType x5, + typename CallbackParamTraits<X6>::ForwardType x6, + typename CallbackParamTraits<X7>::ForwardType x7)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6), CallbackForward(x7)); + } +}; + +// Arity 7 -> 2. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5, typename X6, typename X7> +struct Invoker<5, StorageType, R(X1, X2, X3, X4, X5, X6, X7)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X6>::ForwardType, + typename CallbackParamTraits<X7>::ForwardType); + + typedef R(UnboundRunType)(X6, X7); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X6>::ForwardType x6, + typename CallbackParamTraits<X7>::ForwardType x7) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + typedef typename StorageType::Bound3UnwrapTraits Bound3UnwrapTraits; + typedef typename StorageType::Bound4UnwrapTraits Bound4UnwrapTraits; + typedef typename StorageType::Bound5UnwrapTraits Bound5UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + typename Bound3UnwrapTraits::ForwardType x3 = + Bound3UnwrapTraits::Unwrap(storage->p3_); + typename Bound4UnwrapTraits::ForwardType x4 = + Bound4UnwrapTraits::Unwrap(storage->p4_); + typename Bound5UnwrapTraits::ForwardType x5 = + Bound5UnwrapTraits::Unwrap(storage->p5_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename Bound3UnwrapTraits::ForwardType, + typename Bound4UnwrapTraits::ForwardType, + typename Bound5UnwrapTraits::ForwardType, + typename CallbackParamTraits<X6>::ForwardType x6, + typename CallbackParamTraits<X7>::ForwardType x7)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6), CallbackForward(x7)); + } +}; + +// Arity 7 -> 1. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5, typename X6, typename X7> +struct Invoker<6, StorageType, R(X1, X2, X3, X4, X5, X6, X7)> { + typedef R(RunType)(BindStateBase*, + typename CallbackParamTraits<X7>::ForwardType); + + typedef R(UnboundRunType)(X7); + + static R Run(BindStateBase* base, + typename CallbackParamTraits<X7>::ForwardType x7) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + typedef typename StorageType::Bound3UnwrapTraits Bound3UnwrapTraits; + typedef typename StorageType::Bound4UnwrapTraits Bound4UnwrapTraits; + typedef typename StorageType::Bound5UnwrapTraits Bound5UnwrapTraits; + typedef typename StorageType::Bound6UnwrapTraits Bound6UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + typename Bound3UnwrapTraits::ForwardType x3 = + Bound3UnwrapTraits::Unwrap(storage->p3_); + typename Bound4UnwrapTraits::ForwardType x4 = + Bound4UnwrapTraits::Unwrap(storage->p4_); + typename Bound5UnwrapTraits::ForwardType x5 = + Bound5UnwrapTraits::Unwrap(storage->p5_); + typename Bound6UnwrapTraits::ForwardType x6 = + Bound6UnwrapTraits::Unwrap(storage->p6_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename Bound3UnwrapTraits::ForwardType, + typename Bound4UnwrapTraits::ForwardType, + typename Bound5UnwrapTraits::ForwardType, + typename Bound6UnwrapTraits::ForwardType, + typename CallbackParamTraits<X7>::ForwardType x7)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6), CallbackForward(x7)); + } +}; + +// Arity 7 -> 0. +template <typename StorageType, typename R,typename X1, typename X2, + typename X3, typename X4, typename X5, typename X6, typename X7> +struct Invoker<7, StorageType, R(X1, X2, X3, X4, X5, X6, X7)> { + typedef R(RunType)(BindStateBase*); + + typedef R(UnboundRunType)(); + + static R Run(BindStateBase* base) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. + typedef typename StorageType::Bound1UnwrapTraits Bound1UnwrapTraits; + typedef typename StorageType::Bound2UnwrapTraits Bound2UnwrapTraits; + typedef typename StorageType::Bound3UnwrapTraits Bound3UnwrapTraits; + typedef typename StorageType::Bound4UnwrapTraits Bound4UnwrapTraits; + typedef typename StorageType::Bound5UnwrapTraits Bound5UnwrapTraits; + typedef typename StorageType::Bound6UnwrapTraits Bound6UnwrapTraits; + typedef typename StorageType::Bound7UnwrapTraits Bound7UnwrapTraits; + + typename Bound1UnwrapTraits::ForwardType x1 = + Bound1UnwrapTraits::Unwrap(storage->p1_); + typename Bound2UnwrapTraits::ForwardType x2 = + Bound2UnwrapTraits::Unwrap(storage->p2_); + typename Bound3UnwrapTraits::ForwardType x3 = + Bound3UnwrapTraits::Unwrap(storage->p3_); + typename Bound4UnwrapTraits::ForwardType x4 = + Bound4UnwrapTraits::Unwrap(storage->p4_); + typename Bound5UnwrapTraits::ForwardType x5 = + Bound5UnwrapTraits::Unwrap(storage->p5_); + typename Bound6UnwrapTraits::ForwardType x6 = + Bound6UnwrapTraits::Unwrap(storage->p6_); + typename Bound7UnwrapTraits::ForwardType x7 = + Bound7UnwrapTraits::Unwrap(storage->p7_); + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void(typename Bound1UnwrapTraits::ForwardType, + typename Bound2UnwrapTraits::ForwardType, + typename Bound3UnwrapTraits::ForwardType, + typename Bound4UnwrapTraits::ForwardType, + typename Bound5UnwrapTraits::ForwardType, + typename Bound6UnwrapTraits::ForwardType, + typename Bound7UnwrapTraits::ForwardType)> + ::MakeItSo(storage->runnable_, CallbackForward(x1), + CallbackForward(x2), CallbackForward(x3), + CallbackForward(x4), CallbackForward(x5), + CallbackForward(x6), CallbackForward(x7)); + } +}; + + +// BindState<> +// +// This stores all the state passed into Bind() and is also where most +// of the template resolution magic occurs. +// +// Runnable is the functor we are binding arguments to. +// RunType is type of the Run() function that the Invoker<> should use. +// Normally, this is the same as the RunType of the Runnable, but it can +// be different if an adapter like IgnoreResult() has been used. +// +// BoundArgsType contains the storage type for all the bound arguments by +// (ab)using a function type. +template <typename Runnable, typename RunType, typename BoundArgsType> +struct BindState; + +template <typename Runnable, typename RunType> +struct BindState<Runnable, RunType, void()> : public BindStateBase { + typedef Runnable RunnableType; + typedef false_type IsWeakCall; + typedef Invoker<0, BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + explicit BindState(const Runnable& runnable) + : runnable_(runnable) { + } + + virtual ~BindState() { } + + RunnableType runnable_; +}; + +template <typename Runnable, typename RunType, typename P1> +struct BindState<Runnable, RunType, void(P1)> : public BindStateBase { + typedef Runnable RunnableType; + typedef IsWeakMethod<HasIsMethodTag<Runnable>::value, P1> IsWeakCall; + typedef Invoker<1, BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + + // Convenience typedefs for bound argument types. + typedef UnwrapTraits<P1> Bound1UnwrapTraits; + + BindState(const Runnable& runnable, const P1& p1) + : runnable_(runnable), + p1_(p1) { + MaybeRefcount<HasIsMethodTag<Runnable>::value, P1>::AddRef(p1_); + } + + virtual ~BindState() { MaybeRefcount<HasIsMethodTag<Runnable>::value, + P1>::Release(p1_); } + + RunnableType runnable_; + P1 p1_; +}; + +template <typename Runnable, typename RunType, typename P1, typename P2> +struct BindState<Runnable, RunType, void(P1, P2)> : public BindStateBase { + typedef Runnable RunnableType; + typedef IsWeakMethod<HasIsMethodTag<Runnable>::value, P1> IsWeakCall; + typedef Invoker<2, BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + + // Convenience typedefs for bound argument types. + typedef UnwrapTraits<P1> Bound1UnwrapTraits; + typedef UnwrapTraits<P2> Bound2UnwrapTraits; + + BindState(const Runnable& runnable, const P1& p1, const P2& p2) + : runnable_(runnable), + p1_(p1), + p2_(p2) { + MaybeRefcount<HasIsMethodTag<Runnable>::value, P1>::AddRef(p1_); + } + + virtual ~BindState() { MaybeRefcount<HasIsMethodTag<Runnable>::value, + P1>::Release(p1_); } + + RunnableType runnable_; + P1 p1_; + P2 p2_; +}; + +template <typename Runnable, typename RunType, typename P1, typename P2, + typename P3> +struct BindState<Runnable, RunType, void(P1, P2, P3)> : public BindStateBase { + typedef Runnable RunnableType; + typedef IsWeakMethod<HasIsMethodTag<Runnable>::value, P1> IsWeakCall; + typedef Invoker<3, BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + + // Convenience typedefs for bound argument types. + typedef UnwrapTraits<P1> Bound1UnwrapTraits; + typedef UnwrapTraits<P2> Bound2UnwrapTraits; + typedef UnwrapTraits<P3> Bound3UnwrapTraits; + + BindState(const Runnable& runnable, const P1& p1, const P2& p2, const P3& p3) + : runnable_(runnable), + p1_(p1), + p2_(p2), + p3_(p3) { + MaybeRefcount<HasIsMethodTag<Runnable>::value, P1>::AddRef(p1_); + } + + virtual ~BindState() { MaybeRefcount<HasIsMethodTag<Runnable>::value, + P1>::Release(p1_); } + + RunnableType runnable_; + P1 p1_; + P2 p2_; + P3 p3_; +}; + +template <typename Runnable, typename RunType, typename P1, typename P2, + typename P3, typename P4> +struct BindState<Runnable, RunType, void(P1, P2, P3, + P4)> : public BindStateBase { + typedef Runnable RunnableType; + typedef IsWeakMethod<HasIsMethodTag<Runnable>::value, P1> IsWeakCall; + typedef Invoker<4, BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + + // Convenience typedefs for bound argument types. + typedef UnwrapTraits<P1> Bound1UnwrapTraits; + typedef UnwrapTraits<P2> Bound2UnwrapTraits; + typedef UnwrapTraits<P3> Bound3UnwrapTraits; + typedef UnwrapTraits<P4> Bound4UnwrapTraits; + + BindState(const Runnable& runnable, const P1& p1, const P2& p2, const P3& p3, + const P4& p4) + : runnable_(runnable), + p1_(p1), + p2_(p2), + p3_(p3), + p4_(p4) { + MaybeRefcount<HasIsMethodTag<Runnable>::value, P1>::AddRef(p1_); + } + + virtual ~BindState() { MaybeRefcount<HasIsMethodTag<Runnable>::value, + P1>::Release(p1_); } + + RunnableType runnable_; + P1 p1_; + P2 p2_; + P3 p3_; + P4 p4_; +}; + +template <typename Runnable, typename RunType, typename P1, typename P2, + typename P3, typename P4, typename P5> +struct BindState<Runnable, RunType, void(P1, P2, P3, P4, + P5)> : public BindStateBase { + typedef Runnable RunnableType; + typedef IsWeakMethod<HasIsMethodTag<Runnable>::value, P1> IsWeakCall; + typedef Invoker<5, BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + + // Convenience typedefs for bound argument types. + typedef UnwrapTraits<P1> Bound1UnwrapTraits; + typedef UnwrapTraits<P2> Bound2UnwrapTraits; + typedef UnwrapTraits<P3> Bound3UnwrapTraits; + typedef UnwrapTraits<P4> Bound4UnwrapTraits; + typedef UnwrapTraits<P5> Bound5UnwrapTraits; + + BindState(const Runnable& runnable, const P1& p1, const P2& p2, const P3& p3, + const P4& p4, const P5& p5) + : runnable_(runnable), + p1_(p1), + p2_(p2), + p3_(p3), + p4_(p4), + p5_(p5) { + MaybeRefcount<HasIsMethodTag<Runnable>::value, P1>::AddRef(p1_); + } + + virtual ~BindState() { MaybeRefcount<HasIsMethodTag<Runnable>::value, + P1>::Release(p1_); } + + RunnableType runnable_; + P1 p1_; + P2 p2_; + P3 p3_; + P4 p4_; + P5 p5_; +}; + +template <typename Runnable, typename RunType, typename P1, typename P2, + typename P3, typename P4, typename P5, typename P6> +struct BindState<Runnable, RunType, void(P1, P2, P3, P4, P5, + P6)> : public BindStateBase { + typedef Runnable RunnableType; + typedef IsWeakMethod<HasIsMethodTag<Runnable>::value, P1> IsWeakCall; + typedef Invoker<6, BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + + // Convenience typedefs for bound argument types. + typedef UnwrapTraits<P1> Bound1UnwrapTraits; + typedef UnwrapTraits<P2> Bound2UnwrapTraits; + typedef UnwrapTraits<P3> Bound3UnwrapTraits; + typedef UnwrapTraits<P4> Bound4UnwrapTraits; + typedef UnwrapTraits<P5> Bound5UnwrapTraits; + typedef UnwrapTraits<P6> Bound6UnwrapTraits; + + BindState(const Runnable& runnable, const P1& p1, const P2& p2, const P3& p3, + const P4& p4, const P5& p5, const P6& p6) + : runnable_(runnable), + p1_(p1), + p2_(p2), + p3_(p3), + p4_(p4), + p5_(p5), + p6_(p6) { + MaybeRefcount<HasIsMethodTag<Runnable>::value, P1>::AddRef(p1_); + } + + virtual ~BindState() { MaybeRefcount<HasIsMethodTag<Runnable>::value, + P1>::Release(p1_); } + + RunnableType runnable_; + P1 p1_; + P2 p2_; + P3 p3_; + P4 p4_; + P5 p5_; + P6 p6_; +}; + +template <typename Runnable, typename RunType, typename P1, typename P2, + typename P3, typename P4, typename P5, typename P6, typename P7> +struct BindState<Runnable, RunType, void(P1, P2, P3, P4, P5, P6, + P7)> : public BindStateBase { + typedef Runnable RunnableType; + typedef IsWeakMethod<HasIsMethodTag<Runnable>::value, P1> IsWeakCall; + typedef Invoker<7, BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + + // Convenience typedefs for bound argument types. + typedef UnwrapTraits<P1> Bound1UnwrapTraits; + typedef UnwrapTraits<P2> Bound2UnwrapTraits; + typedef UnwrapTraits<P3> Bound3UnwrapTraits; + typedef UnwrapTraits<P4> Bound4UnwrapTraits; + typedef UnwrapTraits<P5> Bound5UnwrapTraits; + typedef UnwrapTraits<P6> Bound6UnwrapTraits; + typedef UnwrapTraits<P7> Bound7UnwrapTraits; + + BindState(const Runnable& runnable, const P1& p1, const P2& p2, const P3& p3, + const P4& p4, const P5& p5, const P6& p6, const P7& p7) + : runnable_(runnable), + p1_(p1), + p2_(p2), + p3_(p3), + p4_(p4), + p5_(p5), + p6_(p6), + p7_(p7) { + MaybeRefcount<HasIsMethodTag<Runnable>::value, P1>::AddRef(p1_); + } + + virtual ~BindState() { MaybeRefcount<HasIsMethodTag<Runnable>::value, + P1>::Release(p1_); } + + RunnableType runnable_; + P1 p1_; + P2 p2_; + P3 p3_; + P4 p4_; + P5 p5_; + P6 p6_; + P7 p7_; +}; + +} // namespace internal +} // namespace base + +#endif // BASE_BIND_INTERNAL_H_
diff --git a/src/base/bind_internal.h.pump b/src/base/bind_internal.h.pump new file mode 100644 index 0000000..4760af7 --- /dev/null +++ b/src/base/bind_internal.h.pump
@@ -0,0 +1,504 @@ +$$ 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 +$range ARITY 0..MAX_ARITY + +// 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. + +#ifndef BASE_BIND_INTERNAL_H_ +#define BASE_BIND_INTERNAL_H_ + +#include "base/bind_helpers.h" +#include "base/callback_internal.h" +#include "base/memory/raw_scoped_refptr_mismatch_checker.h" +#include "base/memory/weak_ptr.h" +#include "base/template_util.h" +#include "build/build_config.h" + +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) +// Check for C++11 support +#if (_MSC_VER >= 1700) || (__cplusplus > 199711L) +#include "base/bind_internal_functor.h" +#endif +#endif + +#if defined(OS_WIN) +#include "base/bind_internal_win.h" +#endif + +namespace base { +namespace internal { + +// See base/callback.h for user documentation. +// +// +// CONCEPTS: +// Runnable -- A type (really a type class) that has a single Run() method +// and a RunType typedef that corresponds to the type of Run(). +// A Runnable can declare that it should treated like a method +// call by including a typedef named IsMethod. The value of +// this typedef is NOT inspected, only the existence. When a +// Runnable declares itself a method, Bind() will enforce special +// refcounting + WeakPtr handling semantics for the first +// parameter which is expected to be an object. +// Functor -- A copyable type representing something that should be called. +// All function pointers, Callback<>, and Runnables are functors +// even if the invocation syntax differs. +// RunType -- A function type (as opposed to function _pointer_ type) for +// a Run() function. Usually just a convenience typedef. +// (Bound)ArgsType -- A function type that is being (ab)used to store the +// types of set of arguments. The "return" type is always +// void here. We use this hack so that we do not need +// a new type name for each arity of type. (eg., +// BindState1, BindState2). This makes forward +// declarations and friending much much easier. +// +// Types: +// RunnableAdapter<> -- Wraps the various "function" pointer types into an +// object that adheres to the Runnable interface. +// There are |3*ARITY| RunnableAdapter types. +// FunctionTraits<> -- Type traits that unwrap a function signature into a +// a set of easier to use typedefs. Used mainly for +// compile time asserts. +// There are |ARITY| FunctionTraits types. +// ForceVoidReturn<> -- Helper class for translating function signatures to +// equivalent forms with a "void" return type. +// There are |ARITY| ForceVoidReturn types. +// FunctorTraits<> -- Type traits used determine the correct RunType and +// RunnableType for a Functor. This is where function +// signature adapters are applied. +// There are |ARITY| ForceVoidReturn types. +// MakeRunnable<> -- Takes a Functor and returns an object in the Runnable +// type class that represents the underlying Functor. +// There are |O(1)| MakeRunnable types. +// InvokeHelper<> -- Take a Runnable + arguments and actully invokes it. +// Handle the differing syntaxes needed for WeakPtr<> support, +// and for ignoring return values. This is separate from +// Invoker to avoid creating multiple version of Invoker<> +// which grows at O(n^2) with the arity. +// There are |k*ARITY| InvokeHelper types. +// Invoker<> -- Unwraps the curried parameters and executes the Runnable. +// There are |(ARITY^2 + ARITY)/2| Invoketypes. +// BindState<> -- Stores the curried parameters, and is the main entry point +// into the Bind() system, doing most of the type resolution. +// There are ARITY BindState types. + +// RunnableAdapter<> +// +// The RunnableAdapter<> templates provide a uniform interface for invoking +// a function pointer, method pointer, or const method pointer. The adapter +// exposes a Run() method with an appropriate signature. Using this wrapper +// allows for writing code that supports all three pointer types without +// undue repetition. Without it, a lot of code would need to be repeated 3 +// times. +// +// For method pointers and const method pointers the first argument to Run() +// is considered to be the received of the method. This is similar to STL's +// mem_fun(). +// +// This class also exposes a RunType typedef that is the function type of the +// Run() function. +// +// If and only if the wrapper contains a method or const method pointer, an +// IsMethod typedef is exposed. The existence of this typedef (NOT the value) +// marks that the wrapper should be considered a method wrapper. + +template <typename Functor> +class RunnableAdapter; + +$for ARITY [[ +$range ARG 1..ARITY + +// Function: Arity $(ARITY). +template <typename R[[]] +$if ARITY > 0[[, ]] $for ARG , [[typename A$(ARG)]]> +class RunnableAdapter<R(*)($for ARG , [[A$(ARG)]])> { + public: + typedef R (RunType)($for ARG , [[A$(ARG)]]); + + explicit RunnableAdapter(R(*function)($for ARG , [[A$(ARG)]])) + : function_(function) { + } + + R Run($for ARG , [[typename CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]]) { + return function_($for ARG , [[CallbackForward(a$(ARG))]]); + } + + private: + R (*function_)($for ARG , [[A$(ARG)]]); +}; + +// Method: Arity $(ARITY). +template <typename R, typename T[[]] +$if ARITY > 0[[, ]] $for ARG , [[typename A$(ARG)]]> +class RunnableAdapter<R(T::*)($for ARG , [[A$(ARG)]])> { + public: + typedef R (RunType)(T*[[]] +$if ARITY > 0[[, ]] $for ARG , [[A$(ARG)]]); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)($for ARG , [[A$(ARG)]])) + : method_(method) { + } + + R Run(T* object[[]] +$if ARITY > 0[[, ]] $for ARG, [[typename CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]]) { + return (object->*method_)($for ARG , [[CallbackForward(a$(ARG))]]); + } + + private: + R (T::*method_)($for ARG , [[A$(ARG)]]); +}; + +// Const Method: Arity $(ARITY). +template <typename R, typename T[[]] +$if ARITY > 0[[, ]] $for ARG , [[typename A$(ARG)]]> +class RunnableAdapter<R(T::*)($for ARG , [[A$(ARG)]]) const> { + public: + typedef R (RunType)(const T*[[]] +$if ARITY > 0[[, ]] $for ARG , [[A$(ARG)]]); + typedef true_type IsMethod; + + explicit RunnableAdapter(R(T::*method)($for ARG , [[A$(ARG)]]) const) + : method_(method) { + } + + R Run(const T* object[[]] +$if ARITY > 0[[, ]] $for ARG, [[typename CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]]) { + return (object->*method_)($for ARG , [[CallbackForward(a$(ARG))]]); + } + + private: + R (T::*method_)($for ARG , [[A$(ARG)]]) const; +}; + +]] $$ for ARITY + + +// FunctionTraits<> +// +// Breaks a function signature apart into typedefs for easier introspection. +template <typename Sig> +struct FunctionTraits; + +$for ARITY [[ +$range ARG 1..ARITY + +template <typename R[[]] +$if ARITY > 0[[, ]] $for ARG , [[typename A$(ARG)]]> +struct FunctionTraits<R($for ARG , [[A$(ARG)]])> { + typedef R ReturnType; +$for ARG [[ + + typedef A$(ARG) A$(ARG)Type; +]] + +}; + +]] + + +// ForceVoidReturn<> +// +// Set of templates that support forcing the function return type to void. +template <typename Sig> +struct ForceVoidReturn; + +$for ARITY [[ +$range ARG 1..ARITY + +template <typename R[[]] +$if ARITY > 0[[, ]] $for ARG , [[typename A$(ARG)]]> +struct ForceVoidReturn<R($for ARG , [[A$(ARG)]])> { + typedef void(RunType)($for ARG , [[A$(ARG)]]); +}; + +]] $$ for ARITY + + +// FunctorTraits<> +// +// See description at top of file. +template <typename T> +struct FunctorTraits { + typedef RunnableAdapter<T> RunnableType; + typedef typename RunnableType::RunType RunType; +}; + +template <typename T> +struct FunctorTraits<IgnoreResultHelper<T> > { + typedef typename FunctorTraits<T>::RunnableType RunnableType; + typedef typename ForceVoidReturn< + typename RunnableType::RunType>::RunType RunType; +}; + +template <typename T> +struct FunctorTraits<Callback<T> > { + typedef Callback<T> RunnableType; + typedef typename Callback<T>::RunType RunType; +}; + + +// MakeRunnable<> +// +// Converts a passed in functor to a RunnableType using type inference. + +template <typename T> +typename FunctorTraits<T>::RunnableType MakeRunnable(const T& t) { + return RunnableAdapter<T>(t); +} + +template <typename T> +typename FunctorTraits<T>::RunnableType +MakeRunnable(const IgnoreResultHelper<T>& t) { + return MakeRunnable(t.functor_); +} + +template <typename T> +const typename FunctorTraits<Callback<T> >::RunnableType& +MakeRunnable(const Callback<T>& t) { + DCHECK(!t.is_null()); + return t; +} + + +// InvokeHelper<> +// +// There are 3 logical InvokeHelper<> specializations: normal, void-return, +// WeakCalls. +// +// The normal type just calls the underlying runnable. +// +// We need a InvokeHelper to handle void return types in order to support +// IgnoreResult(). Normally, if the Runnable's RunType had a void return, +// the template system would just accept "return functor.Run()" ignoring +// the fact that a void function is being used with return. This piece of +// sugar breaks though when the Runnable's RunType is not void. Thus, we +// need a partial specialization to change the syntax to drop the "return" +// from the invocation call. +// +// WeakCalls similarly need special syntax that is applied to the first +// argument to check if they should no-op themselves. +template <bool IsWeakCall, typename ReturnType, typename Runnable, + typename ArgsType> +struct InvokeHelper; + +$for ARITY [[ +$range ARG 1..ARITY + +template <typename ReturnType, typename Runnable[[]] +$if ARITY > 0 [[,]] $for ARG , [[typename A$(ARG)]]> +struct InvokeHelper<false, ReturnType, Runnable, + void($for ARG , [[A$(ARG)]])> { + static ReturnType MakeItSo(Runnable runnable[[]] +$if ARITY > 0[[, ]] $for ARG , [[A$(ARG) a$(ARG)]]) { + return runnable.Run($for ARG , [[CallbackForward(a$(ARG))]]); + } +}; + +template <typename Runnable[[]] +$if ARITY > 0 [[,]] $for ARG , [[typename A$(ARG)]]> +struct InvokeHelper<false, void, Runnable, + void($for ARG , [[A$(ARG)]])> { + static void MakeItSo(Runnable runnable[[]] +$if ARITY > 0[[, ]] $for ARG , [[A$(ARG) a$(ARG)]]) { + runnable.Run($for ARG , [[CallbackForward(a$(ARG))]]); + } +}; + +$if ARITY > 0 [[ + +template <typename Runnable[[]], $for ARG , [[typename A$(ARG)]]> +struct InvokeHelper<true, void, Runnable, + void($for ARG , [[A$(ARG)]])> { + static void MakeItSo(Runnable runnable[[]] +$if ARITY > 0[[, ]] $for ARG , [[A$(ARG) a$(ARG)]]) { + if (!a1.get()) { + return; + } + + runnable.Run($for ARG , [[CallbackForward(a$(ARG))]]); + } +}; + +]] + +]] $$ for ARITY + +#if !defined(_MSC_VER) + +template <typename ReturnType, typename Runnable, typename ArgsType> +struct InvokeHelper<true, ReturnType, Runnable, ArgsType> { + // WeakCalls are only supported for functions with a void return type. + // Otherwise, the function result would be undefined if the the WeakPtr<> + // is invalidated. + COMPILE_ASSERT(is_void<ReturnType>::value, + weak_ptrs_can_only_bind_to_methods_without_return_values); +}; + +#endif + +// Invoker<> +// +// See description at the top of the file. +template <int NumBound, typename Storage, typename RunType> +struct Invoker; + +$for ARITY [[ + +$$ Number of bound arguments. +$range BOUND 0..ARITY +$for BOUND [[ + +$var UNBOUND = ARITY - BOUND +$range ARG 1..ARITY +$range BOUND_ARG 1..BOUND +$range UNBOUND_ARG (ARITY - UNBOUND + 1)..ARITY + +// Arity $(ARITY) -> $(UNBOUND). +template <typename StorageType, typename R[[]] +$if ARITY > 0 [[,]][[]] +$for ARG , [[typename X$(ARG)]]> +struct Invoker<$(BOUND), StorageType, R($for ARG , [[X$(ARG)]])> { + typedef R(RunType)(BindStateBase*[[]] +$if UNBOUND != 0 [[, ]] +$for UNBOUND_ARG , [[typename CallbackParamTraits<X$(UNBOUND_ARG)>::ForwardType]]); + + typedef R(UnboundRunType)($for UNBOUND_ARG , [[X$(UNBOUND_ARG)]]); + + static R Run(BindStateBase* base[[]] +$if UNBOUND != 0 [[, ]][[]] +$for UNBOUND_ARG , [[ +typename CallbackParamTraits<X$(UNBOUND_ARG)>::ForwardType x$(UNBOUND_ARG) +]][[]] +) { + StorageType* storage = static_cast<StorageType*>(base); + + // Local references to make debugger stepping easier. If in a debugger, + // you really want to warp ahead and step through the + // InvokeHelper<>::MakeItSo() call below. +$for BOUND_ARG +[[ + + typedef typename StorageType::Bound$(BOUND_ARG)UnwrapTraits Bound$(BOUND_ARG)UnwrapTraits; +]] + + +$for BOUND_ARG +[[ + + typename Bound$(BOUND_ARG)UnwrapTraits::ForwardType x$(BOUND_ARG) = + Bound$(BOUND_ARG)UnwrapTraits::Unwrap(storage->p$(BOUND_ARG)_); +]] + + return InvokeHelper<StorageType::IsWeakCall::value, R, + typename StorageType::RunnableType, + void( +$for BOUND_ARG , [[ +typename Bound$(BOUND_ARG)UnwrapTraits::ForwardType +]] + +$if UNBOUND > 0 [[$if BOUND > 0 [[, ]]]][[]] + +$for UNBOUND_ARG , [[ +typename CallbackParamTraits<X$(UNBOUND_ARG)>::ForwardType x$(UNBOUND_ARG) +]] +)> + ::MakeItSo(storage->runnable_ +$if ARITY > 0[[, ]] $for ARG , [[CallbackForward(x$(ARG))]]); + } +}; + +]] $$ for BOUND +]] $$ for ARITY + + +// BindState<> +// +// This stores all the state passed into Bind() and is also where most +// of the template resolution magic occurs. +// +// Runnable is the functor we are binding arguments to. +// RunType is type of the Run() function that the Invoker<> should use. +// Normally, this is the same as the RunType of the Runnable, but it can +// be different if an adapter like IgnoreResult() has been used. +// +// BoundArgsType contains the storage type for all the bound arguments by +// (ab)using a function type. +template <typename Runnable, typename RunType, typename BoundArgsType> +struct BindState; + +$for ARITY [[ +$range ARG 1..ARITY + +template <typename Runnable, typename RunType[[]] +$if ARITY > 0[[, ]] $for ARG , [[typename P$(ARG)]]> +struct BindState<Runnable, RunType, void($for ARG , [[P$(ARG)]])> : public BindStateBase { + typedef Runnable RunnableType; + +$if ARITY > 0 [[ + typedef IsWeakMethod<HasIsMethodTag<Runnable>::value, P1> IsWeakCall; +]] $else [[ + typedef false_type IsWeakCall; +]] + + typedef Invoker<$(ARITY), BindState, RunType> InvokerType; + typedef typename InvokerType::UnboundRunType UnboundRunType; + +$if ARITY > 0 [[ + + // Convenience typedefs for bound argument types. + +$for ARG [[ + typedef UnwrapTraits<P$(ARG)> Bound$(ARG)UnwrapTraits; + +]] $$ for ARG + + +]] $$ if ARITY > 0 + +$$ The extra [[ ]] is needed to massage spacing. Silly pump.py. +[[ ]]$if ARITY == 0 [[explicit ]]BindState(const Runnable& runnable +$if ARITY > 0 [[, ]] $for ARG , [[const P$(ARG)& p$(ARG)]]) + : runnable_(runnable)[[]] +$if ARITY == 0 [[ + { + +]] $else [[ +, $for ARG , [[ + + p$(ARG)_(p$(ARG)) +]] { + MaybeRefcount<HasIsMethodTag<Runnable>::value, P1>::AddRef(p1_); + +]] + } + + virtual ~BindState() { +$if ARITY > 0 [[ + MaybeRefcount<HasIsMethodTag<Runnable>::value, P1>::Release(p1_); +]] + } + + RunnableType runnable_; + +$for ARG [[ + P$(ARG) p$(ARG)_; + +]] +}; + +]] $$ for ARITY + +} // namespace internal +} // namespace base + +#endif // BASE_BIND_INTERNAL_H_
diff --git a/src/base/bind_internal_functor.h b/src/base/bind_internal_functor.h new file mode 100644 index 0000000..ede69bc --- /dev/null +++ b/src/base/bind_internal_functor.h
@@ -0,0 +1,221 @@ +// This file was GENERATED by command: +// pump.py bind_internal_functor.h.pump +// DO NOT EDIT BY HAND!!! + + +// 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_BIND_INTERNAL_FUNCTOR_H_ +#define BASE_BIND_INTERNAL_FUNCTOR_H_ + +namespace base { +namespace internal { + +// The RunnableAdapterFunctor<> template is used in a template specialization +// for RunnableAdapter<> which enables support for functor objects, including +// C++11 lambdas. The functor signature is deduced from the type of the +// operator() method using decltype(). +// +// All supported functors are assumed to have a const qualifier in their +// operator() method. + +template <typename Functor> +class RunnableAdapter; + +template <typename Functor, typename Sig> +class RunnableAdapterFunctor; + +template <typename Functor> +class RunnableAdapter + : public RunnableAdapterFunctor<Functor, decltype(&Functor::operator())> { + public: + typedef RunnableAdapterFunctor<Functor, + decltype(&Functor::operator())> BaseType; + + explicit RunnableAdapter(Functor functor) + : BaseType(functor) {} +}; + +// Functor: Arity 0. +template <typename F, typename T, typename R> +class RunnableAdapterFunctor<F, R(T::*)() const> { + public: + typedef R (RunType)(); + + explicit RunnableAdapterFunctor(F functor) + : functor_(functor) { + } + + R Run() { + return functor_(); + } + + private: + F functor_; +}; + +// Functor: Arity 1. +template <typename F, typename T, typename R, typename A1> +class RunnableAdapterFunctor<F, R(T::*)(A1) const> { + public: + typedef R (RunType)(A1); + + explicit RunnableAdapterFunctor(F functor) + : functor_(functor) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1) { + return functor_(CallbackForward(a1)); + } + + private: + F functor_; +}; + +// Functor: Arity 2. +template <typename F, typename T, typename R, typename A1, typename A2> +class RunnableAdapterFunctor<F, R(T::*)(A1, A2) const> { + public: + typedef R (RunType)(A1, A2); + + explicit RunnableAdapterFunctor(F functor) + : functor_(functor) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2) { + return functor_(CallbackForward(a1), CallbackForward(a2)); + } + + private: + F functor_; +}; + +// Functor: Arity 3. +template <typename F, typename T, typename R, typename A1, typename A2, + typename A3> +class RunnableAdapterFunctor<F, R(T::*)(A1, A2, A3) const> { + public: + typedef R (RunType)(A1, A2, A3); + + explicit RunnableAdapterFunctor(F functor) + : functor_(functor) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3) { + return functor_(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3)); + } + + private: + F functor_; +}; + +// Functor: Arity 4. +template <typename F, typename T, typename R, typename A1, typename A2, + typename A3, typename A4> +class RunnableAdapterFunctor<F, R(T::*)(A1, A2, A3, A4) const> { + public: + typedef R (RunType)(A1, A2, A3, A4); + + explicit RunnableAdapterFunctor(F functor) + : functor_(functor) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4) { + return functor_(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4)); + } + + private: + F functor_; +}; + +// Functor: Arity 5. +template <typename F, typename T, typename R, typename A1, typename A2, + typename A3, typename A4, typename A5> +class RunnableAdapterFunctor<F, R(T::*)(A1, A2, A3, A4, A5) const> { + public: + typedef R (RunType)(A1, A2, A3, A4, A5); + + explicit RunnableAdapterFunctor(F functor) + : functor_(functor) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5) { + return functor_(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4), CallbackForward(a5)); + } + + private: + F functor_; +}; + +// Functor: Arity 6. +template <typename F, typename T, typename R, typename A1, typename A2, + typename A3, typename A4, typename A5, typename A6> +class RunnableAdapterFunctor<F, R(T::*)(A1, A2, A3, A4, A5, A6) const> { + public: + typedef R (RunType)(A1, A2, A3, A4, A5, A6); + + explicit RunnableAdapterFunctor(F functor) + : functor_(functor) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5, + typename CallbackParamTraits<A6>::ForwardType a6) { + return functor_(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4), CallbackForward(a5), + CallbackForward(a6)); + } + + private: + F functor_; +}; + +// Functor: Arity 7. +template <typename F, typename T, typename R, typename A1, typename A2, + typename A3, typename A4, typename A5, typename A6, typename A7> +class RunnableAdapterFunctor<F, R(T::*)(A1, A2, A3, A4, A5, A6, A7) const> { + public: + typedef R (RunType)(A1, A2, A3, A4, A5, A6, A7); + + explicit RunnableAdapterFunctor(F functor) + : functor_(functor) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5, + typename CallbackParamTraits<A6>::ForwardType a6, + typename CallbackParamTraits<A7>::ForwardType a7) { + return functor_(CallbackForward(a1), CallbackForward(a2), + CallbackForward(a3), CallbackForward(a4), CallbackForward(a5), + CallbackForward(a6), CallbackForward(a7)); + } + + private: + F functor_; +}; + +} // namespace internal +} // namespace base + +#endif // BASE_BIND_INTERNAL_FUNCTOR_H_
diff --git a/src/base/bind_internal_functor.h.pump b/src/base/bind_internal_functor.h.pump new file mode 100644 index 0000000..919521e --- /dev/null +++ b/src/base/bind_internal_functor.h.pump
@@ -0,0 +1,74 @@ +$$ 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 +$range ARITY 0..MAX_ARITY + +// 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_BIND_INTERNAL_FUNCTOR_H_ +#define BASE_BIND_INTERNAL_FUNCTOR_H_ + +namespace base { +namespace internal { + +// The RunnableAdapterFunctor<> template is used in a template specialization +// for RunnableAdapter<> which enables support for functor objects, including +// C++11 lambdas. The functor signature is deduced from the type of the +// operator() method using decltype(). +// +// All supported functors are assumed to have a const qualifier in their +// operator() method. + +template <typename Functor> +class RunnableAdapter; + +template <typename Functor, typename Sig> +class RunnableAdapterFunctor; + +template <typename Functor> +class RunnableAdapter + : public RunnableAdapterFunctor<Functor, decltype(&Functor::operator())> { + public: + typedef RunnableAdapterFunctor<Functor, + decltype(&Functor::operator())> BaseType; + + explicit RunnableAdapter(Functor functor) + : BaseType(functor) {} +}; + +$for ARITY [[ +$range ARG 1..ARITY + +// Functor: Arity $(ARITY). +template <typename F, typename T, typename R[[]] +$if ARITY > 0[[, ]] $for ARG , [[typename A$(ARG)]]> +class RunnableAdapterFunctor<F, R(T::*)($for ARG , [[A$(ARG)]]) const> { + public: + typedef R (RunType)($for ARG , [[A$(ARG)]]); + + explicit RunnableAdapterFunctor(F functor) + : functor_(functor) { + } + + R Run($for ARG , [[typename CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]]) { + return functor_($for ARG , [[CallbackForward(a$(ARG))]]); + } + + private: + F functor_; +}; + +]] $$ for ARITY + +} // namespace internal +} // namespace base + +#endif // BASE_BIND_INTERNAL_FUNCTOR_H_
diff --git a/src/base/bind_internal_win.h b/src/base/bind_internal_win.h new file mode 100644 index 0000000..7a8486a --- /dev/null +++ b/src/base/bind_internal_win.h
@@ -0,0 +1,368 @@ +// This file was GENERATED by command: +// pump.py bind_internal_win.h.pump +// DO NOT EDIT BY HAND!!! + + +// 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. + +// Specializations of RunnableAdapter<> for Windows specific calling +// conventions. Please see base/bind_internal.h for more info. + +#ifndef BASE_BIND_INTERNAL_WIN_H_ +#define BASE_BIND_INTERNAL_WIN_H_ + +// In the x64 architecture in Windows, __fastcall, __stdcall, etc, are all +// the same as __cdecl which would turn the following specializations into +// multiple definitions. +#if !defined(ARCH_CPU_X86_64) + +namespace base { +namespace internal { + +template <typename Functor> +class RunnableAdapter; + +// __stdcall Function: Arity 0. +template <typename R> +class RunnableAdapter<R(__stdcall *)()> { + public: + typedef R (RunType)(); + + explicit RunnableAdapter(R(__stdcall *function)()) + : function_(function) { + } + + R Run() { + return function_(); + } + + private: + R (__stdcall *function_)(); +}; + +// __fastcall Function: Arity 0. +template <typename R> +class RunnableAdapter<R(__fastcall *)()> { + public: + typedef R (RunType)(); + + explicit RunnableAdapter(R(__fastcall *function)()) + : function_(function) { + } + + R Run() { + return function_(); + } + + private: + R (__fastcall *function_)(); +}; + +// __stdcall Function: Arity 1. +template <typename R, typename A1> +class RunnableAdapter<R(__stdcall *)(A1)> { + public: + typedef R (RunType)(A1); + + explicit RunnableAdapter(R(__stdcall *function)(A1)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1) { + return function_(a1); + } + + private: + R (__stdcall *function_)(A1); +}; + +// __fastcall Function: Arity 1. +template <typename R, typename A1> +class RunnableAdapter<R(__fastcall *)(A1)> { + public: + typedef R (RunType)(A1); + + explicit RunnableAdapter(R(__fastcall *function)(A1)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1) { + return function_(a1); + } + + private: + R (__fastcall *function_)(A1); +}; + +// __stdcall Function: Arity 2. +template <typename R, typename A1, typename A2> +class RunnableAdapter<R(__stdcall *)(A1, A2)> { + public: + typedef R (RunType)(A1, A2); + + explicit RunnableAdapter(R(__stdcall *function)(A1, A2)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2) { + return function_(a1, a2); + } + + private: + R (__stdcall *function_)(A1, A2); +}; + +// __fastcall Function: Arity 2. +template <typename R, typename A1, typename A2> +class RunnableAdapter<R(__fastcall *)(A1, A2)> { + public: + typedef R (RunType)(A1, A2); + + explicit RunnableAdapter(R(__fastcall *function)(A1, A2)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2) { + return function_(a1, a2); + } + + private: + R (__fastcall *function_)(A1, A2); +}; + +// __stdcall Function: Arity 3. +template <typename R, typename A1, typename A2, typename A3> +class RunnableAdapter<R(__stdcall *)(A1, A2, A3)> { + public: + typedef R (RunType)(A1, A2, A3); + + explicit RunnableAdapter(R(__stdcall *function)(A1, A2, A3)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3) { + return function_(a1, a2, a3); + } + + private: + R (__stdcall *function_)(A1, A2, A3); +}; + +// __fastcall Function: Arity 3. +template <typename R, typename A1, typename A2, typename A3> +class RunnableAdapter<R(__fastcall *)(A1, A2, A3)> { + public: + typedef R (RunType)(A1, A2, A3); + + explicit RunnableAdapter(R(__fastcall *function)(A1, A2, A3)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3) { + return function_(a1, a2, a3); + } + + private: + R (__fastcall *function_)(A1, A2, A3); +}; + +// __stdcall Function: Arity 4. +template <typename R, typename A1, typename A2, typename A3, typename A4> +class RunnableAdapter<R(__stdcall *)(A1, A2, A3, A4)> { + public: + typedef R (RunType)(A1, A2, A3, A4); + + explicit RunnableAdapter(R(__stdcall *function)(A1, A2, A3, A4)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4) { + return function_(a1, a2, a3, a4); + } + + private: + R (__stdcall *function_)(A1, A2, A3, A4); +}; + +// __fastcall Function: Arity 4. +template <typename R, typename A1, typename A2, typename A3, typename A4> +class RunnableAdapter<R(__fastcall *)(A1, A2, A3, A4)> { + public: + typedef R (RunType)(A1, A2, A3, A4); + + explicit RunnableAdapter(R(__fastcall *function)(A1, A2, A3, A4)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4) { + return function_(a1, a2, a3, a4); + } + + private: + R (__fastcall *function_)(A1, A2, A3, A4); +}; + +// __stdcall Function: Arity 5. +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5> +class RunnableAdapter<R(__stdcall *)(A1, A2, A3, A4, A5)> { + public: + typedef R (RunType)(A1, A2, A3, A4, A5); + + explicit RunnableAdapter(R(__stdcall *function)(A1, A2, A3, A4, A5)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5) { + return function_(a1, a2, a3, a4, a5); + } + + private: + R (__stdcall *function_)(A1, A2, A3, A4, A5); +}; + +// __fastcall Function: Arity 5. +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5> +class RunnableAdapter<R(__fastcall *)(A1, A2, A3, A4, A5)> { + public: + typedef R (RunType)(A1, A2, A3, A4, A5); + + explicit RunnableAdapter(R(__fastcall *function)(A1, A2, A3, A4, A5)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5) { + return function_(a1, a2, a3, a4, a5); + } + + private: + R (__fastcall *function_)(A1, A2, A3, A4, A5); +}; + +// __stdcall Function: Arity 6. +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6> +class RunnableAdapter<R(__stdcall *)(A1, A2, A3, A4, A5, A6)> { + public: + typedef R (RunType)(A1, A2, A3, A4, A5, A6); + + explicit RunnableAdapter(R(__stdcall *function)(A1, A2, A3, A4, A5, A6)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5, + typename CallbackParamTraits<A6>::ForwardType a6) { + return function_(a1, a2, a3, a4, a5, a6); + } + + private: + R (__stdcall *function_)(A1, A2, A3, A4, A5, A6); +}; + +// __fastcall Function: Arity 6. +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6> +class RunnableAdapter<R(__fastcall *)(A1, A2, A3, A4, A5, A6)> { + public: + typedef R (RunType)(A1, A2, A3, A4, A5, A6); + + explicit RunnableAdapter(R(__fastcall *function)(A1, A2, A3, A4, A5, A6)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5, + typename CallbackParamTraits<A6>::ForwardType a6) { + return function_(a1, a2, a3, a4, a5, a6); + } + + private: + R (__fastcall *function_)(A1, A2, A3, A4, A5, A6); +}; + +// __stdcall Function: Arity 7. +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6, typename A7> +class RunnableAdapter<R(__stdcall *)(A1, A2, A3, A4, A5, A6, A7)> { + public: + typedef R (RunType)(A1, A2, A3, A4, A5, A6, A7); + + explicit RunnableAdapter(R(__stdcall *function)(A1, A2, A3, A4, A5, A6, A7)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5, + typename CallbackParamTraits<A6>::ForwardType a6, + typename CallbackParamTraits<A7>::ForwardType a7) { + return function_(a1, a2, a3, a4, a5, a6, a7); + } + + private: + R (__stdcall *function_)(A1, A2, A3, A4, A5, A6, A7); +}; + +// __fastcall Function: Arity 7. +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6, typename A7> +class RunnableAdapter<R(__fastcall *)(A1, A2, A3, A4, A5, A6, A7)> { + public: + typedef R (RunType)(A1, A2, A3, A4, A5, A6, A7); + + explicit RunnableAdapter(R(__fastcall *function)(A1, A2, A3, A4, A5, A6, A7)) + : function_(function) { + } + + R Run(typename CallbackParamTraits<A1>::ForwardType a1, + typename CallbackParamTraits<A2>::ForwardType a2, + typename CallbackParamTraits<A3>::ForwardType a3, + typename CallbackParamTraits<A4>::ForwardType a4, + typename CallbackParamTraits<A5>::ForwardType a5, + typename CallbackParamTraits<A6>::ForwardType a6, + typename CallbackParamTraits<A7>::ForwardType a7) { + return function_(a1, a2, a3, a4, a5, a6, a7); + } + + private: + R (__fastcall *function_)(A1, A2, A3, A4, A5, A6, A7); +}; + +} // namespace internal +} // namespace base + +#endif // !defined(ARCH_CPU_X86_64) + +#endif // BASE_BIND_INTERNAL_WIN_H_
diff --git a/src/base/bind_internal_win.h.pump b/src/base/bind_internal_win.h.pump new file mode 100644 index 0000000..cd108b6 --- /dev/null +++ b/src/base/bind_internal_win.h.pump
@@ -0,0 +1,81 @@ +$$ 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 (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. + +// Specializations of RunnableAdapter<> for Windows specific calling +// conventions. Please see base/bind_internal.h for more info. + +#ifndef BASE_BIND_INTERNAL_WIN_H_ +#define BASE_BIND_INTERNAL_WIN_H_ + +// In the x64 architecture in Windows, __fastcall, __stdcall, etc, are all +// the same as __cdecl which would turn the following specializations into +// multiple definitions. +#if !defined(ARCH_CPU_X86_64) + +namespace base { +namespace internal { + +template <typename Functor> +class RunnableAdapter; + +$range ARITY 0..MAX_ARITY +$for ARITY [[ +$range ARG 1..ARITY + +// __stdcall Function: Arity $(ARITY). +template <typename R[[]] +$if ARITY > 0[[, ]] $for ARG , [[typename A$(ARG)]]> +class RunnableAdapter<R(__stdcall *)($for ARG , [[A$(ARG)]])> { + public: + typedef R (RunType)($for ARG , [[A$(ARG)]]); + + explicit RunnableAdapter(R(__stdcall *function)($for ARG , [[A$(ARG)]])) + : function_(function) { + } + + R Run($for ARG , [[typename CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]]) { + return function_($for ARG , [[a$(ARG)]]); + } + + private: + R (__stdcall *function_)($for ARG , [[A$(ARG)]]); +}; + +// __fastcall Function: Arity $(ARITY). +template <typename R[[]] +$if ARITY > 0[[, ]] $for ARG , [[typename A$(ARG)]]> +class RunnableAdapter<R(__fastcall *)($for ARG , [[A$(ARG)]])> { + public: + typedef R (RunType)($for ARG , [[A$(ARG)]]); + + explicit RunnableAdapter(R(__fastcall *function)($for ARG , [[A$(ARG)]])) + : function_(function) { + } + + R Run($for ARG , [[typename CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]]) { + return function_($for ARG , [[a$(ARG)]]); + } + + private: + R (__fastcall *function_)($for ARG , [[A$(ARG)]]); +}; + +]] $$for ARITY + +} // namespace internal +} // namespace base + +#endif // !defined(ARCH_CPU_X86_64) + +#endif // BASE_BIND_INTERNAL_WIN_H_
diff --git a/src/base/bind_unittest.cc b/src/base/bind_unittest.cc new file mode 100644 index 0000000..1d808a6 --- /dev/null +++ b/src/base/bind_unittest.cc
@@ -0,0 +1,814 @@ +// 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/bind.h" + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::Mock; +using ::testing::Return; +using ::testing::StrictMock; + +namespace base { +namespace { + +class IncompleteType; + +class NoRef { + public: + NoRef() {} + + MOCK_METHOD0(VoidMethod0, void(void)); + MOCK_CONST_METHOD0(VoidConstMethod0, void(void)); + + MOCK_METHOD0(IntMethod0, int(void)); + MOCK_CONST_METHOD0(IntConstMethod0, int(void)); + + private: + // Particularly important in this test to ensure no copies are made. + DISALLOW_COPY_AND_ASSIGN(NoRef); +}; + +class HasRef : public NoRef { + public: + HasRef() {} + + MOCK_CONST_METHOD0(AddRef, void(void)); + MOCK_CONST_METHOD0(Release, bool(void)); + + private: + // Particularly important in this test to ensure no copies are made. + DISALLOW_COPY_AND_ASSIGN(HasRef); +}; + +class HasRefPrivateDtor : public HasRef { + private: + ~HasRefPrivateDtor() {} +}; + +static const int kParentValue = 1; +static const int kChildValue = 2; + +class Parent { + public: + void AddRef(void) const {} + void Release(void) const {} + virtual void VirtualSet() { value = kParentValue; } + void NonVirtualSet() { value = kParentValue; } + int value; +}; + +class Child : public Parent { + public: + virtual void VirtualSet() OVERRIDE { value = kChildValue; } + void NonVirtualSet() { value = kChildValue; } +}; + +class NoRefParent { + public: + virtual void VirtualSet() { value = kParentValue; } + void NonVirtualSet() { value = kParentValue; } + int value; +}; + +class NoRefChild : public NoRefParent { + virtual void VirtualSet() OVERRIDE { value = kChildValue; } + void NonVirtualSet() { value = kChildValue; } +}; + +// Used for probing the number of copies that occur if a type must be coerced +// during argument forwarding in the Run() methods. +struct DerivedCopyCounter { + DerivedCopyCounter(int* copies, int* assigns) + : copies_(copies), assigns_(assigns) { + } + int* copies_; + int* assigns_; +}; + +// Used for probing the number of copies in an argument. +class CopyCounter { + public: + CopyCounter(int* copies, int* assigns) + : copies_(copies), assigns_(assigns) { + } + + CopyCounter(const CopyCounter& other) + : copies_(other.copies_), + assigns_(other.assigns_) { + (*copies_)++; + } + + // Probing for copies from coercion. + CopyCounter(const DerivedCopyCounter& other) + : copies_(other.copies_), + assigns_(other.assigns_) { + (*copies_)++; + } + + const CopyCounter& operator=(const CopyCounter& rhs) { + copies_ = rhs.copies_; + assigns_ = rhs.assigns_; + + if (assigns_) { + (*assigns_)++; + } + + return *this; + } + + int copies() const { + return *copies_; + } + + int assigns() const { + return *assigns_; + } + + private: + int* copies_; + int* assigns_; +}; + +class DeleteCounter { + public: + explicit DeleteCounter(int* deletes) + : deletes_(deletes) { + } + + ~DeleteCounter() { + (*deletes_)++; + } + + void VoidMethod0() {} + + private: + int* deletes_; +}; + +template <typename T> +T PassThru(T scoper) { + return scoper.Pass(); +} + +// Some test functions that we can Bind to. +template <typename T> +T PolymorphicIdentity(T t) { + return t; +} + +template <typename T> +void VoidPolymorphic1(T t) { +} + +int Identity(int n) { + return n; +} + +int ArrayGet(const int array[], int n) { + return array[n]; +} + +int Sum(int a, int b, int c, int d, int e, int f) { + return a + b + c + d + e + f; +} + +const char* CStringIdentity(const char* s) { + return s; +} + +int GetCopies(const CopyCounter& counter) { + return counter.copies(); +} + +int UnwrapNoRefParent(NoRefParent p) { + return p.value; +} + +int UnwrapNoRefParentPtr(NoRefParent* p) { + return p->value; +} + +int UnwrapNoRefParentConstRef(const NoRefParent& p) { + return p.value; +} + +void RefArgSet(int &n) { + n = 2; +} + +void PtrArgSet(int *n) { + *n = 2; +} + +int FunctionWithWeakFirstParam(WeakPtr<NoRef> o, int n) { + return n; +} + +void TakesACallback(const Closure& callback) { + callback.Run(); +} + +class BindTest : public ::testing::Test { + public: + BindTest() { + const_has_ref_ptr_ = &has_ref_; + const_no_ref_ptr_ = &no_ref_; + static_func_mock_ptr = &static_func_mock_; + } + + virtual ~BindTest() { + } + + static void VoidFunc0(void) { + static_func_mock_ptr->VoidMethod0(); + } + + static int IntFunc0(void) { return static_func_mock_ptr->IntMethod0(); } + + protected: + StrictMock<NoRef> no_ref_; + StrictMock<HasRef> has_ref_; + const HasRef* const_has_ref_ptr_; + const NoRef* const_no_ref_ptr_; + StrictMock<NoRef> static_func_mock_; + + // Used by the static functions to perform expectations. + static StrictMock<NoRef>* static_func_mock_ptr; + + private: + DISALLOW_COPY_AND_ASSIGN(BindTest); +}; + +StrictMock<NoRef>* BindTest::static_func_mock_ptr; + +// Sanity check that we can instantiate a callback for each arity. +TEST_F(BindTest, ArityTest) { + Callback<int(void)> c0 = Bind(&Sum, 32, 16, 8, 4, 2, 1); + EXPECT_EQ(63, c0.Run()); + + Callback<int(int)> c1 = Bind(&Sum, 32, 16, 8, 4, 2); + EXPECT_EQ(75, c1.Run(13)); + + Callback<int(int,int)> c2 = Bind(&Sum, 32, 16, 8, 4); + EXPECT_EQ(85, c2.Run(13, 12)); + + Callback<int(int,int,int)> c3 = Bind(&Sum, 32, 16, 8); + EXPECT_EQ(92, c3.Run(13, 12, 11)); + + Callback<int(int,int,int,int)> c4 = Bind(&Sum, 32, 16); + EXPECT_EQ(94, c4.Run(13, 12, 11, 10)); + + Callback<int(int,int,int,int,int)> c5 = Bind(&Sum, 32); + EXPECT_EQ(87, c5.Run(13, 12, 11, 10, 9)); + + Callback<int(int,int,int,int,int,int)> c6 = Bind(&Sum); + EXPECT_EQ(69, c6.Run(13, 12, 11, 10, 9, 14)); +} + +// Test the Currying ability of the Callback system. +TEST_F(BindTest, CurryingTest) { + Callback<int(int,int,int,int,int,int)> c6 = Bind(&Sum); + EXPECT_EQ(69, c6.Run(13, 12, 11, 10, 9, 14)); + + Callback<int(int,int,int,int,int)> c5 = Bind(c6, 32); + EXPECT_EQ(87, c5.Run(13, 12, 11, 10, 9)); + + Callback<int(int,int,int,int)> c4 = Bind(c5, 16); + EXPECT_EQ(94, c4.Run(13, 12, 11, 10)); + + Callback<int(int,int,int)> c3 = Bind(c4, 8); + EXPECT_EQ(92, c3.Run(13, 12, 11)); + + Callback<int(int,int)> c2 = Bind(c3, 4); + EXPECT_EQ(85, c2.Run(13, 12)); + + Callback<int(int)> c1 = Bind(c2, 2); + EXPECT_EQ(75, c1.Run(13)); + + Callback<int(void)> c0 = Bind(c1, 1); + EXPECT_EQ(63, c0.Run()); +} + +// Test that currying the rvalue result of another Bind() works correctly. +// - rvalue should be usable as argument to Bind(). +// - multiple runs of resulting Callback remain valid. +TEST_F(BindTest, CurryingRvalueResultOfBind) { + int n = 0; + Closure cb = base::Bind(&TakesACallback, base::Bind(&PtrArgSet, &n)); + + // If we implement Bind() such that the return value has auto_ptr-like + // semantics, the second call here will fail because ownership of + // the internal BindState<> would have been transfered to a *temporary* + // constructon of a Callback object on the first call. + cb.Run(); + EXPECT_EQ(2, n); + + n = 0; + cb.Run(); + EXPECT_EQ(2, n); +} + +// Function type support. +// - Normal function. +// - Normal function bound with non-refcounted first argument. +// - Method bound to non-const object. +// - Method bound to scoped_refptr. +// - Const method bound to non-const object. +// - Const method bound to const object. +// - Derived classes can be used with pointers to non-virtual base functions. +// - Derived classes can be used with pointers to virtual base functions (and +// preserve virtual dispatch). +TEST_F(BindTest, FunctionTypeSupport) { + EXPECT_CALL(static_func_mock_, VoidMethod0()); + EXPECT_CALL(has_ref_, AddRef()).Times(5); + EXPECT_CALL(has_ref_, Release()).Times(5); + EXPECT_CALL(has_ref_, VoidMethod0()).Times(2); + EXPECT_CALL(has_ref_, VoidConstMethod0()).Times(2); + + Closure normal_cb = Bind(&VoidFunc0); + Callback<NoRef*(void)> normal_non_refcounted_cb = + Bind(&PolymorphicIdentity<NoRef*>, &no_ref_); + normal_cb.Run(); + EXPECT_EQ(&no_ref_, normal_non_refcounted_cb.Run()); + + Closure method_cb = Bind(&HasRef::VoidMethod0, &has_ref_); + Closure method_refptr_cb = Bind(&HasRef::VoidMethod0, + make_scoped_refptr(&has_ref_)); + Closure const_method_nonconst_obj_cb = Bind(&HasRef::VoidConstMethod0, + &has_ref_); + Closure const_method_const_obj_cb = Bind(&HasRef::VoidConstMethod0, + const_has_ref_ptr_); + method_cb.Run(); + method_refptr_cb.Run(); + const_method_nonconst_obj_cb.Run(); + const_method_const_obj_cb.Run(); + + Child child; + child.value = 0; + Closure virtual_set_cb = Bind(&Parent::VirtualSet, &child); + virtual_set_cb.Run(); + EXPECT_EQ(kChildValue, child.value); + + child.value = 0; + Closure non_virtual_set_cb = Bind(&Parent::NonVirtualSet, &child); + non_virtual_set_cb.Run(); + EXPECT_EQ(kParentValue, child.value); +} + +// Return value support. +// - Function with return value. +// - Method with return value. +// - Const method with return value. +TEST_F(BindTest, ReturnValues) { + EXPECT_CALL(static_func_mock_, IntMethod0()).WillOnce(Return(1337)); + EXPECT_CALL(has_ref_, AddRef()).Times(3); + EXPECT_CALL(has_ref_, Release()).Times(3); + EXPECT_CALL(has_ref_, IntMethod0()).WillOnce(Return(31337)); + EXPECT_CALL(has_ref_, IntConstMethod0()) + .WillOnce(Return(41337)) + .WillOnce(Return(51337)); + + Callback<int(void)> normal_cb = Bind(&IntFunc0); + Callback<int(void)> method_cb = Bind(&HasRef::IntMethod0, &has_ref_); + Callback<int(void)> const_method_nonconst_obj_cb = + Bind(&HasRef::IntConstMethod0, &has_ref_); + Callback<int(void)> const_method_const_obj_cb = + Bind(&HasRef::IntConstMethod0, const_has_ref_ptr_); + EXPECT_EQ(1337, normal_cb.Run()); + EXPECT_EQ(31337, method_cb.Run()); + EXPECT_EQ(41337, const_method_nonconst_obj_cb.Run()); + EXPECT_EQ(51337, const_method_const_obj_cb.Run()); +} + +// IgnoreResult adapter test. +// - Function with return value. +// - Method with return value. +// - Const Method with return. +// - Method with return value bound to WeakPtr<>. +// - Const Method with return bound to WeakPtr<>. +TEST_F(BindTest, IgnoreResult) { + EXPECT_CALL(static_func_mock_, IntMethod0()).WillOnce(Return(1337)); + EXPECT_CALL(has_ref_, AddRef()).Times(2); + EXPECT_CALL(has_ref_, Release()).Times(2); + EXPECT_CALL(has_ref_, IntMethod0()).WillOnce(Return(10)); + EXPECT_CALL(has_ref_, IntConstMethod0()).WillOnce(Return(11)); + EXPECT_CALL(no_ref_, IntMethod0()).WillOnce(Return(12)); + EXPECT_CALL(no_ref_, IntConstMethod0()).WillOnce(Return(13)); + + Closure normal_func_cb = Bind(IgnoreResult(&IntFunc0)); + normal_func_cb.Run(); + + Closure non_void_method_cb = + Bind(IgnoreResult(&HasRef::IntMethod0), &has_ref_); + non_void_method_cb.Run(); + + Closure non_void_const_method_cb = + Bind(IgnoreResult(&HasRef::IntConstMethod0), &has_ref_); + non_void_const_method_cb.Run(); + + WeakPtrFactory<NoRef> weak_factory(&no_ref_); + WeakPtrFactory<const NoRef> const_weak_factory(const_no_ref_ptr_); + + Closure non_void_weak_method_cb = + Bind(IgnoreResult(&NoRef::IntMethod0), weak_factory.GetWeakPtr()); + non_void_weak_method_cb.Run(); + + Closure non_void_weak_const_method_cb = + Bind(IgnoreResult(&NoRef::IntConstMethod0), weak_factory.GetWeakPtr()); + non_void_weak_const_method_cb.Run(); + + weak_factory.InvalidateWeakPtrs(); + non_void_weak_const_method_cb.Run(); + non_void_weak_method_cb.Run(); +} + +// Argument binding tests. +// - Argument binding to primitive. +// - Argument binding to primitive pointer. +// - Argument binding to a literal integer. +// - Argument binding to a literal string. +// - Argument binding with template function. +// - Argument binding to an object. +// - Argument binding to pointer to incomplete type. +// - Argument gets type converted. +// - Pointer argument gets converted. +// - Const Reference forces conversion. +TEST_F(BindTest, ArgumentBinding) { + int n = 2; + + Callback<int(void)> bind_primitive_cb = Bind(&Identity, n); + EXPECT_EQ(n, bind_primitive_cb.Run()); + + Callback<int*(void)> bind_primitive_pointer_cb = + Bind(&PolymorphicIdentity<int*>, &n); + EXPECT_EQ(&n, bind_primitive_pointer_cb.Run()); + + Callback<int(void)> bind_int_literal_cb = Bind(&Identity, 3); + EXPECT_EQ(3, bind_int_literal_cb.Run()); + + Callback<const char*(void)> bind_string_literal_cb = + Bind(&CStringIdentity, "hi"); + EXPECT_STREQ("hi", bind_string_literal_cb.Run()); + + Callback<int(void)> bind_template_function_cb = + Bind(&PolymorphicIdentity<int>, 4); + EXPECT_EQ(4, bind_template_function_cb.Run()); + + NoRefParent p; + p.value = 5; + Callback<int(void)> bind_object_cb = Bind(&UnwrapNoRefParent, p); + EXPECT_EQ(5, bind_object_cb.Run()); + + IncompleteType* incomplete_ptr = reinterpret_cast<IncompleteType*>(123); + Callback<IncompleteType*(void)> bind_incomplete_ptr_cb = + Bind(&PolymorphicIdentity<IncompleteType*>, incomplete_ptr); + EXPECT_EQ(incomplete_ptr, bind_incomplete_ptr_cb.Run()); + + NoRefChild c; + c.value = 6; + Callback<int(void)> bind_promotes_cb = Bind(&UnwrapNoRefParent, c); + EXPECT_EQ(6, bind_promotes_cb.Run()); + + c.value = 7; + Callback<int(void)> bind_pointer_promotes_cb = + Bind(&UnwrapNoRefParentPtr, &c); + EXPECT_EQ(7, bind_pointer_promotes_cb.Run()); + + c.value = 8; + Callback<int(void)> bind_const_reference_promotes_cb = + Bind(&UnwrapNoRefParentConstRef, c); + EXPECT_EQ(8, bind_const_reference_promotes_cb.Run()); +} + +// Unbound argument type support tests. +// - Unbound value. +// - Unbound pointer. +// - Unbound reference. +// - Unbound const reference. +// - Unbound unsized array. +// - Unbound sized array. +// - Unbound array-of-arrays. +TEST_F(BindTest, UnboundArgumentTypeSupport) { + Callback<void(int)> unbound_value_cb = Bind(&VoidPolymorphic1<int>); + Callback<void(int*)> unbound_pointer_cb = Bind(&VoidPolymorphic1<int*>); + Callback<void(int&)> unbound_ref_cb = Bind(&VoidPolymorphic1<int&>); + Callback<void(const int&)> unbound_const_ref_cb = + Bind(&VoidPolymorphic1<const int&>); + Callback<void(int[])> unbound_unsized_array_cb = + Bind(&VoidPolymorphic1<int[]>); + Callback<void(int[2])> unbound_sized_array_cb = + Bind(&VoidPolymorphic1<int[2]>); + Callback<void(int[][2])> unbound_array_of_arrays_cb = + Bind(&VoidPolymorphic1<int[][2]>); +} + +// Function with unbound reference parameter. +// - Original parameter is modified by callback. +TEST_F(BindTest, UnboundReferenceSupport) { + int n = 0; + Callback<void(int&)> unbound_ref_cb = Bind(&RefArgSet); + unbound_ref_cb.Run(n); + EXPECT_EQ(2, n); +} + +// Functions that take reference parameters. +// - Forced reference parameter type still stores a copy. +// - Forced const reference parameter type still stores a copy. +TEST_F(BindTest, ReferenceArgumentBinding) { + int n = 1; + int& ref_n = n; + const int& const_ref_n = n; + + Callback<int(void)> ref_copies_cb = Bind(&Identity, ref_n); + EXPECT_EQ(n, ref_copies_cb.Run()); + n++; + EXPECT_EQ(n - 1, ref_copies_cb.Run()); + + Callback<int(void)> const_ref_copies_cb = Bind(&Identity, const_ref_n); + EXPECT_EQ(n, const_ref_copies_cb.Run()); + n++; + EXPECT_EQ(n - 1, const_ref_copies_cb.Run()); +} + +// Check that we can pass in arrays and have them be stored as a pointer. +// - Array of values stores a pointer. +// - Array of const values stores a pointer. +TEST_F(BindTest, ArrayArgumentBinding) { + int array[4] = {1, 1, 1, 1}; + const int (*const_array_ptr)[4] = &array; + + Callback<int(void)> array_cb = Bind(&ArrayGet, array, 1); + EXPECT_EQ(1, array_cb.Run()); + + Callback<int(void)> const_array_cb = Bind(&ArrayGet, *const_array_ptr, 1); + EXPECT_EQ(1, const_array_cb.Run()); + + array[1] = 3; + EXPECT_EQ(3, array_cb.Run()); + EXPECT_EQ(3, const_array_cb.Run()); +} + +// Verify SupportsAddRefAndRelease correctly introspects the class type for +// AddRef() and Release(). +// - Class with AddRef() and Release() +// - Class without AddRef() and Release() +// - Derived Class with AddRef() and Release() +// - Derived Class without AddRef() and Release() +// - Derived Class with AddRef() and Release() and a private destructor. +TEST_F(BindTest, SupportsAddRefAndRelease) { + EXPECT_TRUE(internal::SupportsAddRefAndRelease<HasRef>::value); + EXPECT_FALSE(internal::SupportsAddRefAndRelease<NoRef>::value); + + // StrictMock<T> is a derived class of T. So, we use StrictMock<HasRef> and + // StrictMock<NoRef> to test that SupportsAddRefAndRelease works over + // inheritance. + EXPECT_TRUE(internal::SupportsAddRefAndRelease<StrictMock<HasRef> >::value); + EXPECT_FALSE(internal::SupportsAddRefAndRelease<StrictMock<NoRef> >::value); + + // This matters because the implementation creates a dummy class that + // inherits from the template type. + EXPECT_TRUE(internal::SupportsAddRefAndRelease<HasRefPrivateDtor>::value); +} + +// Unretained() wrapper support. +// - Method bound to Unretained() non-const object. +// - Const method bound to Unretained() non-const object. +// - Const method bound to Unretained() const object. +TEST_F(BindTest, Unretained) { + EXPECT_CALL(no_ref_, VoidMethod0()); + EXPECT_CALL(no_ref_, VoidConstMethod0()).Times(2); + + Callback<void(void)> method_cb = + Bind(&NoRef::VoidMethod0, Unretained(&no_ref_)); + method_cb.Run(); + + Callback<void(void)> const_method_cb = + Bind(&NoRef::VoidConstMethod0, Unretained(&no_ref_)); + const_method_cb.Run(); + + Callback<void(void)> const_method_const_ptr_cb = + Bind(&NoRef::VoidConstMethod0, Unretained(const_no_ref_ptr_)); + const_method_const_ptr_cb.Run(); +} + +// WeakPtr() support. +// - Method bound to WeakPtr<> to non-const object. +// - Const method bound to WeakPtr<> to non-const object. +// - Const method bound to WeakPtr<> to const object. +// - Normal Function with WeakPtr<> as P1 can have return type and is +// not canceled. +TEST_F(BindTest, WeakPtr) { + EXPECT_CALL(no_ref_, VoidMethod0()); + EXPECT_CALL(no_ref_, VoidConstMethod0()).Times(2); + + WeakPtrFactory<NoRef> weak_factory(&no_ref_); + WeakPtrFactory<const NoRef> const_weak_factory(const_no_ref_ptr_); + + Closure method_cb = + Bind(&NoRef::VoidMethod0, weak_factory.GetWeakPtr()); + method_cb.Run(); + + Closure const_method_cb = + Bind(&NoRef::VoidConstMethod0, const_weak_factory.GetWeakPtr()); + const_method_cb.Run(); + + Closure const_method_const_ptr_cb = + Bind(&NoRef::VoidConstMethod0, const_weak_factory.GetWeakPtr()); + const_method_const_ptr_cb.Run(); + + Callback<int(int)> normal_func_cb = + Bind(&FunctionWithWeakFirstParam, weak_factory.GetWeakPtr()); + EXPECT_EQ(1, normal_func_cb.Run(1)); + + weak_factory.InvalidateWeakPtrs(); + const_weak_factory.InvalidateWeakPtrs(); + + method_cb.Run(); + const_method_cb.Run(); + const_method_const_ptr_cb.Run(); + + // Still runs even after the pointers are invalidated. + EXPECT_EQ(2, normal_func_cb.Run(2)); +} + +// ConstRef() wrapper support. +// - Binding w/o ConstRef takes a copy. +// - Binding a ConstRef takes a reference. +// - Binding ConstRef to a function ConstRef does not copy on invoke. +TEST_F(BindTest, ConstRef) { + int n = 1; + + Callback<int(void)> copy_cb = Bind(&Identity, n); + Callback<int(void)> const_ref_cb = Bind(&Identity, ConstRef(n)); + EXPECT_EQ(n, copy_cb.Run()); + EXPECT_EQ(n, const_ref_cb.Run()); + n++; + EXPECT_EQ(n - 1, copy_cb.Run()); + EXPECT_EQ(n, const_ref_cb.Run()); + + int copies = 0; + int assigns = 0; + CopyCounter counter(&copies, &assigns); + Callback<int(void)> all_const_ref_cb = + Bind(&GetCopies, ConstRef(counter)); + EXPECT_EQ(0, all_const_ref_cb.Run()); + EXPECT_EQ(0, copies); + EXPECT_EQ(0, assigns); +} + +// Test Owned() support. +TEST_F(BindTest, Owned) { + int deletes = 0; + DeleteCounter* counter = new DeleteCounter(&deletes); + + // If we don't capture, delete happens on Callback destruction/reset. + // return the same value. + Callback<DeleteCounter*(void)> no_capture_cb = + Bind(&PolymorphicIdentity<DeleteCounter*>, Owned(counter)); + ASSERT_EQ(counter, no_capture_cb.Run()); + ASSERT_EQ(counter, no_capture_cb.Run()); + EXPECT_EQ(0, deletes); + no_capture_cb.Reset(); // This should trigger a delete. + EXPECT_EQ(1, deletes); + + deletes = 0; + counter = new DeleteCounter(&deletes); + base::Closure own_object_cb = + Bind(&DeleteCounter::VoidMethod0, Owned(counter)); + own_object_cb.Run(); + EXPECT_EQ(0, deletes); + own_object_cb.Reset(); + EXPECT_EQ(1, deletes); +} + +// Passed() wrapper support. +// - Passed() can be constructed from a pointer to scoper. +// - Passed() can be constructed from a scoper rvalue. +// - Using Passed() gives Callback Ownership. +// - Ownership is transferred from Callback to callee on the first Run(). +// - Callback supports unbound arguments. +TEST_F(BindTest, ScopedPtr) { + int deletes = 0; + + // Tests the Passed() function's support for pointers. + scoped_ptr<DeleteCounter> ptr(new DeleteCounter(&deletes)); + Callback<scoped_ptr<DeleteCounter>(void)> unused_callback = + Bind(&PassThru<scoped_ptr<DeleteCounter> >, Passed(&ptr)); + EXPECT_FALSE(ptr.get()); + EXPECT_EQ(0, deletes); + + // If we never invoke the Callback, it retains ownership and deletes. + unused_callback.Reset(); + EXPECT_EQ(1, deletes); + + // Tests the Passed() function's support for rvalues. + deletes = 0; + DeleteCounter* counter = new DeleteCounter(&deletes); + Callback<scoped_ptr<DeleteCounter>(void)> callback = + Bind(&PassThru<scoped_ptr<DeleteCounter> >, + Passed(scoped_ptr<DeleteCounter>(counter))); + EXPECT_FALSE(ptr.get()); + EXPECT_EQ(0, deletes); + + // Check that ownership can be transferred back out. + scoped_ptr<DeleteCounter> result = callback.Run(); + ASSERT_EQ(counter, result.get()); + EXPECT_EQ(0, deletes); + + // Resetting does not delete since ownership was transferred. + callback.Reset(); + EXPECT_EQ(0, deletes); + + // Ensure that we actually did get ownership. + result.reset(); + EXPECT_EQ(1, deletes); + + // Test unbound argument forwarding. + Callback<scoped_ptr<DeleteCounter>(scoped_ptr<DeleteCounter>)> cb_unbound = + Bind(&PassThru<scoped_ptr<DeleteCounter> >); + ptr.reset(new DeleteCounter(&deletes)); + cb_unbound.Run(ptr.Pass()); +} + +// Argument Copy-constructor usage for non-reference parameters. +// - Bound arguments are only copied once. +// - Forwarded arguments are only copied once. +// - Forwarded arguments with coercions are only copied twice (once for the +// coercion, and one for the final dispatch). +TEST_F(BindTest, ArgumentCopies) { + int copies = 0; + int assigns = 0; + + CopyCounter counter(&copies, &assigns); + + Callback<void(void)> copy_cb = + Bind(&VoidPolymorphic1<CopyCounter>, counter); + EXPECT_GE(1, copies); + EXPECT_EQ(0, assigns); + + copies = 0; + assigns = 0; + Callback<void(CopyCounter)> forward_cb = + Bind(&VoidPolymorphic1<CopyCounter>); + forward_cb.Run(counter); + EXPECT_GE(1, copies); + EXPECT_EQ(0, assigns); + + copies = 0; + assigns = 0; + DerivedCopyCounter dervied(&copies, &assigns); + Callback<void(CopyCounter)> coerce_cb = + Bind(&VoidPolymorphic1<CopyCounter>); + coerce_cb.Run(dervied); + EXPECT_GE(2, copies); + EXPECT_EQ(0, assigns); +} + +// Callback construction and assignment tests. +// - Construction from an InvokerStorageHolder should not cause ref/deref. +// - Assignment from other callback should only cause one ref +// +// TODO(ajwong): Is there actually a way to test this? + +#if defined(OS_WIN) +int __fastcall FastCallFunc(int n) { + return n; +} + +int __stdcall StdCallFunc(int n) { + return n; +} + +// Windows specific calling convention support. +// - Can bind a __fastcall function. +// - Can bind a __stdcall function. +TEST_F(BindTest, WindowsCallingConventions) { + Callback<int(void)> fastcall_cb = Bind(&FastCallFunc, 1); + EXPECT_EQ(1, fastcall_cb.Run()); + + Callback<int(void)> stdcall_cb = Bind(&StdCallFunc, 2); + EXPECT_EQ(2, stdcall_cb.Run()); +} +#endif + +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST + +// Test null callbacks cause a DCHECK. +TEST(BindDeathTest, NullCallback) { + base::Callback<void(int)> null_cb; + ASSERT_TRUE(null_cb.is_null()); + EXPECT_DEATH(base::Bind(null_cb, 42), ""); +} + +#endif // (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && + // GTEST_HAS_DEATH_TEST + +} // namespace +} // namespace base
diff --git a/src/base/bind_unittest.nc b/src/base/bind_unittest.nc new file mode 100644 index 0000000..069092c --- /dev/null +++ b/src/base/bind_unittest.nc
@@ -0,0 +1,203 @@ +// 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/callback.h" +#include "base/bind.h" +#include "base/memory/ref_counted.h" + +namespace base { + +// Do not put everything inside an anonymous namespace. If you do, many of the +// helper function declarations will generate unused definition warnings unless +// unused definition warnings. + +static const int kParentValue = 1; +static const int kChildValue = 2; + +class NoRef { + public: + void VoidMethod0() {} + void VoidConstMethod0() const {} + int IntMethod0() { return 1; } +}; + +class HasRef : public NoRef, public base::RefCounted<HasRef> { +}; + +class Parent { + public: + void AddRef(void) const {} + void Release(void) const {} + virtual void VirtualSet() { value = kParentValue; } + void NonVirtualSet() { value = kParentValue; } + int value; +}; + +class Child : public Parent { + public: + virtual void VirtualSet() { value = kChildValue; } + void NonVirtualSet() { value = kChildValue; } +}; + +class NoRefParent { + public: + virtual void VirtualSet() { value = kParentValue; } + void NonVirtualSet() { value = kParentValue; } + int value; +}; + +class NoRefChild : public NoRefParent { + virtual void VirtualSet() { value = kChildValue; } + void NonVirtualSet() { value = kChildValue; } +}; + +template <typename T> +T PolymorphicIdentity(T t) { + return t; +} + +int UnwrapParentRef(Parent& p) { + return p.value; +} + +template <typename T> +void VoidPolymorphic1(T t) { +} + +#if defined(NCTEST_METHOD_ON_CONST_OBJECT) // [r"invalid conversion from 'const base::NoRef\*' to 'base::NoRef\*'"] + +// Method bound to const-object. +// +// Only const methods should be allowed to work with const objects. +void WontCompile() { + HasRef has_ref; + const HasRef* const_has_ref_ptr_ = &has_ref; + Callback<void(void)> method_to_const_cb = + Bind(&HasRef::VoidMethod0, const_has_ref_ptr_); + method_to_const_cb.Run(); +} + +#elif defined(NCTEST_METHOD_BIND_NEEDS_REFCOUNTED_OBJECT) // [r"has no member named 'AddRef'"] + +// Method bound to non-refcounted object. +// +// We require refcounts unless you have Unretained(). +void WontCompile() { + NoRef no_ref; + Callback<void(void)> no_ref_cb = + Bind(&NoRef::VoidMethod0, &no_ref); + no_ref_cb.Run(); +} + +#elif defined(NCTEST_CONST_METHOD_NEEDS_REFCOUNTED_OBJECT) // [r"has no member named 'AddRef'"] + +// Const Method bound to non-refcounted object. +// +// We require refcounts unless you have Unretained(). +void WontCompile() { + NoRef no_ref; + Callback<void(void)> no_ref_const_cb = + Bind(&NoRef::VoidConstMethod0, &no_ref); + no_ref_const_cb.Run(); +} + +#elif defined(NCTEST_CONST_POINTER) // [r"invalid conversion from 'const base::NoRef\*' to 'base::NoRef\*'"] + +// Const argument used with non-const pointer parameter of same type. +// +// This is just a const-correctness check. +void WontCompile() { + const NoRef* const_no_ref_ptr; + Callback<NoRef*(void)> pointer_same_cb = + Bind(&PolymorphicIdentity<NoRef*>, const_no_ref_ptr); + pointer_same_cb.Run(); +} + +#elif defined(NCTEST_CONST_POINTER_SUBTYPE) // [r"'const base::NoRefParent\*' to 'base::NoRefParent\*'"] + +// Const argument used with non-const pointer parameter of super type. +// +// This is just a const-correctness check. +void WontCompile() { + const NoRefChild* const_child_ptr; + Callback<NoRefParent*(void)> pointer_super_cb = + Bind(&PolymorphicIdentity<NoRefParent*>, const_child_ptr); + pointer_super_cb.Run(); +} + +#elif defined(DISABLED_NCTEST_DISALLOW_NON_CONST_REF_PARAM) // [r"badstring"] +// I think there's a type safety promotion issue here where we can pass a const +// ref to a non const-ref function, or vice versa accidentally. Or we make a +// copy accidentally. Check. + +// Functions with reference parameters, unsupported. +// +// First, non-const reference parameters are disallowed by the Google +// style guide. Second, since we are doing argument forwarding it becomes +// very tricky to avoid copies, maintain const correctness, and not +// accidentally have the function be modifying a temporary, or a copy. +void WontCompile() { + Parent p; + Callback<int(Parent&)> ref_arg_cb = Bind(&UnwrapParentRef); + ref_arg_cb.Run(p); +} + +#elif defined(NCTEST_DISALLOW_BIND_TO_NON_CONST_REF_PARAM) // [r"creating array with negative size"] + +// Binding functions with reference parameters, unsupported. +// +// See comment in NCTEST_DISALLOW_NON_CONST_REF_PARAM +void WontCompile() { + Parent p; + Callback<int(void)> ref_cb = Bind(&UnwrapParentRef, p); + ref_cb.Run(); +} + +#elif defined(NCTEST_NO_IMPLICIT_ARRAY_PTR_CONVERSION) // [r"creating array with negative size"] + +// A method should not be bindable with an array of objects. +// +// This is likely not wanted behavior. We specifically check for it though +// because it is possible, depending on how you implement prebinding, to +// implicitly convert an array type to a pointer type. +void WontCompile() { + HasRef p[10]; + Callback<void(void)> method_bound_to_array_cb = + Bind(&HasRef::VoidMethod0, p); + method_bound_to_array_cb.Run(); +} + +#elif defined(NCTEST_NO_RAW_PTR_FOR_REFCOUNTED_TYPES) // [r"creating array with negative size"] + +// Refcounted types should not be bound as a raw pointer. +void WontCompile() { + HasRef for_raw_ptr; + int a; + Callback<void(void)> ref_count_as_raw_ptr_a = + Bind(&VoidPolymorphic1<int*>, &a); + Callback<void(void)> ref_count_as_raw_ptr = + Bind(&VoidPolymorphic1<HasRef*>, &for_raw_ptr); +} + +#elif defined(NCTEST_WEAKPTR_BIND_MUST_RETURN_VOID) // [r"creating array with negative size"] + +// WeakPtrs cannot be bound to methods with return types. +void WontCompile() { + NoRef no_ref; + WeakPtrFactory<NoRef> weak_factory(&no_ref); + Callback<int(void)> weak_ptr_with_non_void_return_type = + Bind(&NoRef::IntMethod0, weak_factory.GetWeakPtr()); + weak_ptr_with_non_void_return_type.Run(); +} + +#elif defined(NCTEST_DISALLOW_ASSIGN_DIFFERINT_TYPES) // [r"invalid conversion from"] + +// Bind result cannot be assigned to Callbacks with a mismatching type. +void WontCompile() { + Closure callback_mismatches_bind_type = Bind(&VoidPolymorphic1<int>); +} + +#endif + +} // namespace base
diff --git a/src/base/bits.h b/src/base/bits.h new file mode 100644 index 0000000..b2209e8 --- /dev/null +++ b/src/base/bits.h
@@ -0,0 +1,47 @@ +// Copyright (c) 2009 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 some bit utilities. + +#ifndef BASE_BITS_H_ +#define BASE_BITS_H_ + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace base { +namespace bits { + +// Returns the integer i such as 2^i <= n < 2^(i+1) +inline int Log2Floor(uint32 n) { + if (n == 0) + return -1; + int log = 0; + uint32 value = n; + for (int i = 4; i >= 0; --i) { + int shift = (1 << i); + uint32 x = value >> shift; + if (x != 0) { + value = x; + log += shift; + } + } + DCHECK_EQ(value, 1u); + return log; +} + +// Returns the integer i such as 2^(i-1) < n <= 2^i +inline int Log2Ceiling(uint32 n) { + if (n == 0) { + return -1; + } else { + // Log2Floor returns -1 for 0, so the following works correctly for n=1. + return 1 + Log2Floor(n - 1); + } +} + +} // namespace bits +} // namespace base + +#endif // BASE_BITS_H_
diff --git a/src/base/bits_unittest.cc b/src/base/bits_unittest.cc new file mode 100644 index 0000000..e913d6a --- /dev/null +++ b/src/base/bits_unittest.cc
@@ -0,0 +1,48 @@ +// Copyright (c) 2009 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 contains the unit tests for the bit utilities. + +#include "base/bits.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace bits { + +TEST(BitsTest, Log2Floor) { + EXPECT_EQ(-1, Log2Floor(0)); + EXPECT_EQ(0, Log2Floor(1)); + EXPECT_EQ(1, Log2Floor(2)); + EXPECT_EQ(1, Log2Floor(3)); + EXPECT_EQ(2, Log2Floor(4)); + for (int i = 3; i < 31; ++i) { + unsigned int value = 1U << i; + EXPECT_EQ(i, Log2Floor(value)); + EXPECT_EQ(i, Log2Floor(value + 1)); + EXPECT_EQ(i, Log2Floor(value + 2)); + EXPECT_EQ(i - 1, Log2Floor(value - 1)); + EXPECT_EQ(i - 1, Log2Floor(value - 2)); + } + EXPECT_EQ(31, Log2Floor(0xffffffffU)); +} + +TEST(BitsTest, Log2Ceiling) { + EXPECT_EQ(-1, Log2Ceiling(0)); + EXPECT_EQ(0, Log2Ceiling(1)); + EXPECT_EQ(1, Log2Ceiling(2)); + EXPECT_EQ(2, Log2Ceiling(3)); + EXPECT_EQ(2, Log2Ceiling(4)); + for (int i = 3; i < 31; ++i) { + unsigned int value = 1U << i; + EXPECT_EQ(i, Log2Ceiling(value)); + EXPECT_EQ(i + 1, Log2Ceiling(value + 1)); + EXPECT_EQ(i + 1, Log2Ceiling(value + 2)); + EXPECT_EQ(i, Log2Ceiling(value - 1)); + EXPECT_EQ(i, Log2Ceiling(value - 2)); + } + EXPECT_EQ(32, Log2Ceiling(0xffffffffU)); +} + +} // namespace bits +} // namespace base
diff --git a/src/base/build_time.cc b/src/base/build_time.cc new file mode 100644 index 0000000..bb2274b --- /dev/null +++ b/src/base/build_time.cc
@@ -0,0 +1,25 @@ +// 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/build_time.h" + +#include "base/logging.h" +#include "base/time.h" + +namespace base { + +Time GetBuildTime() { + Time integral_build_time; + // The format of __DATE__ and __TIME__ is specified by the ANSI C Standard, + // section 6.8.8. + // + // __DATE__ is exactly "Mmm DD YYYY". + // __TIME__ is exactly "hh:mm:ss". + const char kDateTime[] = __DATE__ " " __TIME__ " PST"; + bool result = Time::FromString(kDateTime, &integral_build_time); + DCHECK(result); + return integral_build_time; +} + +} // namespace base
diff --git a/src/base/build_time.h b/src/base/build_time.h new file mode 100644 index 0000000..e7bbb39 --- /dev/null +++ b/src/base/build_time.h
@@ -0,0 +1,25 @@ +// 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. + +#ifndef BASE_BUILD_TIME_ +#define BASE_BUILD_TIME_ + +#include "base/base_export.h" +#include "base/time.h" + +namespace base { + +// GetBuildTime returns the time at which the current binary was built. +// +// This uses the __DATE__ and __TIME__ macros, which don't trigger a rebuild +// when they change. However, official builds will always be rebuilt from +// scratch. +// +// Also, since __TIME__ doesn't include a timezone, this value should only be +// considered accurate to a day. +Time BASE_EXPORT GetBuildTime(); + +} // namespace base + +#endif // BASE_BUILD_TIME_
diff --git a/src/base/build_time_unittest.cc b/src/base/build_time_unittest.cc new file mode 100644 index 0000000..399a53f --- /dev/null +++ b/src/base/build_time_unittest.cc
@@ -0,0 +1,30 @@ +// 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/build_time.h" + +#include "testing/gtest/include/gtest/gtest.h" + +TEST(BuildTime, DateLooksValid) { + char build_date[] = __DATE__; + + EXPECT_EQ(11u, strlen(build_date)); + EXPECT_EQ(' ', build_date[3]); + EXPECT_EQ(' ', build_date[6]); +} + +TEST(BuildTime, TimeLooksValid) { + char build_time[] = __TIME__; + + EXPECT_EQ(8u, strlen(build_time)); + EXPECT_EQ(':', build_time[2]); + EXPECT_EQ(':', build_time[5]); +} + +TEST(BuildTime, DoesntCrash) { + // Since __DATE__ isn't updated unless one does a clobber build, we can't + // really test the value returned by it, except to check that it doesn't + // crash. + base::GetBuildTime(); +}
diff --git a/src/base/callback.h b/src/base/callback.h new file mode 100644 index 0000000..18c1c6c --- /dev/null +++ b/src/base/callback.h
@@ -0,0 +1,893 @@ +// This file was GENERATED by command: +// pump.py callback.h.pump +// DO NOT EDIT BY HAND!!! + + +// 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_CALLBACK_H_ +#define BASE_CALLBACK_H_ + +#include "base/callback_forward.h" +#include "base/callback_internal.h" +#include "base/template_util.h" + +// NOTE: Header files that do not require the full definition of Callback or +// Closure should #include "base/callback_forward.h" instead of this file. + +// ----------------------------------------------------------------------------- +// Introduction +// ----------------------------------------------------------------------------- +// +// The templated Callback class is a generalized function object. Together +// with the Bind() function in bind.h, they provide a type-safe method for +// performing partial application of functions. +// +// Partial application (or "currying") is the process of binding a subset of +// a function's arguments to produce another function that takes fewer +// arguments. This can be used to pass around a unit of delayed execution, +// much like lexical closures are used in other languages. For example, it +// is used in Chromium code to schedule tasks on different MessageLoops. +// +// A callback with no unbound input parameters (base::Callback<void(void)>) +// is called a base::Closure. Note that this is NOT the same as what other +// languages refer to as a closure -- it does not retain a reference to its +// enclosing environment. +// +// MEMORY MANAGEMENT AND PASSING +// +// The Callback objects themselves should be passed by const-reference, and +// stored by copy. They internally store their state via a refcounted class +// and thus do not need to be deleted. +// +// The reason to pass via a const-reference is to avoid unnecessary +// AddRef/Release pairs to the internal state. +// +// +// ----------------------------------------------------------------------------- +// Quick reference for basic stuff +// ----------------------------------------------------------------------------- +// +// BINDING A BARE FUNCTION +// +// int Return5() { return 5; } +// base::Callback<int(void)> func_cb = base::Bind(&Return5); +// LOG(INFO) << func_cb.Run(); // Prints 5. +// +// BINDING A CLASS METHOD +// +// The first argument to bind is the member function to call, the second is +// the object on which to call it. +// +// class Ref : public base::RefCountedThreadSafe<Ref> { +// public: +// int Foo() { return 3; } +// void PrintBye() { LOG(INFO) << "bye."; } +// }; +// scoped_refptr<Ref> ref = new Ref(); +// base::Callback<void(void)> ref_cb = base::Bind(&Ref::Foo, ref); +// LOG(INFO) << ref_cb.Run(); // Prints out 3. +// +// By default the object must support RefCounted or you will get a compiler +// error. If you're passing between threads, be sure it's +// RefCountedThreadSafe! See "Advanced binding of member functions" below if +// you don't want to use reference counting. +// +// RUNNING A CALLBACK +// +// Callbacks can be run with their "Run" method, which has the same +// signature as the template argument to the callback. +// +// void DoSomething(const base::Callback<void(int, std::string)>& callback) { +// callback.Run(5, "hello"); +// } +// +// Callbacks can be run more than once (they don't get deleted or marked when +// run). However, this precludes using base::Passed (see below). +// +// void DoSomething(const base::Callback<double(double)>& callback) { +// double myresult = callback.Run(3.14159); +// myresult += callback.Run(2.71828); +// } +// +// PASSING UNBOUND INPUT PARAMETERS +// +// Unbound parameters are specified at the time a callback is Run(). They are +// specified in the Callback template type: +// +// void MyFunc(int i, const std::string& str) {} +// base::Callback<void(int, const std::string&)> cb = base::Bind(&MyFunc); +// cb.Run(23, "hello, world"); +// +// PASSING BOUND INPUT PARAMETERS +// +// Bound parameters are specified when you create thee callback as arguments +// to Bind(). They will be passed to the function and the Run()ner of the +// callback doesn't see those values or even know that the function it's +// calling. +// +// void MyFunc(int i, const std::string& str) {} +// base::Callback<void(void)> cb = base::Bind(&MyFunc, 23, "hello world"); +// cb.Run(); +// +// A callback with no unbound input parameters (base::Callback<void(void)>) +// is called a base::Closure. So we could have also written: +// +// base::Closure cb = base::Bind(&MyFunc, 23, "hello world"); +// +// When calling member functions, bound parameters just go after the object +// pointer. +// +// base::Closure cb = base::Bind(&MyClass::MyFunc, this, 23, "hello world"); +// +// PARTIAL BINDING OF PARAMETERS +// +// You can specify some parameters when you create the callback, and specify +// the rest when you execute the callback. +// +// void MyFunc(int i, const std::string& str) {} +// base::Callback<void(const std::string&)> cb = base::Bind(&MyFunc, 23); +// cb.Run("hello world"); +// +// When calling a function bound parameters are first, followed by unbound +// parameters. +// +// +// ----------------------------------------------------------------------------- +// Quick reference for advanced binding +// ----------------------------------------------------------------------------- +// +// BINDING A CLASS METHOD WITH WEAK POINTERS +// +// base::Bind(&MyClass::Foo, GetWeakPtr()); +// +// The callback will not be issued if the object is destroyed at the time +// it's issued. DANGER: weak pointers are not threadsafe, so don't use this +// when passing between threads! +// +// BINDING A CLASS METHOD WITH MANUAL LIFETIME MANAGEMENT +// +// base::Bind(&MyClass::Foo, base::Unretained(this)); +// +// This disables all lifetime management on the object. You're responsible +// for making sure the object is alive at the time of the call. You break it, +// you own it! +// +// BINDING A CLASS METHOD AND HAVING THE CALLBACK OWN THE CLASS +// +// MyClass* myclass = new MyClass; +// base::Bind(&MyClass::Foo, base::Owned(myclass)); +// +// The object will be deleted when the callback is destroyed, even if it's +// not run (like if you post a task during shutdown). Potentially useful for +// "fire and forget" cases. +// +// IGNORING RETURN VALUES +// +// Sometimes you want to call a function that returns a value in a callback +// that doesn't expect a return value. +// +// int DoSomething(int arg) { cout << arg << endl; } +// base::Callback<void<int>) cb = +// base::Bind(base::IgnoreResult(&DoSomething)); +// +// +// ----------------------------------------------------------------------------- +// Quick reference for binding parameters to Bind() +// ----------------------------------------------------------------------------- +// +// Bound parameters are specified as arguments to Bind() and are passed to the +// function. A callback with no parameters or no unbound parameters is called a +// Closure (base::Callback<void(void)> and base::Closure are the same thing). +// +// PASSING PARAMETERS OWNED BY THE CALLBACK +// +// void Foo(int* arg) { cout << *arg << endl; } +// int* pn = new int(1); +// base::Closure foo_callback = base::Bind(&foo, base::Owned(pn)); +// +// The parameter will be deleted when the callback is destroyed, even if it's +// not run (like if you post a task during shutdown). +// +// PASSING PARAMETERS AS A scoped_ptr +// +// void TakesOwnership(scoped_ptr<Foo> arg) {} +// scoped_ptr<Foo> f(new Foo); +// // f becomes null during the following call. +// base::Closure cb = base::Bind(&TakesOwnership, base::Passed(&f)); +// +// Ownership of the parameter will be with the callback until the it is run, +// when ownership is passed to the callback function. This means the callback +// can only be run once. If the callback is never run, it will delete the +// object when it's destroyed. +// +// PASSING PARAMETERS AS A scoped_refptr +// +// void TakesOneRef(scoped_refptr<Foo> arg) {} +// scoped_refptr<Foo> f(new Foo) +// base::Closure cb = base::Bind(&TakesOneRef, f); +// +// This should "just work." The closure will take a reference as long as it +// is alive, and another reference will be taken for the called function. +// +// PASSING PARAMETERS BY REFERENCE +// +// void foo(int arg) { cout << arg << endl } +// int n = 1; +// base::Closure has_ref = base::Bind(&foo, base::ConstRef(n)); +// n = 2; +// has_ref.Run(); // Prints "2" +// +// Normally parameters are copied in the closure. DANGER: ConstRef stores a +// const reference instead, referencing the original parameter. This means +// that you must ensure the object outlives the callback! +// +// +// ----------------------------------------------------------------------------- +// Implementation notes +// ----------------------------------------------------------------------------- +// +// WHERE IS THIS DESIGN FROM: +// +// The design Callback and Bind is heavily influenced by C++'s +// tr1::function/tr1::bind, and by the "Google Callback" system used inside +// Google. +// +// +// HOW THE IMPLEMENTATION WORKS: +// +// There are three main components to the system: +// 1) The Callback classes. +// 2) The Bind() functions. +// 3) The arguments wrappers (e.g., Unretained() and ConstRef()). +// +// The Callback classes represent a generic function pointer. Internally, +// it stores a refcounted piece of state that represents the target function +// and all its bound parameters. Each Callback specialization has a templated +// constructor that takes an BindState<>*. In the context of the constructor, +// the static type of this BindState<> pointer uniquely identifies the +// function it is representing, all its bound parameters, and a Run() method +// that is capable of invoking the target. +// +// Callback's constructor takes the BindState<>* that has the full static type +// and erases the target function type as well as the types of the bound +// parameters. It does this by storing a pointer to the specific Run() +// function, and upcasting the state of BindState<>* to a +// BindStateBase*. This is safe as long as this BindStateBase pointer +// is only used with the stored Run() pointer. +// +// To BindState<> objects are created inside the Bind() functions. +// These functions, along with a set of internal templates, are responsible for +// +// - Unwrapping the function signature into return type, and parameters +// - Determining the number of parameters that are bound +// - Creating the BindState storing the bound parameters +// - Performing compile-time asserts to avoid error-prone behavior +// - Returning an Callback<> with an arity matching the number of unbound +// parameters and that knows the correct refcounting semantics for the +// target object if we are binding a method. +// +// The Bind functions do the above using type-inference, and template +// specializations. +// +// By default Bind() will store copies of all bound parameters, and attempt +// to refcount a target object if the function being bound is a class method. +// These copies are created even if the function takes parameters as const +// references. (Binding to non-const references is forbidden, see bind.h.) +// +// To change this behavior, we introduce a set of argument wrappers +// (e.g., Unretained(), and ConstRef()). These are simple container templates +// that are passed by value, and wrap a pointer to argument. See the +// file-level comment in base/bind_helpers.h for more info. +// +// These types are passed to the Unwrap() functions, and the MaybeRefcount() +// functions respectively to modify the behavior of Bind(). The Unwrap() +// and MaybeRefcount() functions change behavior by doing partial +// specialization based on whether or not a parameter is a wrapper type. +// +// ConstRef() is similar to tr1::cref. Unretained() is specific to Chromium. +// +// +// WHY NOT TR1 FUNCTION/BIND? +// +// Direct use of tr1::function and tr1::bind was considered, but ultimately +// rejected because of the number of copy constructors invocations involved +// in the binding of arguments during construction, and the forwarding of +// arguments during invocation. These copies will no longer be an issue in +// C++0x because C++0x will support rvalue reference allowing for the compiler +// to avoid these copies. However, waiting for C++0x is not an option. +// +// Measured with valgrind on gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5), the +// tr1::bind call itself will invoke a non-trivial copy constructor three times +// for each bound parameter. Also, each when passing a tr1::function, each +// bound argument will be copied again. +// +// In addition to the copies taken at binding and invocation, copying a +// tr1::function causes a copy to be made of all the bound parameters and +// state. +// +// Furthermore, in Chromium, it is desirable for the Callback to take a +// reference on a target object when representing a class method call. This +// is not supported by tr1. +// +// Lastly, tr1::function and tr1::bind has a more general and flexible API. +// This includes things like argument reordering by use of +// tr1::bind::placeholder, support for non-const reference parameters, and some +// limited amount of subtyping of the tr1::function object (e.g., +// tr1::function<int(int)> is convertible to tr1::function<void(int)>). +// +// These are not features that are required in Chromium. Some of them, such as +// allowing for reference parameters, and subtyping of functions, may actually +// become a source of errors. Removing support for these features actually +// allows for a simpler implementation, and a terser Currying API. +// +// +// WHY NOT GOOGLE CALLBACKS? +// +// The Google callback system also does not support refcounting. Furthermore, +// its implementation has a number of strange edge cases with respect to type +// conversion of its arguments. In particular, the argument's constness must +// at times match exactly the function signature, or the type-inference might +// break. Given the above, writing a custom solution was easier. +// +// +// MISSING FUNCTIONALITY +// - Invoking the return of Bind. Bind(&foo).Run() does not work; +// - Binding arrays to functions that take a non-const pointer. +// Example: +// void Foo(const char* ptr); +// void Bar(char* ptr); +// Bind(&Foo, "test"); +// Bind(&Bar, "test"); // This fails because ptr is not const. + +namespace base { + +// First, we forward declare the Callback class template. This informs the +// compiler that the template only has 1 type parameter which is the function +// signature that the Callback is representing. +// +// After this, create template specializations for 0-7 parameters. Note that +// even though the template typelist grows, the specialization still +// only has one type: the function signature. +// +// If you are thinking of forward declaring Callback in your own header file, +// please include "base/callback_forward.h" instead. +template <typename Sig> +class Callback; + +namespace internal { +template <typename Runnable, typename RunType, typename BoundArgsType> +struct BindState; +} // namespace internal + +template <typename R> +class Callback<R(void)> : public internal::CallbackBase { + public: + typedef R(RunType)(); + + Callback() : CallbackBase(NULL) { } + + // Note that this constructor CANNOT be explicit, and that Bind() CANNOT + // return the exact Callback<> type. See base/bind.h for details. + template <typename Runnable, typename BindRunType, typename BoundArgsType> + Callback(internal::BindState<Runnable, BindRunType, + BoundArgsType>* bind_state) + : CallbackBase(bind_state) { + + // Force the assignment to a local variable of PolymorphicInvoke + // so the compiler will typecheck that the passed in Run() method has + // the correct type. + PolymorphicInvoke invoke_func = + &internal::BindState<Runnable, BindRunType, BoundArgsType> + ::InvokerType::Run; +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + polymorphic_invoke_ = reinterpret_cast<InvokeFuncStorage>(invoke_func); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run() const { +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + PolymorphicInvoke f = + reinterpret_cast<PolymorphicInvoke>(polymorphic_invoke_); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + + return f(bind_state_.get()); + } + + private: + typedef R(*PolymorphicInvoke)( + internal::BindStateBase*); + +}; + +template <typename R, typename A1> +class Callback<R(A1)> : public internal::CallbackBase { + public: + typedef R(RunType)(A1); + + Callback() : CallbackBase(NULL) { } + + // Note that this constructor CANNOT be explicit, and that Bind() CANNOT + // return the exact Callback<> type. See base/bind.h for details. + template <typename Runnable, typename BindRunType, typename BoundArgsType> + Callback(internal::BindState<Runnable, BindRunType, + BoundArgsType>* bind_state) + : CallbackBase(bind_state) { + + // Force the assignment to a local variable of PolymorphicInvoke + // so the compiler will typecheck that the passed in Run() method has + // the correct type. + PolymorphicInvoke invoke_func = + &internal::BindState<Runnable, BindRunType, BoundArgsType> + ::InvokerType::Run; +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + polymorphic_invoke_ = reinterpret_cast<InvokeFuncStorage>(invoke_func); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run(typename internal::CallbackParamTraits<A1>::ForwardType a1) const { +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + PolymorphicInvoke f = + reinterpret_cast<PolymorphicInvoke>(polymorphic_invoke_); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + + return f(bind_state_.get(), internal::CallbackForward(a1)); + } + + private: + typedef R(*PolymorphicInvoke)( + internal::BindStateBase*, + typename internal::CallbackParamTraits<A1>::ForwardType); + +}; + +template <typename R, typename A1, typename A2> +class Callback<R(A1, A2)> : public internal::CallbackBase { + public: + typedef R(RunType)(A1, A2); + + Callback() : CallbackBase(NULL) { } + + // Note that this constructor CANNOT be explicit, and that Bind() CANNOT + // return the exact Callback<> type. See base/bind.h for details. + template <typename Runnable, typename BindRunType, typename BoundArgsType> + Callback(internal::BindState<Runnable, BindRunType, + BoundArgsType>* bind_state) + : CallbackBase(bind_state) { + + // Force the assignment to a local variable of PolymorphicInvoke + // so the compiler will typecheck that the passed in Run() method has + // the correct type. + PolymorphicInvoke invoke_func = + &internal::BindState<Runnable, BindRunType, BoundArgsType> + ::InvokerType::Run; +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + polymorphic_invoke_ = reinterpret_cast<InvokeFuncStorage>(invoke_func); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run(typename internal::CallbackParamTraits<A1>::ForwardType a1, + typename internal::CallbackParamTraits<A2>::ForwardType a2) const { +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + PolymorphicInvoke f = + reinterpret_cast<PolymorphicInvoke>(polymorphic_invoke_); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + + return f(bind_state_.get(), internal::CallbackForward(a1), + internal::CallbackForward(a2)); + } + + private: + typedef R(*PolymorphicInvoke)( + internal::BindStateBase*, + typename internal::CallbackParamTraits<A1>::ForwardType, + typename internal::CallbackParamTraits<A2>::ForwardType); + +}; + +template <typename R, typename A1, typename A2, typename A3> +class Callback<R(A1, A2, A3)> : public internal::CallbackBase { + public: + typedef R(RunType)(A1, A2, A3); + + Callback() : CallbackBase(NULL) { } + + // Note that this constructor CANNOT be explicit, and that Bind() CANNOT + // return the exact Callback<> type. See base/bind.h for details. + template <typename Runnable, typename BindRunType, typename BoundArgsType> + Callback(internal::BindState<Runnable, BindRunType, + BoundArgsType>* bind_state) + : CallbackBase(bind_state) { + + // Force the assignment to a local variable of PolymorphicInvoke + // so the compiler will typecheck that the passed in Run() method has + // the correct type. + PolymorphicInvoke invoke_func = + &internal::BindState<Runnable, BindRunType, BoundArgsType> + ::InvokerType::Run; +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + polymorphic_invoke_ = reinterpret_cast<InvokeFuncStorage>(invoke_func); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run(typename internal::CallbackParamTraits<A1>::ForwardType a1, + typename internal::CallbackParamTraits<A2>::ForwardType a2, + typename internal::CallbackParamTraits<A3>::ForwardType a3) const { +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + PolymorphicInvoke f = + reinterpret_cast<PolymorphicInvoke>(polymorphic_invoke_); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + + return f(bind_state_.get(), internal::CallbackForward(a1), + internal::CallbackForward(a2), + internal::CallbackForward(a3)); + } + + private: + typedef R(*PolymorphicInvoke)( + internal::BindStateBase*, + typename internal::CallbackParamTraits<A1>::ForwardType, + typename internal::CallbackParamTraits<A2>::ForwardType, + typename internal::CallbackParamTraits<A3>::ForwardType); + +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4> +class Callback<R(A1, A2, A3, A4)> : public internal::CallbackBase { + public: + typedef R(RunType)(A1, A2, A3, A4); + + Callback() : CallbackBase(NULL) { } + + // Note that this constructor CANNOT be explicit, and that Bind() CANNOT + // return the exact Callback<> type. See base/bind.h for details. + template <typename Runnable, typename BindRunType, typename BoundArgsType> + Callback(internal::BindState<Runnable, BindRunType, + BoundArgsType>* bind_state) + : CallbackBase(bind_state) { + + // Force the assignment to a local variable of PolymorphicInvoke + // so the compiler will typecheck that the passed in Run() method has + // the correct type. + PolymorphicInvoke invoke_func = + &internal::BindState<Runnable, BindRunType, BoundArgsType> + ::InvokerType::Run; +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + polymorphic_invoke_ = reinterpret_cast<InvokeFuncStorage>(invoke_func); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run(typename internal::CallbackParamTraits<A1>::ForwardType a1, + typename internal::CallbackParamTraits<A2>::ForwardType a2, + typename internal::CallbackParamTraits<A3>::ForwardType a3, + typename internal::CallbackParamTraits<A4>::ForwardType a4) const { +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + PolymorphicInvoke f = + reinterpret_cast<PolymorphicInvoke>(polymorphic_invoke_); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + + return f(bind_state_.get(), internal::CallbackForward(a1), + internal::CallbackForward(a2), + internal::CallbackForward(a3), + internal::CallbackForward(a4)); + } + + private: + typedef R(*PolymorphicInvoke)( + internal::BindStateBase*, + typename internal::CallbackParamTraits<A1>::ForwardType, + typename internal::CallbackParamTraits<A2>::ForwardType, + typename internal::CallbackParamTraits<A3>::ForwardType, + typename internal::CallbackParamTraits<A4>::ForwardType); + +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5> +class Callback<R(A1, A2, A3, A4, A5)> : public internal::CallbackBase { + public: + typedef R(RunType)(A1, A2, A3, A4, A5); + + Callback() : CallbackBase(NULL) { } + + // Note that this constructor CANNOT be explicit, and that Bind() CANNOT + // return the exact Callback<> type. See base/bind.h for details. + template <typename Runnable, typename BindRunType, typename BoundArgsType> + Callback(internal::BindState<Runnable, BindRunType, + BoundArgsType>* bind_state) + : CallbackBase(bind_state) { + + // Force the assignment to a local variable of PolymorphicInvoke + // so the compiler will typecheck that the passed in Run() method has + // the correct type. + PolymorphicInvoke invoke_func = + &internal::BindState<Runnable, BindRunType, BoundArgsType> + ::InvokerType::Run; +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + polymorphic_invoke_ = reinterpret_cast<InvokeFuncStorage>(invoke_func); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run(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) const { +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + PolymorphicInvoke f = + reinterpret_cast<PolymorphicInvoke>(polymorphic_invoke_); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + + return f(bind_state_.get(), internal::CallbackForward(a1), + internal::CallbackForward(a2), + internal::CallbackForward(a3), + internal::CallbackForward(a4), + internal::CallbackForward(a5)); + } + + private: + typedef R(*PolymorphicInvoke)( + internal::BindStateBase*, + typename internal::CallbackParamTraits<A1>::ForwardType, + typename internal::CallbackParamTraits<A2>::ForwardType, + typename internal::CallbackParamTraits<A3>::ForwardType, + typename internal::CallbackParamTraits<A4>::ForwardType, + typename internal::CallbackParamTraits<A5>::ForwardType); + +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6> +class Callback<R(A1, A2, A3, A4, A5, A6)> : public internal::CallbackBase { + public: + typedef R(RunType)(A1, A2, A3, A4, A5, A6); + + Callback() : CallbackBase(NULL) { } + + // Note that this constructor CANNOT be explicit, and that Bind() CANNOT + // return the exact Callback<> type. See base/bind.h for details. + template <typename Runnable, typename BindRunType, typename BoundArgsType> + Callback(internal::BindState<Runnable, BindRunType, + BoundArgsType>* bind_state) + : CallbackBase(bind_state) { + + // Force the assignment to a local variable of PolymorphicInvoke + // so the compiler will typecheck that the passed in Run() method has + // the correct type. + PolymorphicInvoke invoke_func = + &internal::BindState<Runnable, BindRunType, BoundArgsType> + ::InvokerType::Run; +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + polymorphic_invoke_ = reinterpret_cast<InvokeFuncStorage>(invoke_func); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run(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) const { +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + PolymorphicInvoke f = + reinterpret_cast<PolymorphicInvoke>(polymorphic_invoke_); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + + return f(bind_state_.get(), internal::CallbackForward(a1), + internal::CallbackForward(a2), + internal::CallbackForward(a3), + internal::CallbackForward(a4), + internal::CallbackForward(a5), + internal::CallbackForward(a6)); + } + + private: + typedef R(*PolymorphicInvoke)( + internal::BindStateBase*, + typename internal::CallbackParamTraits<A1>::ForwardType, + typename internal::CallbackParamTraits<A2>::ForwardType, + typename internal::CallbackParamTraits<A3>::ForwardType, + typename internal::CallbackParamTraits<A4>::ForwardType, + typename internal::CallbackParamTraits<A5>::ForwardType, + typename internal::CallbackParamTraits<A6>::ForwardType); + +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6, typename A7> +class Callback<R(A1, A2, A3, A4, A5, A6, A7)> : public internal::CallbackBase { + public: + typedef R(RunType)(A1, A2, A3, A4, A5, A6, A7); + + Callback() : CallbackBase(NULL) { } + + // Note that this constructor CANNOT be explicit, and that Bind() CANNOT + // return the exact Callback<> type. See base/bind.h for details. + template <typename Runnable, typename BindRunType, typename BoundArgsType> + Callback(internal::BindState<Runnable, BindRunType, + BoundArgsType>* bind_state) + : CallbackBase(bind_state) { + + // Force the assignment to a local variable of PolymorphicInvoke + // so the compiler will typecheck that the passed in Run() method has + // the correct type. + PolymorphicInvoke invoke_func = + &internal::BindState<Runnable, BindRunType, BoundArgsType> + ::InvokerType::Run; +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + polymorphic_invoke_ = reinterpret_cast<InvokeFuncStorage>(invoke_func); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run(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) const { +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + PolymorphicInvoke f = + reinterpret_cast<PolymorphicInvoke>(polymorphic_invoke_); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + + return f(bind_state_.get(), internal::CallbackForward(a1), + internal::CallbackForward(a2), + internal::CallbackForward(a3), + internal::CallbackForward(a4), + internal::CallbackForward(a5), + internal::CallbackForward(a6), + internal::CallbackForward(a7)); + } + + private: + typedef R(*PolymorphicInvoke)( + internal::BindStateBase*, + typename internal::CallbackParamTraits<A1>::ForwardType, + typename internal::CallbackParamTraits<A2>::ForwardType, + typename internal::CallbackParamTraits<A3>::ForwardType, + typename internal::CallbackParamTraits<A4>::ForwardType, + typename internal::CallbackParamTraits<A5>::ForwardType, + typename internal::CallbackParamTraits<A6>::ForwardType, + typename internal::CallbackParamTraits<A7>::ForwardType); + +}; + + +// Syntactic sugar to make Callbacks<void(void)> easier to declare since it +// will be used in a lot of APIs with delayed execution. +typedef Callback<void(void)> Closure; + +} // namespace base + +#endif // BASE_CALLBACK_H
diff --git a/src/base/callback.h.pump b/src/base/callback.h.pump new file mode 100644 index 0000000..b6a0aee --- /dev/null +++ b/src/base/callback.h.pump
@@ -0,0 +1,452 @@ +$$ 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 (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_CALLBACK_H_ +#define BASE_CALLBACK_H_ + +#include "base/callback_forward.h" +#include "base/callback_internal.h" +#include "base/template_util.h" + +// NOTE: Header files that do not require the full definition of Callback or +// Closure should #include "base/callback_forward.h" instead of this file. + +// ----------------------------------------------------------------------------- +// Introduction +// ----------------------------------------------------------------------------- +// +// The templated Callback class is a generalized function object. Together +// with the Bind() function in bind.h, they provide a type-safe method for +// performing partial application of functions. +// +// Partial application (or "currying") is the process of binding a subset of +// a function's arguments to produce another function that takes fewer +// arguments. This can be used to pass around a unit of delayed execution, +// much like lexical closures are used in other languages. For example, it +// is used in Chromium code to schedule tasks on different MessageLoops. +// +// A callback with no unbound input parameters (base::Callback<void(void)>) +// is called a base::Closure. Note that this is NOT the same as what other +// languages refer to as a closure -- it does not retain a reference to its +// enclosing environment. +// +// MEMORY MANAGEMENT AND PASSING +// +// The Callback objects themselves should be passed by const-reference, and +// stored by copy. They internally store their state via a refcounted class +// and thus do not need to be deleted. +// +// The reason to pass via a const-reference is to avoid unnecessary +// AddRef/Release pairs to the internal state. +// +// +// ----------------------------------------------------------------------------- +// Quick reference for basic stuff +// ----------------------------------------------------------------------------- +// +// BINDING A BARE FUNCTION +// +// int Return5() { return 5; } +// base::Callback<int(void)> func_cb = base::Bind(&Return5); +// LOG(INFO) << func_cb.Run(); // Prints 5. +// +// BINDING A CLASS METHOD +// +// The first argument to bind is the member function to call, the second is +// the object on which to call it. +// +// class Ref : public base::RefCountedThreadSafe<Ref> { +// public: +// int Foo() { return 3; } +// void PrintBye() { LOG(INFO) << "bye."; } +// }; +// scoped_refptr<Ref> ref = new Ref(); +// base::Callback<void(void)> ref_cb = base::Bind(&Ref::Foo, ref); +// LOG(INFO) << ref_cb.Run(); // Prints out 3. +// +// By default the object must support RefCounted or you will get a compiler +// error. If you're passing between threads, be sure it's +// RefCountedThreadSafe! See "Advanced binding of member functions" below if +// you don't want to use reference counting. +// +// RUNNING A CALLBACK +// +// Callbacks can be run with their "Run" method, which has the same +// signature as the template argument to the callback. +// +// void DoSomething(const base::Callback<void(int, std::string)>& callback) { +// callback.Run(5, "hello"); +// } +// +// Callbacks can be run more than once (they don't get deleted or marked when +// run). However, this precludes using base::Passed (see below). +// +// void DoSomething(const base::Callback<double(double)>& callback) { +// double myresult = callback.Run(3.14159); +// myresult += callback.Run(2.71828); +// } +// +// PASSING UNBOUND INPUT PARAMETERS +// +// Unbound parameters are specified at the time a callback is Run(). They are +// specified in the Callback template type: +// +// void MyFunc(int i, const std::string& str) {} +// base::Callback<void(int, const std::string&)> cb = base::Bind(&MyFunc); +// cb.Run(23, "hello, world"); +// +// PASSING BOUND INPUT PARAMETERS +// +// Bound parameters are specified when you create thee callback as arguments +// to Bind(). They will be passed to the function and the Run()ner of the +// callback doesn't see those values or even know that the function it's +// calling. +// +// void MyFunc(int i, const std::string& str) {} +// base::Callback<void(void)> cb = base::Bind(&MyFunc, 23, "hello world"); +// cb.Run(); +// +// A callback with no unbound input parameters (base::Callback<void(void)>) +// is called a base::Closure. So we could have also written: +// +// base::Closure cb = base::Bind(&MyFunc, 23, "hello world"); +// +// When calling member functions, bound parameters just go after the object +// pointer. +// +// base::Closure cb = base::Bind(&MyClass::MyFunc, this, 23, "hello world"); +// +// PARTIAL BINDING OF PARAMETERS +// +// You can specify some parameters when you create the callback, and specify +// the rest when you execute the callback. +// +// void MyFunc(int i, const std::string& str) {} +// base::Callback<void(const std::string&)> cb = base::Bind(&MyFunc, 23); +// cb.Run("hello world"); +// +// When calling a function bound parameters are first, followed by unbound +// parameters. +// +// +// ----------------------------------------------------------------------------- +// Quick reference for advanced binding +// ----------------------------------------------------------------------------- +// +// BINDING A CLASS METHOD WITH WEAK POINTERS +// +// base::Bind(&MyClass::Foo, GetWeakPtr()); +// +// The callback will not be issued if the object is destroyed at the time +// it's issued. DANGER: weak pointers are not threadsafe, so don't use this +// when passing between threads! +// +// BINDING A CLASS METHOD WITH MANUAL LIFETIME MANAGEMENT +// +// base::Bind(&MyClass::Foo, base::Unretained(this)); +// +// This disables all lifetime management on the object. You're responsible +// for making sure the object is alive at the time of the call. You break it, +// you own it! +// +// BINDING A CLASS METHOD AND HAVING THE CALLBACK OWN THE CLASS +// +// MyClass* myclass = new MyClass; +// base::Bind(&MyClass::Foo, base::Owned(myclass)); +// +// The object will be deleted when the callback is destroyed, even if it's +// not run (like if you post a task during shutdown). Potentially useful for +// "fire and forget" cases. +// +// IGNORING RETURN VALUES +// +// Sometimes you want to call a function that returns a value in a callback +// that doesn't expect a return value. +// +// int DoSomething(int arg) { cout << arg << endl; } +// base::Callback<void<int>) cb = +// base::Bind(base::IgnoreResult(&DoSomething)); +// +// +// ----------------------------------------------------------------------------- +// Quick reference for binding parameters to Bind() +// ----------------------------------------------------------------------------- +// +// Bound parameters are specified as arguments to Bind() and are passed to the +// function. A callback with no parameters or no unbound parameters is called a +// Closure (base::Callback<void(void)> and base::Closure are the same thing). +// +// PASSING PARAMETERS OWNED BY THE CALLBACK +// +// void Foo(int* arg) { cout << *arg << endl; } +// int* pn = new int(1); +// base::Closure foo_callback = base::Bind(&foo, base::Owned(pn)); +// +// The parameter will be deleted when the callback is destroyed, even if it's +// not run (like if you post a task during shutdown). +// +// PASSING PARAMETERS AS A scoped_ptr +// +// void TakesOwnership(scoped_ptr<Foo> arg) {} +// scoped_ptr<Foo> f(new Foo); +// // f becomes null during the following call. +// base::Closure cb = base::Bind(&TakesOwnership, base::Passed(&f)); +// +// Ownership of the parameter will be with the callback until the it is run, +// when ownership is passed to the callback function. This means the callback +// can only be run once. If the callback is never run, it will delete the +// object when it's destroyed. +// +// PASSING PARAMETERS AS A scoped_refptr +// +// void TakesOneRef(scoped_refptr<Foo> arg) {} +// scoped_refptr<Foo> f(new Foo) +// base::Closure cb = base::Bind(&TakesOneRef, f); +// +// This should "just work." The closure will take a reference as long as it +// is alive, and another reference will be taken for the called function. +// +// PASSING PARAMETERS BY REFERENCE +// +// void foo(int arg) { cout << arg << endl } +// int n = 1; +// base::Closure has_ref = base::Bind(&foo, base::ConstRef(n)); +// n = 2; +// has_ref.Run(); // Prints "2" +// +// Normally parameters are copied in the closure. DANGER: ConstRef stores a +// const reference instead, referencing the original parameter. This means +// that you must ensure the object outlives the callback! +// +// +// ----------------------------------------------------------------------------- +// Implementation notes +// ----------------------------------------------------------------------------- +// +// WHERE IS THIS DESIGN FROM: +// +// The design Callback and Bind is heavily influenced by C++'s +// tr1::function/tr1::bind, and by the "Google Callback" system used inside +// Google. +// +// +// HOW THE IMPLEMENTATION WORKS: +// +// There are three main components to the system: +// 1) The Callback classes. +// 2) The Bind() functions. +// 3) The arguments wrappers (e.g., Unretained() and ConstRef()). +// +// The Callback classes represent a generic function pointer. Internally, +// it stores a refcounted piece of state that represents the target function +// and all its bound parameters. Each Callback specialization has a templated +// constructor that takes an BindState<>*. In the context of the constructor, +// the static type of this BindState<> pointer uniquely identifies the +// function it is representing, all its bound parameters, and a Run() method +// that is capable of invoking the target. +// +// Callback's constructor takes the BindState<>* that has the full static type +// and erases the target function type as well as the types of the bound +// parameters. It does this by storing a pointer to the specific Run() +// function, and upcasting the state of BindState<>* to a +// BindStateBase*. This is safe as long as this BindStateBase pointer +// is only used with the stored Run() pointer. +// +// To BindState<> objects are created inside the Bind() functions. +// These functions, along with a set of internal templates, are responsible for +// +// - Unwrapping the function signature into return type, and parameters +// - Determining the number of parameters that are bound +// - Creating the BindState storing the bound parameters +// - Performing compile-time asserts to avoid error-prone behavior +// - Returning an Callback<> with an arity matching the number of unbound +// parameters and that knows the correct refcounting semantics for the +// target object if we are binding a method. +// +// The Bind functions do the above using type-inference, and template +// specializations. +// +// By default Bind() will store copies of all bound parameters, and attempt +// to refcount a target object if the function being bound is a class method. +// These copies are created even if the function takes parameters as const +// references. (Binding to non-const references is forbidden, see bind.h.) +// +// To change this behavior, we introduce a set of argument wrappers +// (e.g., Unretained(), and ConstRef()). These are simple container templates +// that are passed by value, and wrap a pointer to argument. See the +// file-level comment in base/bind_helpers.h for more info. +// +// These types are passed to the Unwrap() functions, and the MaybeRefcount() +// functions respectively to modify the behavior of Bind(). The Unwrap() +// and MaybeRefcount() functions change behavior by doing partial +// specialization based on whether or not a parameter is a wrapper type. +// +// ConstRef() is similar to tr1::cref. Unretained() is specific to Chromium. +// +// +// WHY NOT TR1 FUNCTION/BIND? +// +// Direct use of tr1::function and tr1::bind was considered, but ultimately +// rejected because of the number of copy constructors invocations involved +// in the binding of arguments during construction, and the forwarding of +// arguments during invocation. These copies will no longer be an issue in +// C++0x because C++0x will support rvalue reference allowing for the compiler +// to avoid these copies. However, waiting for C++0x is not an option. +// +// Measured with valgrind on gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5), the +// tr1::bind call itself will invoke a non-trivial copy constructor three times +// for each bound parameter. Also, each when passing a tr1::function, each +// bound argument will be copied again. +// +// In addition to the copies taken at binding and invocation, copying a +// tr1::function causes a copy to be made of all the bound parameters and +// state. +// +// Furthermore, in Chromium, it is desirable for the Callback to take a +// reference on a target object when representing a class method call. This +// is not supported by tr1. +// +// Lastly, tr1::function and tr1::bind has a more general and flexible API. +// This includes things like argument reordering by use of +// tr1::bind::placeholder, support for non-const reference parameters, and some +// limited amount of subtyping of the tr1::function object (e.g., +// tr1::function<int(int)> is convertible to tr1::function<void(int)>). +// +// These are not features that are required in Chromium. Some of them, such as +// allowing for reference parameters, and subtyping of functions, may actually +// become a source of errors. Removing support for these features actually +// allows for a simpler implementation, and a terser Currying API. +// +// +// WHY NOT GOOGLE CALLBACKS? +// +// The Google callback system also does not support refcounting. Furthermore, +// its implementation has a number of strange edge cases with respect to type +// conversion of its arguments. In particular, the argument's constness must +// at times match exactly the function signature, or the type-inference might +// break. Given the above, writing a custom solution was easier. +// +// +// MISSING FUNCTIONALITY +// - Invoking the return of Bind. Bind(&foo).Run() does not work; +// - Binding arrays to functions that take a non-const pointer. +// Example: +// void Foo(const char* ptr); +// void Bar(char* ptr); +// Bind(&Foo, "test"); +// Bind(&Bar, "test"); // This fails because ptr is not const. + +namespace base { + +// First, we forward declare the Callback class template. This informs the +// compiler that the template only has 1 type parameter which is the function +// signature that the Callback is representing. +// +// After this, create template specializations for 0-$(MAX_ARITY) parameters. Note that +// even though the template typelist grows, the specialization still +// only has one type: the function signature. +// +// If you are thinking of forward declaring Callback in your own header file, +// please include "base/callback_forward.h" instead. +template <typename Sig> +class Callback; + +namespace internal { +template <typename Runnable, typename RunType, typename BoundArgsType> +struct BindState; +} // namespace internal + + +$range ARITY 0..MAX_ARITY +$for ARITY [[ +$range ARG 1..ARITY + +$if ARITY == 0 [[ +template <typename R> +class Callback<R(void)> : public internal::CallbackBase { +]] $else [[ +template <typename R, $for ARG , [[typename A$(ARG)]]> +class Callback<R($for ARG , [[A$(ARG)]])> : public internal::CallbackBase { +]] + + public: + typedef R(RunType)($for ARG , [[A$(ARG)]]); + + Callback() : CallbackBase(NULL) { } + + // Note that this constructor CANNOT be explicit, and that Bind() CANNOT + // return the exact Callback<> type. See base/bind.h for details. + template <typename Runnable, typename BindRunType, typename BoundArgsType> + Callback(internal::BindState<Runnable, BindRunType, + BoundArgsType>* bind_state) + : CallbackBase(bind_state) { + + // Force the assignment to a local variable of PolymorphicInvoke + // so the compiler will typecheck that the passed in Run() method has + // the correct type. + PolymorphicInvoke invoke_func = + &internal::BindState<Runnable, BindRunType, BoundArgsType> + ::InvokerType::Run; +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + polymorphic_invoke_ = reinterpret_cast<InvokeFuncStorage>(invoke_func); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + } + + bool Equals(const Callback& other) const { + return CallbackBase::Equals(other); + } + + R Run($for ARG , + [[typename internal::CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]]) const { +#if defined(COBALT_WIN) +#pragma warning(push) +#pragma warning(disable : 4191) // Calling this function through the result + // pointer may cause your program to fail. +#endif + PolymorphicInvoke f = + reinterpret_cast<PolymorphicInvoke>(polymorphic_invoke_); +#if defined(COBALT_WIN) +#pragma warning(pop) +#endif + + return f(bind_state_.get()[[]] +$if ARITY != 0 [[, ]] +$for ARG , + [[internal::CallbackForward(a$(ARG))]]); + } + + private: + typedef R(*PolymorphicInvoke)( + internal::BindStateBase*[[]] +$if ARITY != 0 [[, ]] +$for ARG , [[typename internal::CallbackParamTraits<A$(ARG)>::ForwardType]]); + +}; + + +]] $$ for ARITY + +// Syntactic sugar to make Callbacks<void(void)> easier to declare since it +// will be used in a lot of APIs with delayed execution. +typedef Callback<void(void)> Closure; + +} // namespace base + +#endif // BASE_CALLBACK_H
diff --git a/src/base/callback_forward.h b/src/base/callback_forward.h new file mode 100644 index 0000000..7983248 --- /dev/null +++ b/src/base/callback_forward.h
@@ -0,0 +1,17 @@ +// 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. + +#ifndef BASE_CALLBACK_FORWARD_H_ +#define BASE_CALLBACK_FORWARD_H_ + +namespace base { + +template <typename Sig> +class Callback; + +typedef Callback<void(void)> Closure; + +} // namespace base + +#endif // BASE_CALLBACK_FORWARD_H
diff --git a/src/base/callback_helpers.h b/src/base/callback_helpers.h new file mode 100644 index 0000000..52cb71b --- /dev/null +++ b/src/base/callback_helpers.h
@@ -0,0 +1,30 @@ +// 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. + +// This defines helpful methods for dealing with Callbacks. Because Callbacks +// are implemented using templates, with a class per callback signature, adding +// methods to Callback<> itself is unattractive (lots of extra code gets +// generated). Instead, consider adding methods here. +// +// ResetAndReturn(&cb) is like cb.Reset() but allows executing a callback (via a +// copy) after the original callback is Reset(). This can be handy if Run() +// reads/writes the variable holding the Callback. + +#ifndef BASE_CALLBACK_HELPERS_H_ +#define BASE_CALLBACK_HELPERS_H_ + +#include "base/callback.h" + +namespace base { + +template <typename Sig> +base::Callback<Sig> ResetAndReturn(base::Callback<Sig>* cb) { + base::Callback<Sig> ret(*cb); + cb->Reset(); + return ret; +} + +} // namespace base + +#endif // BASE_CALLBACK_HELPERS_H_
diff --git a/src/base/callback_internal.cc b/src/base/callback_internal.cc new file mode 100644 index 0000000..2dde402 --- /dev/null +++ b/src/base/callback_internal.cc
@@ -0,0 +1,38 @@ +// 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/callback_internal.h" + +#include "base/logging.h" + +namespace base { +namespace internal { + +bool CallbackBase::is_null() const { + return bind_state_.get() == NULL; +} + +void CallbackBase::Reset() { + polymorphic_invoke_ = NULL; + // NULL the bind_state_ last, since it may be holding the last ref to whatever + // object owns us, and we may be deleted after that. + bind_state_ = NULL; +} + +bool CallbackBase::Equals(const CallbackBase& other) const { + return bind_state_.get() == other.bind_state_.get() && + polymorphic_invoke_ == other.polymorphic_invoke_; +} + +CallbackBase::CallbackBase(BindStateBase* bind_state) + : bind_state_(bind_state), + polymorphic_invoke_(NULL) { + DCHECK(!bind_state_ || bind_state_->HasOneRef()); +} + +CallbackBase::~CallbackBase() { +} + +} // namespace internal +} // namespace base
diff --git a/src/base/callback_internal.h b/src/base/callback_internal.h new file mode 100644 index 0000000..2f834c3 --- /dev/null +++ b/src/base/callback_internal.h
@@ -0,0 +1,193 @@ +// 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. + +// This file contains utility functions and classes that help the +// implementation, and management of the Callback objects. + +#ifndef BASE_CALLBACK_INTERNAL_H_ +#define BASE_CALLBACK_INTERNAL_H_ + +#include <stddef.h> + +#include "base/base_export.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" + +template <typename T> +class ScopedVector; + +namespace base { +namespace internal { + +// BindStateBase is used to provide an opaque handle that the Callback +// class can use to represent a function object with bound arguments. It +// behaves as an existential type that is used by a corresponding +// DoInvoke function to perform the function execution. This allows +// us to shield the Callback class from the types of the bound argument via +// "type erasure." +class BindStateBase : public RefCountedThreadSafe<BindStateBase> { + protected: + friend class RefCountedThreadSafe<BindStateBase>; + virtual ~BindStateBase() {} +}; + +// Holds the Callback methods that don't require specialization to reduce +// template bloat. +class BASE_EXPORT CallbackBase { + public: + // Returns true if Callback is null (doesn't refer to anything). + bool is_null() const; + + // Returns the Callback into an uninitialized state. + void Reset(); + + protected: + // In C++, it is safe to cast function pointers to function pointers of + // another type. It is not okay to use void*. We create a InvokeFuncStorage + // that that can store our function pointer, and then cast it back to + // the original type on usage. + typedef void(*InvokeFuncStorage)(void); + + // Returns true if this callback equals |other|. |other| may be null. + bool Equals(const CallbackBase& other) const; + + // Allow initializing of |bind_state_| via the constructor to avoid default + // initialization of the scoped_refptr. We do not also initialize + // |polymorphic_invoke_| here because doing a normal assignment in the + // derived Callback templates makes for much nicer compiler errors. + explicit CallbackBase(BindStateBase* bind_state); + + // Force the destructor to be instantiated inside this translation unit so + // that our subclasses will not get inlined versions. Avoids more template + // bloat. + ~CallbackBase(); + + scoped_refptr<BindStateBase> bind_state_; + InvokeFuncStorage polymorphic_invoke_; +}; + +// This is a typetraits object that's used to take an argument type, and +// extract a suitable type for storing and forwarding arguments. +// +// In particular, it strips off references, and converts arrays to +// pointers for storage; and it avoids accidentally trying to create a +// "reference of a reference" if the argument is a reference type. +// +// This array type becomes an issue for storage because we are passing bound +// 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> +struct CallbackParamTraits { + typedef const T& ForwardType; + typedef T StorageType; +}; + +// The Storage should almost be impossible to trigger unless someone manually +// specifies type of the bind parameters. However, in case they do, +// this will guard against us accidentally storing a reference parameter. +// +// The ForwardType should only be used for unbound arguments. +template <typename T> +struct CallbackParamTraits<T&> { + typedef T& ForwardType; + typedef T StorageType; +}; + +// Note that for array types, we implicitly add a const in the conversion. This +// means that it is not possible to bind array arguments to functions that take +// a non-const pointer. Trying to specialize the template based on a "const +// 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]> { + typedef const T* ForwardType; + typedef const T* StorageType; +}; + +// See comment for CallbackParamTraits<T[n]>. +template <typename T> +struct CallbackParamTraits<T[]> { + typedef const T* ForwardType; + typedef const T* StorageType; +}; + +// Parameter traits for movable-but-not-copyable scopers. +// +// Callback<>/Bind() understands movable-but-not-copyable semantics where +// the type cannot be copied but can still have its state destructively +// transferred (aka. moved) to another instance of the same type by calling a +// helper function. When used with Bind(), this signifies transferal of the +// object's state to the target function. +// +// For these types, the ForwardType must not be a const reference, or a +// reference. A const reference is inappropriate, and would break const +// 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> +struct CallbackParamTraits<scoped_ptr<T> > { + typedef scoped_ptr<T> ForwardType; + typedef scoped_ptr<T> StorageType; +}; + +template <typename T> +struct CallbackParamTraits<scoped_array<T> > { + typedef scoped_array<T> ForwardType; + typedef scoped_array<T> 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; +}; + +// CallbackForward() is a very limited simulation of C++11's std::forward() +// used by the Callback/Bind system for a set of movable-but-not-copyable +// types. It is needed because forwarding a movable-but-not-copyable +// argument to another function requires us to invoke the proper move +// operator to create a rvalue version of the type. The supported types are +// whitelisted below as overloads of the CallbackForward() function. The +// default template compiles out to be a no-op. +// +// In C++11, std::forward would replace all uses of this function. However, it +// is impossible to implement a general std::forward with C++11 due to a lack +// of rvalue references. +// +// In addition to Callback/Bind, this is used by PostTaskAndReplyWithResult to +// simulate std::forward() and forward the result of one Callback as a +// 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> +scoped_ptr<T> CallbackForward(scoped_ptr<T>& p) { return p.Pass(); } + +template <typename T> +scoped_array<T> CallbackForward(scoped_array<T>& p) { return p.Pass(); } + +template <typename T, typename R> +scoped_ptr_malloc<T, R> CallbackForward(scoped_ptr_malloc<T, R>& p) { + return p.Pass(); +} + +template <typename T> +ScopedVector<T> CallbackForward(ScopedVector<T>& p) { return p.Pass(); } + +} // namespace internal +} // namespace base + +#endif // BASE_CALLBACK_INTERNAL_H_
diff --git a/src/base/callback_unittest.cc b/src/base/callback_unittest.cc new file mode 100644 index 0000000..2c2bef1 --- /dev/null +++ b/src/base/callback_unittest.cc
@@ -0,0 +1,181 @@ +// 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/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/callback_internal.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +struct FakeInvoker { + typedef void(RunType)(internal::BindStateBase*); + static void Run(internal::BindStateBase*) { + } +}; + +} // namespace + +namespace internal { +template <typename Runnable, typename RunType, typename BoundArgsType> +struct BindState; + +// White-box testpoints to inject into a Callback<> object for checking +// comparators and emptiness APIs. Use a BindState that is specialized +// based on a type we declared in the anonymous namespace above to remove any +// chance of colliding with another instantiation and breaking the +// one-definition-rule. +template <> +struct BindState<void(void), void(void), void(FakeInvoker)> + : public BindStateBase { + public: + typedef FakeInvoker InvokerType; +}; + +template <> +struct BindState<void(void), void(void), + void(FakeInvoker, FakeInvoker)> + : public BindStateBase { + public: + typedef FakeInvoker InvokerType; +}; +} // namespace internal + +namespace { + +typedef internal::BindState<void(void), void(void), void(FakeInvoker)> + FakeBindState1; +typedef internal::BindState<void(void), void(void), + void(FakeInvoker, FakeInvoker)> + FakeBindState2; + +class CallbackTest : public ::testing::Test { + public: + CallbackTest() + : callback_a_(new FakeBindState1()), + callback_b_(new FakeBindState2()) { + } + + virtual ~CallbackTest() { + } + + protected: + Callback<void(void)> callback_a_; + const Callback<void(void)> callback_b_; // Ensure APIs work with const. + Callback<void(void)> null_callback_; +}; + +// Ensure we can create unbound callbacks. We need this to be able to store +// them in class members that can be initialized later. +TEST_F(CallbackTest, DefaultConstruction) { + Callback<void(void)> c0; + Callback<void(int)> c1; + Callback<void(int,int)> c2; + Callback<void(int,int,int)> c3; + Callback<void(int,int,int,int)> c4; + Callback<void(int,int,int,int,int)> c5; + Callback<void(int,int,int,int,int,int)> c6; + + EXPECT_TRUE(c0.is_null()); + EXPECT_TRUE(c1.is_null()); + EXPECT_TRUE(c2.is_null()); + EXPECT_TRUE(c3.is_null()); + EXPECT_TRUE(c4.is_null()); + EXPECT_TRUE(c5.is_null()); + EXPECT_TRUE(c6.is_null()); +} + +TEST_F(CallbackTest, IsNull) { + EXPECT_TRUE(null_callback_.is_null()); + EXPECT_FALSE(callback_a_.is_null()); + EXPECT_FALSE(callback_b_.is_null()); +} + +TEST_F(CallbackTest, Equals) { + EXPECT_TRUE(callback_a_.Equals(callback_a_)); + EXPECT_FALSE(callback_a_.Equals(callback_b_)); + EXPECT_FALSE(callback_b_.Equals(callback_a_)); + + // We should compare based on instance, not type. + Callback<void(void)> callback_c(new FakeBindState1()); + Callback<void(void)> callback_a2 = callback_a_; + EXPECT_TRUE(callback_a_.Equals(callback_a2)); + EXPECT_FALSE(callback_a_.Equals(callback_c)); + + // Empty, however, is always equal to empty. + Callback<void(void)> empty2; + EXPECT_TRUE(null_callback_.Equals(empty2)); +} + +TEST_F(CallbackTest, Reset) { + // Resetting should bring us back to empty. + ASSERT_FALSE(callback_a_.is_null()); + ASSERT_FALSE(callback_a_.Equals(null_callback_)); + + callback_a_.Reset(); + + EXPECT_TRUE(callback_a_.is_null()); + EXPECT_TRUE(callback_a_.Equals(null_callback_)); +} + +struct TestForReentrancy { + TestForReentrancy() + : cb_already_run(false), + cb(Bind(&TestForReentrancy::AssertCBIsNull, Unretained(this))) { + } + void AssertCBIsNull() { + ASSERT_TRUE(cb.is_null()); + cb_already_run = true; + } + bool cb_already_run; + Closure cb; +}; + +TEST_F(CallbackTest, ResetAndReturn) { + TestForReentrancy tfr; + ASSERT_FALSE(tfr.cb.is_null()); + ASSERT_FALSE(tfr.cb_already_run); + ResetAndReturn(&tfr.cb).Run(); + ASSERT_TRUE(tfr.cb.is_null()); + ASSERT_TRUE(tfr.cb_already_run); +} + +class CallbackOwner : public base::RefCounted<CallbackOwner> { + public: + CallbackOwner(bool* deleted) { + callback_ = Bind(&CallbackOwner::Unused, this); + deleted_ = deleted; + } + void Reset() { + callback_.Reset(); + // We are deleted here if no-one else had a ref to us. + } + + private: + friend class base::RefCounted<CallbackOwner>; + virtual ~CallbackOwner() { + *deleted_ = true; + } + void Unused() { + FAIL() << "Should never be called"; + } + + Closure callback_; + bool* deleted_; +}; + +TEST_F(CallbackTest, CallbackHasLastRefOnContainingObject) { + bool deleted = false; + CallbackOwner* owner = new CallbackOwner(&deleted); + owner->Reset(); + ASSERT_TRUE(deleted); +} + +} // namespace +} // namespace base
diff --git a/src/base/callback_unittest.nc b/src/base/callback_unittest.nc new file mode 100644 index 0000000..9bddd1f --- /dev/null +++ b/src/base/callback_unittest.nc
@@ -0,0 +1,50 @@ +// 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/callback.h" + +namespace base { + +class Parent { +}; + +class Child : Parent { +}; + +#if defined(NCTEST_EQUALS_REQUIRES_SAMETYPE) // [r"no matching function for call to 'base::Callback<void\(\)>::Equals\(base::Callback<int\(\)>&\)'"] + +// Attempting to call comparison function on two callbacks of different type. +// +// This should be a compile time failure because each callback type should be +// considered distinct. +void WontCompile() { + Closure c1; + Callback<int(void)> c2; + c1.Equals(c2); +} + +#elif defined(NCTEST_CONSTRUCTION_FROM_SUBTYPE) // [r"conversion from 'base::Callback<base::Parent\(\)>' to non-scalar type 'base::Callback<base::Child\(\)>'"] + +// Construction of Callback<A> from Callback<B> if A is supertype of B. +// +// While this is technically safe, most people aren't used to it when coding +// C++ so if this is happening, it is almost certainly an error. +void WontCompile() { + Callback<Parent(void)> cb_a; + Callback<Child(void)> cb_b = cb_a; +} + +#elif defined(NCTEST_ASSIGNMENT_FROM_SUBTYPE) // [r"no match for 'operator=' in 'cb_a = cb_b'"] + +// Assignment of Callback<A> from Callback<B> if A is supertype of B. +// See explanation for NCTEST_CONSTRUCTION_FROM_SUBTYPE +void WontCompile() { + Callback<Parent(void)> cb_a; + Callback<Child(void)> cb_b; + cb_a = cb_b; +} + +#endif + +} // namespace base
diff --git a/src/base/cancelable_callback.h b/src/base/cancelable_callback.h new file mode 100644 index 0000000..b781677 --- /dev/null +++ b/src/base/cancelable_callback.h
@@ -0,0 +1,202 @@ +// 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. +// +// CancelableCallback is a wrapper around base::Callback that allows +// cancellation of a callback. CancelableCallback takes a reference on the +// wrapped callback until this object is destroyed or Reset()/Cancel() are +// called. +// +// NOTE: +// +// Calling CancellableCallback::Cancel() brings the object back to its natural, +// default-constructed state, i.e., CancellableCallback::callback() will return +// a null callback. +// +// THREAD-SAFETY: +// +// CancelableCallback objects must be created on, posted to, cancelled on, and +// destroyed on the same thread. +// +// +// EXAMPLE USAGE: +// +// In the following example, the test is verifying that RunIntensiveTest() +// Quit()s the message loop within 4 seconds. The cancelable callback is posted +// to the message loop, the intensive test runs, the message loop is run, +// then the callback is cancelled. +// +// void TimeoutCallback(const std::string& timeout_message) { +// FAIL() << timeout_message; +// MessageLoop::current()->Quit(); +// } +// +// CancelableClosure timeout(base::Bind(&TimeoutCallback, "Test timed out.")); +// MessageLoop::current()->PostDelayedTask(FROM_HERE, timeout.callback(), +// 4000) // 4 seconds to run. +// RunIntensiveTest(); +// MessageLoop::current()->Run(); +// timeout.Cancel(); // Hopefully this is hit before the timeout callback runs. +// + +#ifndef BASE_CANCELABLE_CALLBACK_H_ +#define BASE_CANCELABLE_CALLBACK_H_ + +#include "base/base_export.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_internal.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/memory/weak_ptr.h" + +namespace base { + +template <typename Sig> +class CancelableCallback; + +template <> +class CancelableCallback<void(void)> { + public: + CancelableCallback() : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {} + + // |callback| must not be null. + explicit CancelableCallback(const base::Callback<void(void)>& callback) + : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), + callback_(callback) { + DCHECK(!callback.is_null()); + InitializeForwarder(); + } + + ~CancelableCallback() {} + + // Cancels and drops the reference to the wrapped callback. + void Cancel() { + weak_factory_.InvalidateWeakPtrs(); + forwarder_.Reset(); + callback_.Reset(); + } + + // Returns true if the wrapped callback has been cancelled. + bool IsCancelled() const { + return callback_.is_null(); + } + + // Sets |callback| as the closure that may be cancelled. |callback| may not + // be null. Outstanding and any previously wrapped callbacks are cancelled. + void Reset(const base::Callback<void(void)>& callback) { + DCHECK(!callback.is_null()); + + // Outstanding tasks (e.g., posted to a message loop) must not be called. + Cancel(); + + // |forwarder_| is no longer valid after Cancel(), so re-bind. + InitializeForwarder(); + + callback_ = callback; + } + + // Returns a callback that can be disabled by calling Cancel(). + const base::Callback<void(void)>& callback() const { + return forwarder_; + } + + private: + void Forward() { + callback_.Run(); + } + + // Helper method to bind |forwarder_| using a weak pointer from + // |weak_factory_|. + void InitializeForwarder() { + forwarder_ = base::Bind(&CancelableCallback<void(void)>::Forward, + weak_factory_.GetWeakPtr()); + } + + // Used to ensure Forward() is not run when this object is destroyed. + base::WeakPtrFactory<CancelableCallback<void(void)> > weak_factory_; + + // The wrapper closure. + base::Callback<void(void)> forwarder_; + + // The stored closure that may be cancelled. + base::Callback<void(void)> callback_; + + DISALLOW_COPY_AND_ASSIGN(CancelableCallback); +}; + +template <typename A1> +class CancelableCallback<void(A1)> { + public: + CancelableCallback() : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {} + + // |callback| must not be null. + explicit CancelableCallback(const base::Callback<void(A1)>& callback) + : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), + callback_(callback) { + DCHECK(!callback.is_null()); + InitializeForwarder(); + } + + ~CancelableCallback() {} + + // Cancels and drops the reference to the wrapped callback. + void Cancel() { + weak_factory_.InvalidateWeakPtrs(); + forwarder_.Reset(); + callback_.Reset(); + } + + // Returns true if the wrapped callback has been cancelled. + bool IsCancelled() const { + return callback_.is_null(); + } + + // Sets |callback| as the closure that may be cancelled. |callback| may not + // be null. Outstanding and any previously wrapped callbacks are cancelled. + void Reset(const base::Callback<void(A1)>& callback) { + DCHECK(!callback.is_null()); + + // Outstanding tasks (e.g., posted to a message loop) must not be called. + Cancel(); + + // |forwarder_| is no longer valid after Cancel(), so re-bind. + InitializeForwarder(); + + callback_ = callback; + } + + // Returns a callback that can be disabled by calling Cancel(). + const base::Callback<void(A1)>& callback() const { + return forwarder_; + } + + private: + void Forward(A1 a1) const { + callback_.Run(a1); + } + + // Helper method to bind |forwarder_| using a weak pointer from + // |weak_factory_|. + void InitializeForwarder() { + forwarder_ = base::Bind(&CancelableCallback<void(A1)>::Forward, + weak_factory_.GetWeakPtr()); + } + + // Used to ensure Forward() is not run when this object is destroyed. + base::WeakPtrFactory<CancelableCallback<void(A1)> > weak_factory_; + + // The wrapper closure. + base::Callback<void(A1)> forwarder_; + + // The stored closure that may be cancelled. + base::Callback<void(A1)> callback_; + + DISALLOW_COPY_AND_ASSIGN(CancelableCallback); +}; + +typedef CancelableCallback<void(void)> CancelableClosure; + +} // namespace base + +#endif // BASE_CANCELABLE_CALLBACK_H_
diff --git a/src/base/cancelable_callback_unittest.cc b/src/base/cancelable_callback_unittest.cc new file mode 100644 index 0000000..51a2a09 --- /dev/null +++ b/src/base/cancelable_callback_unittest.cc
@@ -0,0 +1,185 @@ +// 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/cancelable_callback.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +class TestRefCounted : public RefCountedThreadSafe<TestRefCounted> { + private: + friend class RefCountedThreadSafe<TestRefCounted>; + ~TestRefCounted() {}; +}; + +void Increment(int* count) { (*count)++; } +void IncrementBy(int* count, int n) { (*count) += n; } +void RefCountedParam(const scoped_refptr<TestRefCounted>& ref_counted) {} + +// Cancel(). +// - Callback can be run multiple times. +// - After Cancel(), Run() completes but has no effect. +TEST(CancelableCallbackTest, Cancel) { + int count = 0; + CancelableClosure cancelable( + base::Bind(&Increment, base::Unretained(&count))); + + base::Closure callback = cancelable.callback(); + callback.Run(); + EXPECT_EQ(1, count); + + callback.Run(); + EXPECT_EQ(2, count); + + cancelable.Cancel(); + callback.Run(); + EXPECT_EQ(2, count); +} + +// Cancel() called multiple times. +// - Cancel() cancels all copies of the wrapped callback. +// - Calling Cancel() more than once has no effect. +// - After Cancel(), callback() returns a null callback. +TEST(CancelableCallbackTest, MultipleCancel) { + int count = 0; + CancelableClosure cancelable( + base::Bind(&Increment, base::Unretained(&count))); + + base::Closure callback1 = cancelable.callback(); + base::Closure callback2 = cancelable.callback(); + cancelable.Cancel(); + + callback1.Run(); + EXPECT_EQ(0, count); + + callback2.Run(); + EXPECT_EQ(0, count); + + // Calling Cancel() again has no effect. + cancelable.Cancel(); + + // callback() of a cancelled callback is null. + base::Closure callback3 = cancelable.callback(); + EXPECT_TRUE(callback3.is_null()); +} + +// CancelableCallback destroyed before callback is run. +// - Destruction of CancelableCallback cancels outstanding callbacks. +TEST(CancelableCallbackTest, CallbackCanceledOnDestruction) { + int count = 0; + base::Closure callback; + + { + CancelableClosure cancelable( + base::Bind(&Increment, base::Unretained(&count))); + + callback = cancelable.callback(); + callback.Run(); + EXPECT_EQ(1, count); + } + + callback.Run(); + EXPECT_EQ(1, count); +} + +// Cancel() called on bound closure with a RefCounted parameter. +// - Cancel drops wrapped callback (and, implicitly, its bound arguments). +TEST(CancelableCallbackTest, CancelDropsCallback) { + scoped_refptr<TestRefCounted> ref_counted = new TestRefCounted; + EXPECT_TRUE(ref_counted->HasOneRef()); + + CancelableClosure cancelable(base::Bind(RefCountedParam, ref_counted)); + EXPECT_FALSE(cancelable.IsCancelled()); + EXPECT_TRUE(ref_counted.get()); + EXPECT_FALSE(ref_counted->HasOneRef()); + + // There is only one reference to |ref_counted| after the Cancel(). + cancelable.Cancel(); + EXPECT_TRUE(cancelable.IsCancelled()); + EXPECT_TRUE(ref_counted.get()); + EXPECT_TRUE(ref_counted->HasOneRef()); +} + +// Reset(). +// - Reset() replaces the existing wrapped callback with a new callback. +// - Reset() deactivates outstanding callbacks. +TEST(CancelableCallbackTest, Reset) { + int count = 0; + CancelableClosure cancelable( + base::Bind(&Increment, base::Unretained(&count))); + + base::Closure callback = cancelable.callback(); + callback.Run(); + EXPECT_EQ(1, count); + + callback.Run(); + EXPECT_EQ(2, count); + + cancelable.Reset( + base::Bind(&IncrementBy, base::Unretained(&count), 3)); + EXPECT_FALSE(cancelable.IsCancelled()); + + // The stale copy of the cancelable callback is non-null. + ASSERT_FALSE(callback.is_null()); + + // The stale copy of the cancelable callback is no longer active. + callback.Run(); + EXPECT_EQ(2, count); + + base::Closure callback2 = cancelable.callback(); + ASSERT_FALSE(callback2.is_null()); + + callback2.Run(); + EXPECT_EQ(5, count); +} + +// IsCanceled(). +// - Cancel() transforms the CancelableCallback into a cancelled state. +TEST(CancelableCallbackTest, IsNull) { + CancelableClosure cancelable; + EXPECT_TRUE(cancelable.IsCancelled()); + + int count = 0; + cancelable.Reset(base::Bind(&Increment, + base::Unretained(&count))); + EXPECT_FALSE(cancelable.IsCancelled()); + + cancelable.Cancel(); + EXPECT_TRUE(cancelable.IsCancelled()); +} + +// CancelableCallback posted to a MessageLoop with PostTask. +// - Callbacks posted to a MessageLoop can be cancelled. +TEST(CancelableCallbackTest, PostTask) { + MessageLoop loop(MessageLoop::TYPE_DEFAULT); + + int count = 0; + CancelableClosure cancelable(base::Bind(&Increment, + base::Unretained(&count))); + + MessageLoop::current()->PostTask(FROM_HERE, cancelable.callback()); + MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure()); + MessageLoop::current()->Run(); + + EXPECT_EQ(1, count); + + MessageLoop::current()->PostTask(FROM_HERE, cancelable.callback()); + MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure()); + + // Cancel before running the message loop. + cancelable.Cancel(); + MessageLoop::current()->Run(); + + // Callback never ran due to cancellation; count is the same. + EXPECT_EQ(1, count); +} + +} // namespace +} // namespace base
diff --git a/src/base/check_example.cc b/src/base/check_example.cc new file mode 100644 index 0000000..4b3f428 --- /dev/null +++ b/src/base/check_example.cc
@@ -0,0 +1,25 @@ +// 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 is meant for analyzing the code generated by the CHECK +// macros in a small executable file that's easy to disassemble. + +#include "base/logging.h" + +// An official build shouldn't generate code to print out messages for +// the CHECK* macros, nor should it have the strings in the +// executable. + +void DoCheck(bool b) { + CHECK(b) << "DoCheck " << b; +} + +void DoCheckEq(int x, int y) { + CHECK_EQ(x, y); +} + +int main(int argc, const char* argv[]) { + DoCheck(argc > 1); + DoCheckEq(argc, 1); +}
diff --git a/src/base/chromeos/chromeos_version.cc b/src/base/chromeos/chromeos_version.cc new file mode 100644 index 0000000..4a70cd5 --- /dev/null +++ b/src/base/chromeos/chromeos_version.cc
@@ -0,0 +1,24 @@ +// 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/src/base/chromeos/chromeos_version.h b/src/base/chromeos/chromeos_version.h new file mode 100644 index 0000000..25acd43 --- /dev/null +++ b/src/base/chromeos/chromeos_version.h
@@ -0,0 +1,20 @@ +// 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/src/base/circular_buffer_shell.cc b/src/base/circular_buffer_shell.cc new file mode 100644 index 0000000..a41d89a --- /dev/null +++ b/src/base/circular_buffer_shell.cc
@@ -0,0 +1,251 @@ +// Copyright (c) 2014 Google Inc. 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/circular_buffer_shell.h" + +#include <stdint.h> + +#include <algorithm> + +#include "base/logging.h" +#include "build/build_config.h" + +#if defined(OS_STARBOARD) +#include "starboard/memory.h" +#define malloc SbMemoryAllocate +#define realloc SbMemoryReallocate +#define free SbMemoryFree +#endif + +static inline void* add_to_pointer(void* pointer, size_t amount) { + return static_cast<uint8_t*>(pointer) + amount; +} + +static inline const void* add_to_pointer(const void* pointer, size_t amount) { + return static_cast<const uint8_t*>(pointer) + amount; +} + +namespace base { + +CircularBufferShell::CircularBufferShell( + size_t max_capacity, + ReserveType reserve_type /*= kDoNotReserve*/) + : max_capacity_(max_capacity), + buffer_(NULL), + capacity_(0), + length_(0), + read_position_(0) { + if (reserve_type == kReserve) { + IncreaseCapacityTo(max_capacity_); + } +} + +CircularBufferShell::~CircularBufferShell() { + Clear(); +} + +void CircularBufferShell::Clear() { + base::AutoLock l(lock_); + if (buffer_ != NULL) { + free(buffer_); + buffer_ = NULL; + } + + capacity_ = 0; + length_ = 0; + read_position_ = 0; +} + +void CircularBufferShell::Read(void* destination, + size_t length, + size_t* bytes_read) { + base::AutoLock l(lock_); + DCHECK(destination != NULL || length == 0); + if (destination == NULL) + length = 0; + + ReadAndAdvanceUnchecked(destination, length, bytes_read); +} + +void CircularBufferShell::Peek(void* destination, + size_t length, + size_t source_offset, + size_t* bytes_peeked) const { + base::AutoLock l(lock_); + DCHECK(destination != NULL || length == 0); + if (destination == NULL) + length = 0; + + ReadUnchecked(destination, length, source_offset, bytes_peeked); +} + +void CircularBufferShell::Skip(size_t length, size_t* bytes_skipped) { + base::AutoLock l(lock_); + ReadAndAdvanceUnchecked(NULL, length, bytes_skipped); +} + +bool CircularBufferShell::Write(const void* source, + size_t length, + size_t* bytes_written) { + base::AutoLock l(lock_); + DCHECK(source != NULL || length == 0); + if (source == NULL) + length = 0; + + if (!EnsureCapacityToWrite(length)) { + return false; + } + + size_t produced = 0; + while (true) { + size_t remaining = length - produced; + + // In this pass, write up to the contiguous space left. + size_t to_write = std::min(remaining, capacity_ - GetWritePosition()); + if (to_write == 0) + break; + + // Copy this segment and do the accounting. + void* destination = GetWritePointer(); + const void* src = add_to_pointer(source, produced); + memcpy(destination, src, to_write); + length_ += to_write; + produced += to_write; + } + + if (bytes_written) + *bytes_written = produced; + return true; +} + +size_t CircularBufferShell::GetLength() const { + base::AutoLock l(lock_); + return length_; +} + +void CircularBufferShell::ReadUnchecked(void* destination, + size_t destination_length, + size_t source_offset, + size_t* bytes_read) const { + DCHECK(destination != NULL || bytes_read != NULL); + + size_t dummy = 0; + if (!bytes_read) { + bytes_read = &dummy; + } + + // Return immediately if the CircularBuffer is empty or if |source_offset| is + // greater or equal than |length_|. + if (capacity_ == 0 || source_offset >= length_) { + *bytes_read = 0; + return; + } + + size_t consumed = 0; + size_t source_length = length_ - source_offset; + size_t read_position = (read_position_ + source_offset) % capacity_; + + while (true) { + size_t remaining = std::min(source_length, destination_length - consumed); + + // In this pass, read the remaining data that is contiguous. + size_t to_read = std::min(remaining, capacity_ - read_position); + if (to_read == 0) + break; + + // Copy this segment and do the accounting. + const void* source = add_to_pointer(buffer_, read_position); + if (destination) { + void* dest = add_to_pointer(destination, consumed); + memcpy(dest, source, to_read); + } + source_length -= to_read; + read_position = (read_position + to_read) % capacity_; + consumed += to_read; + } + + *bytes_read = consumed; +} + +void CircularBufferShell::ReadAndAdvanceUnchecked(void* destination, + size_t destination_length, + size_t* bytes_read) { + size_t dummy = 0; + if (!bytes_read) { + bytes_read = &dummy; + } + + // Return immediately if the CircularBuffer is empty. + if (capacity_ == 0) { + *bytes_read = 0; + return; + } + + ReadUnchecked(destination, destination_length, 0, bytes_read); + length_ -= *bytes_read; + read_position_ = (read_position_ + *bytes_read) % capacity_; +} + +void* CircularBufferShell::GetWritePointer() const { + return add_to_pointer(buffer_, GetWritePosition()); +} + +size_t CircularBufferShell::GetWritePosition() const { + return (read_position_ + length_) % capacity_; +} + +bool CircularBufferShell::EnsureCapacityToWrite(size_t length) { + if (capacity_ - length_ < length) { + size_t capacity = std::max(2 * capacity_, length_ + length); + if (capacity > max_capacity_) + capacity = max_capacity_; + + // New capacity still won't be enough. + if (capacity - length_ < length) { + return false; + } + + return IncreaseCapacityTo(capacity); + } + + return true; +} + +bool CircularBufferShell::IncreaseCapacityTo(size_t capacity) { + if (capacity <= capacity_) { + return true; + } + + // If the data isn't wrapped, we can just use realloc. + if (buffer_ != NULL && read_position_ + length_ <= capacity_) { + void* result = realloc(buffer_, capacity); + if (result == NULL) { + return false; + } + capacity_ = capacity; + buffer_ = result; + return true; + } + + void* buffer = malloc(capacity); + if (buffer == NULL) { + return false; + } + + // Read does everything we want, but it will trounce length_. + size_t length = length_; + + // Copy the data over to the new buffer. + ReadUnchecked(buffer, length_, 0, NULL); + + // Adjust the accounting. + length_ = length; + read_position_ = 0; + capacity_ = capacity; + free(buffer_); + buffer_ = buffer; + return true; +} + +} // namespace base
diff --git a/src/base/circular_buffer_shell.h b/src/base/circular_buffer_shell.h new file mode 100644 index 0000000..4350af1 --- /dev/null +++ b/src/base/circular_buffer_shell.h
@@ -0,0 +1,101 @@ +// Copyright (c) 2014 Google Inc. 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_CIRCULAR_BUFFER_SHELL_H_ +#define BASE_CIRCULAR_BUFFER_SHELL_H_ + +#include "base/base_export.h" +#include "base/synchronization/lock.h" + +namespace base { + +// A thread-safe circular buffer implementation. +// TODO: Returns the size in Read(), Peek(), and Skip() as a return +// value. +class BASE_EXPORT CircularBufferShell { + public: + enum ReserveType { kDoNotReserve, kReserve }; + + CircularBufferShell(size_t max_capacity, + ReserveType reserve_type = kDoNotReserve); + ~CircularBufferShell(); + + // Clears out all data in the buffer, releasing any allocated memory. + // Idempotent. + void Clear(); + + // Reads the requested amount of data into the given buffer, writing the + // number of bytes actually read into the bytes_read paramter. If there is + // less data then requested, then the remaining data will be consumed. + void Read(void* destination, size_t length, size_t* bytes_read); + + // It works the same as Read() except: + // 1. It doesn't modify the buffer in any way. + // 2. It allows the caller to specify an offset inside the buffer where the + // peek starts. + void Peek(void* destination, + size_t length, + size_t source_offset, + size_t* bytes_peeked) const; + + // Advance the buffer cursor without reading any data. + void Skip(size_t length, size_t* bytes_skipped); + + // Writes the given data into the circular buffer. Returns false if the buffer + // could not be expanded to hold the new data. If returning false, + // bytes_written will not be set, and the buffer will remain unchanged. + // TODO: Remove bytes_written. Because Write returns false when + // the buffer cannot hold all data, bytes_written isn't useful here unless we + // allow partial write. + bool Write(const void* source, size_t length, size_t* bytes_written); + + // Returns the length of the data left in the buffer to read. + size_t GetLength() const; + + private: + // Ensures that there is enough capacity to write length bytes to the + // buffer. Returns false if it was unable to ensure that capacity due to an + // allocation error, or if it would surpass the configured maximum capacity. + bool EnsureCapacityToWrite(size_t length); + + // Increases the capacity to the given absolute size in bytes. Returns false + // if there was an allocation error, or it would surpass the configured + // maximum capacity. + bool IncreaseCapacityTo(size_t capacity); + + // Private workhorse for Read without the parameter validation or locking. + // When |destination| is NULL, it purely calculates the the bytes that would + // have been read. + // Note that the function doesn't advance the read cursor or modify the + // length. It is caller's responsibility to adjust |read_position_| and + // |length_| according to the return value, which is the actual number of + // bytes read. + void ReadUnchecked(void* destination, + size_t destination_length, + size_t source_offset, + size_t* bytes_read) const; + + // The same the as above functions except that it also advance the + // |read_position_| and adjust the |length_| accordingly. + void ReadAndAdvanceUnchecked(void* destination, + size_t destination_length, + size_t* bytes_read); + + // Gets a pointer to the current write position. + void* GetWritePointer() const; + + // Gets the current write position. + size_t GetWritePosition() const; + + const size_t max_capacity_; + void* buffer_; + size_t capacity_; + size_t length_; + size_t read_position_; + mutable base::Lock lock_; +}; + +} // namespace base + +#endif // BASE_CIRCULAR_BUFFER_SHELL_H_
diff --git a/src/base/circular_buffer_shell_unittest.cc b/src/base/circular_buffer_shell_unittest.cc new file mode 100644 index 0000000..0d4aa1c --- /dev/null +++ b/src/base/circular_buffer_shell_unittest.cc
@@ -0,0 +1,454 @@ +/* + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/circular_buffer_shell.h" + +#include <string.h> + +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// 100 characters, repeating every 16 characters. +const char kTestData[] = + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + "01234567890ABCDEF0123456789ABCDEF0123"; + +// 100 characters, repeating every 17 characters. +#define UNSET_DATA \ + "GHIJKLMNOPQRSTUVWGHIJKLMNOPQRSTUVWGHIJKLMNOPQRSTUVW" \ + "GHIJKLMNOPQRSTUVWGHIJKLMNOPQRSTUVWGHIJKLMNOPQRSTU" + +const char kUnsetData[] = UNSET_DATA; +const size_t kUnsetSize = 1024; + +// Like memcmp, but reports which index and values failed. +void IsSame(const char* expected, const char* actual, size_t length) { + for (size_t i = 0; i < length; ++i) { + if (expected[i] != actual[i]) { + EXPECT_EQ(expected[i], actual[i]) << "at " << i; + return; + } + } +} + +size_t read_pos = 0; +size_t write_pos = 0; + +// If the test uses testWrite and TestRead, then it needs to call ClearPos to +// avoid contamination from previous tests. +void ClearPos() { + read_pos = 0; + write_pos = 0; +} + +void TestWrite(base::CircularBufferShell* circular_buffer, size_t to_write) { + size_t before_length = circular_buffer->GetLength(); + size_t bytes_written = kUnsetSize; + bool result = + circular_buffer->Write(kTestData + write_pos, to_write, &bytes_written); + EXPECT_EQ(true, result); + EXPECT_EQ(to_write, bytes_written); + EXPECT_EQ(before_length + to_write, circular_buffer->GetLength()); + write_pos += to_write; +} + +void TestRead(base::CircularBufferShell* circular_buffer, size_t to_read) { + size_t before_length = circular_buffer->GetLength(); + char data[] = UNSET_DATA UNSET_DATA; + char* buffer = data + strlen(kUnsetData); + size_t bytes_read = kUnsetSize; + circular_buffer->Read(buffer, to_read, &bytes_read); + EXPECT_EQ(to_read, bytes_read); + EXPECT_EQ(before_length - to_read, circular_buffer->GetLength()); + IsSame(kTestData + read_pos, buffer, to_read); + IsSame(kUnsetData + to_read, buffer + to_read, to_read); + IsSame(kUnsetData + strlen(kUnsetData) - to_read, + buffer - to_read, to_read); + read_pos += to_read; +} + +} // namespace + +// --- Sunny Day Tests --- + +TEST(CircularBufferShellTest, Construct) { + ClearPos(); + scoped_ptr<base::CircularBufferShell> circular_buffer( + new base::CircularBufferShell(20)); +} + +TEST(CircularBufferShellTest, SimpleWriteAndRead) { + ClearPos(); + scoped_ptr<base::CircularBufferShell> circular_buffer( + new base::CircularBufferShell(20)); + + TestWrite(circular_buffer.get(), 15); + TestRead(circular_buffer.get(), 5); + TestRead(circular_buffer.get(), 4); + TestRead(circular_buffer.get(), 3); + TestRead(circular_buffer.get(), 2); + TestRead(circular_buffer.get(), 1); +} + +TEST(CircularBufferShellTest, ReadWriteOverBoundary) { + ClearPos(); + scoped_ptr<base::CircularBufferShell> circular_buffer( + new base::CircularBufferShell(20)); + + // Fill the buffer. + TestWrite(circular_buffer.get(), 20); + + // Read half the data from the front. + TestRead(circular_buffer.get(), 10); + + // Fill the back half, making the data wrap around the end. + TestWrite(circular_buffer.get(), 10); + + // Read the whole thing, which should require two memcpys. + TestRead(circular_buffer.get(), 20); + + // Fill the buffer, again around the end, should require two memcpys. + TestWrite(circular_buffer.get(), 20); + + // Read the buffer to verify, should again require two memcpys. + TestRead(circular_buffer.get(), 20); +} + +TEST(CircularBufferShellTest, ExpandWhileNotWrapped) { + ClearPos(); + scoped_ptr<base::CircularBufferShell> circular_buffer( + new base::CircularBufferShell(20)); + + // Set the size with the first write. + TestWrite(circular_buffer.get(), 5); + + // Expand with the second write. + TestWrite(circular_buffer.get(), 5); + + // Read to verify the data is intact + TestRead(circular_buffer.get(), 10); +} + +TEST(CircularBufferShellTest, ExpandWhileNotWrapped2) { + ClearPos(); + scoped_ptr<base::CircularBufferShell> circular_buffer( + new base::CircularBufferShell(20)); + + // Set the size with the first write. + TestWrite(circular_buffer.get(), 5); + + // Read a couple out so that the data doesn't start at the beginning of the + // buffer. + TestRead(circular_buffer.get(), 2); + + // Expand with the second write. + TestWrite(circular_buffer.get(), 5); + + // Read to verify the data is intact + TestRead(circular_buffer.get(), 8); +} + +TEST(CircularBufferShellTest, ExpandWhileWrapped) { + ClearPos(); + scoped_ptr<base::CircularBufferShell> circular_buffer( + new base::CircularBufferShell(20)); + + // Set the size with the first write. + TestWrite(circular_buffer.get(), 10); + + // Read front half. + TestRead(circular_buffer.get(), 5); + + // Wrap with second write + TestWrite(circular_buffer.get(), 5); + + // Write again to expand while wrapped. + TestWrite(circular_buffer.get(), 5); + + // Read to verify the data is intact + TestRead(circular_buffer.get(), 15); +} + +// --- Rainy Day Tests --- + +TEST(CircularBufferShellTest, WriteTooMuch) { + ClearPos(); + scoped_ptr<base::CircularBufferShell> circular_buffer( + new base::CircularBufferShell(20)); + + { + size_t bytes_written = kUnsetSize; + bool result = circular_buffer->Write(kTestData, 25, &bytes_written); + EXPECT_EQ(false, result); + EXPECT_EQ(kUnsetSize, bytes_written); + EXPECT_EQ(0, circular_buffer->GetLength()); + } +} + +TEST(CircularBufferShellTest, ReadEmpty) { + ClearPos(); + scoped_ptr<base::CircularBufferShell> circular_buffer( + new base::CircularBufferShell(20)); + + EXPECT_EQ(circular_buffer->GetLength(), 0); + + { + char buffer[] = UNSET_DATA; + size_t bytes_read = kUnsetSize; + circular_buffer->Read(buffer, 0, &bytes_read); + EXPECT_EQ(0, bytes_read); + EXPECT_EQ(0, circular_buffer->GetLength()); + IsSame(kUnsetData, buffer, 10); + } + + { + char buffer[] = UNSET_DATA; + size_t bytes_read = kUnsetSize; + circular_buffer->Read(buffer, 10, &bytes_read); + EXPECT_EQ(0, bytes_read); + EXPECT_EQ(0, circular_buffer->GetLength()); + IsSame(kUnsetData, buffer, 10); + } +} + +TEST(CircularBufferShellTest, ReadToNull) { + ClearPos(); + scoped_ptr<base::CircularBufferShell> circular_buffer( + new base::CircularBufferShell(20)); + + { + size_t bytes_read = kUnsetSize; + circular_buffer->Read(NULL, 0, &bytes_read); + EXPECT_EQ(0, bytes_read); + } +} + +TEST(CircularBufferShellTest, Peek) { + const size_t kMaxCapacity = 20; + base::CircularBufferShell circular_buffer(kMaxCapacity); + size_t bytes_peeked; + size_t peek_offset = 0; + + circular_buffer.Write(kTestData, kMaxCapacity, NULL); + + // Peek with offset 0. + { + char destination[] = UNSET_DATA; + circular_buffer.Peek(destination + 9, 10, peek_offset, &bytes_peeked); + + EXPECT_EQ(10, bytes_peeked); + IsSame(UNSET_DATA, destination, 9); + IsSame(UNSET_DATA + 9 + bytes_peeked, + destination + 9 + bytes_peeked, + sizeof(UNSET_DATA) - 9 - bytes_peeked); + IsSame(kTestData, destination + 9, 10); + peek_offset += bytes_peeked; + } + + // Peek with non-zero offset. + { + char destination[] = UNSET_DATA; + circular_buffer.Peek(destination + 9, 7, peek_offset, &bytes_peeked); + + EXPECT_EQ(7, bytes_peeked); + IsSame(UNSET_DATA, destination, 9); + IsSame(UNSET_DATA + 9 + bytes_peeked, + destination + 9 + bytes_peeked, + sizeof(UNSET_DATA) - 9 - bytes_peeked); + IsSame(kTestData + peek_offset, destination + 9, bytes_peeked); + peek_offset += bytes_peeked; + } + + // Peek more data than available. + { + char destination[] = UNSET_DATA; + circular_buffer.Peek(destination + 9, 7, peek_offset, &bytes_peeked); + + EXPECT_EQ(3, bytes_peeked); + IsSame(UNSET_DATA, destination, 9); + IsSame(UNSET_DATA + 9 + bytes_peeked, + destination + 9 + bytes_peeked, + sizeof(UNSET_DATA) - 9 - bytes_peeked); + IsSame(kTestData + peek_offset, destination + 9, bytes_peeked); + peek_offset += bytes_peeked; + } + + // Peek an empty buffer. + { + char destination[] = UNSET_DATA; + circular_buffer.Peek(destination + 9, 7, peek_offset, &bytes_peeked); + + IsSame(UNSET_DATA, destination, sizeof(destination)); + EXPECT_EQ(0, bytes_peeked); + // Verify that we are actually peeking instead of reading. + EXPECT_EQ(kMaxCapacity, circular_buffer.GetLength()); + } +} + +TEST(CircularBufferShellTest, Skip) { + const size_t kMaxCapacity = 20; + base::CircularBufferShell circular_buffer(kMaxCapacity); + char destination[] = UNSET_DATA UNSET_DATA; + size_t bytes; + + circular_buffer.Write(kTestData, kMaxCapacity, NULL); + circular_buffer.Skip(10, &bytes); + EXPECT_EQ(10, bytes); + EXPECT_EQ(kMaxCapacity - 10, circular_buffer.GetLength()); + + circular_buffer.Read(destination, kMaxCapacity, &bytes); + + EXPECT_EQ(kMaxCapacity - 10, bytes); + IsSame(kTestData + 10, destination, bytes); +} + +TEST(CircularBufferShellTest, PeekWrapped) { + const size_t kMaxCapacity = 20; + base::CircularBufferShell circular_buffer(kMaxCapacity); + char destination[] = UNSET_DATA; + size_t bytes_peeked; + + circular_buffer.Write(kTestData, kMaxCapacity, NULL); + + // Skip 10 bytes to free some space. + circular_buffer.Skip(10, &bytes_peeked); + + // Fill the free space with new data. + circular_buffer.Write(kTestData + kMaxCapacity, 10, NULL); + EXPECT_EQ(kMaxCapacity, circular_buffer.GetLength()); + + // Peek with a non-zero offset. + circular_buffer.Peek(destination, kMaxCapacity, 5, &bytes_peeked); + + EXPECT_EQ(kMaxCapacity - 5, bytes_peeked); + IsSame(kTestData + 15, destination, bytes_peeked); +} + +TEST(CircularBufferShellTest, SkipWrapped) { + const size_t kMaxCapacity = 20; + base::CircularBufferShell circular_buffer(kMaxCapacity); + char destination[] = UNSET_DATA; + size_t bytes; + + circular_buffer.Write(kTestData, kMaxCapacity, NULL); + circular_buffer.Skip(10, &bytes); + + circular_buffer.Write(kTestData + kMaxCapacity, 10, NULL); + EXPECT_EQ(kMaxCapacity, circular_buffer.GetLength()); + + circular_buffer.Skip(kMaxCapacity - 5, NULL); + EXPECT_EQ(5, circular_buffer.GetLength()); + + circular_buffer.Read(destination, 5, &bytes); + EXPECT_EQ(5, bytes); + IsSame(kTestData + kMaxCapacity + 5, destination, 5); +} + +// --- Legacy Tests --- + +TEST(CircularBufferShellTest, Basic) { + const int max_buffer_length = 10; + const int kReadSize1 = 4; + const int kReadSize2 = 2; + const int kReadSize3 = 4; + const int kReadSize4 = 6; + + // Create a Circular Buffer. + scoped_ptr<base::CircularBufferShell> circular_buffer( + new base::CircularBufferShell(max_buffer_length)); + ASSERT_TRUE(circular_buffer); + EXPECT_EQ(circular_buffer->GetLength(), 0); + + char read_buffer[20]; + size_t bytes_read = 0; + size_t bytes_written = 0; + // Read 4 bytes, got read_pos 0, write_pos 0 + circular_buffer->Read(read_buffer, kReadSize1, &bytes_read); + EXPECT_EQ(bytes_read, 0); + + // Write 5 bytes, got read_pos 0, write_pos 5 + const char write_buffer[] = "hello"; + circular_buffer->Write(write_buffer, strlen(write_buffer), &bytes_written); + EXPECT_EQ(bytes_written, strlen(write_buffer)); + EXPECT_EQ(circular_buffer->GetLength(), strlen(write_buffer)); + + // Write 1 byte, increased buffer size to 10, read_pos 0, write_pos 6 + const char write_buffer2[] = " "; + circular_buffer->Write(write_buffer2, strlen(write_buffer2), &bytes_written); + EXPECT_EQ(bytes_written, strlen(write_buffer2)); + EXPECT_EQ(circular_buffer->GetLength(), + strlen(write_buffer) + strlen(write_buffer2)); + + // Read 2 bytes, got read_pos 2, write_pos 6 + circular_buffer->Read(read_buffer, kReadSize2, &bytes_read); + EXPECT_EQ(0, memcmp(read_buffer, "he", kReadSize2)); + EXPECT_EQ(bytes_read, kReadSize2); + EXPECT_EQ(circular_buffer->GetLength(), + strlen(write_buffer) + strlen(write_buffer2) - kReadSize2); + + // Write 6 bytes, got read_pos 2, write_pos 2, full of data + const char write_buffer3[] = "world!"; + circular_buffer->Write(write_buffer3, strlen(write_buffer3), &bytes_written); + EXPECT_EQ(bytes_written, strlen(write_buffer3)); + EXPECT_EQ(circular_buffer->GetLength(), + strlen(write_buffer) + strlen(write_buffer2) + + strlen(write_buffer3) - kReadSize2); + + // Read 4 bytes, got read_pos 6, write_pos 2 + circular_buffer->Read(read_buffer, kReadSize3, &bytes_read); + EXPECT_EQ(bytes_read, kReadSize3); + EXPECT_EQ(0, memcmp(read_buffer, "llo ", kReadSize3)); + EXPECT_EQ(circular_buffer->GetLength(), + strlen(write_buffer) + strlen(write_buffer2) + + strlen(write_buffer3) - kReadSize2 - kReadSize3); + + // Read 6 bytes, got read_pos 2, write_pos 2, empty + circular_buffer->Read(read_buffer, kReadSize4, &bytes_read); + EXPECT_EQ(bytes_read, kReadSize4); + EXPECT_EQ(0, memcmp(read_buffer, "world!", kReadSize4)); + EXPECT_EQ(circular_buffer->GetLength(), 0); +} + +TEST(CircularBufferShellTest, CycleReadWrite) { + const int max_buffer_length = 5000; + // Create a Circular Buffer. + scoped_ptr<base::CircularBufferShell> circular_buffer( + new base::CircularBufferShell(max_buffer_length)); + ASSERT_TRUE(circular_buffer); + EXPECT_EQ(circular_buffer->GetLength(), 0); + + size_t bytes_written = 0; + size_t bytes_read = 0; + char write_buffer[500]; + char read_buffer[2000]; + + for (int i = 0; i < 50; ++i) { + circular_buffer->Write(write_buffer, sizeof(write_buffer), &bytes_written); + EXPECT_EQ(bytes_written, sizeof(write_buffer)); + EXPECT_EQ(circular_buffer->GetLength(), sizeof(write_buffer)); + + circular_buffer->Write(write_buffer, sizeof(write_buffer), &bytes_written); + EXPECT_EQ(bytes_written, sizeof(write_buffer)); + EXPECT_EQ(circular_buffer->GetLength(), + sizeof(write_buffer) + sizeof(write_buffer)); + + circular_buffer->Read(read_buffer, sizeof(read_buffer), &bytes_read); + EXPECT_EQ(bytes_read, sizeof(write_buffer) + sizeof(write_buffer)); + EXPECT_EQ(circular_buffer->GetLength(), 0); + } +}
diff --git a/src/base/command_line.cc b/src/base/command_line.cc new file mode 100644 index 0000000..66fe11a --- /dev/null +++ b/src/base/command_line.cc
@@ -0,0 +1,416 @@ +// 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/command_line.h" + +#include <algorithm> +#include <ostream> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/string_split.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#include <shellapi.h> +#endif + +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) +const CommandLine::CharType* const kSwitchPrefixes[] = {L"--", L"-", L"/"}; +#elif defined(OS_POSIX) || defined(OS_STARBOARD) +// Unixes don't use slash as a switch. +const CommandLine::CharType* const kSwitchPrefixes[] = {"--", "-"}; +#endif + +size_t GetSwitchPrefixLength(const CommandLine::StringType& string) { + for (size_t i = 0; i < arraysize(kSwitchPrefixes); ++i) { + CommandLine::StringType prefix(kSwitchPrefixes[i]); + if (string.compare(0, prefix.length(), prefix) == 0) + return prefix.length(); + } + return 0; +} + +// Fills in |switch_string| and |switch_value| if |string| is a switch. +// This will preserve the input switch prefix in the output |switch_string|. +bool IsSwitch(const CommandLine::StringType& string, + CommandLine::StringType* switch_string, + CommandLine::StringType* switch_value) { + switch_string->clear(); + switch_value->clear(); + size_t prefix_length = GetSwitchPrefixLength(string); + if (prefix_length == 0 || prefix_length == string.length()) + return false; + + const size_t equals_position = string.find(kSwitchValueSeparator); + *switch_string = string.substr(0, equals_position); + if (equals_position != CommandLine::StringType::npos) + *switch_value = string.substr(equals_position + 1); + return true; +} + +// Append switches and arguments, keeping switches before arguments. +void AppendSwitchesAndArguments(CommandLine& command_line, + const CommandLine::StringVector& argv) { + bool parse_switches = true; + for (size_t i = 1; i < argv.size(); ++i) { + CommandLine::StringType arg = argv[i]; + TrimWhitespace(arg, TRIM_ALL, &arg); + + CommandLine::StringType switch_string; + CommandLine::StringType switch_value; + parse_switches &= (arg != kSwitchTerminator); + if (parse_switches && IsSwitch(arg, &switch_string, &switch_value)) { +#if defined(OS_WIN) + command_line.AppendSwitchNative(WideToASCII(switch_string), switch_value); +#elif defined(OS_POSIX) || defined(OS_STARBOARD) + command_line.AppendSwitchNative(switch_string, switch_value); +#endif + } else { + command_line.AppendArgNative(arg); + } + } +} + +// Lowercase switches for backwards compatiblity *on Windows*. +std::string LowerASCIIOnWindows(const std::string& string) { +#if defined(OS_WIN) + return StringToLowerASCII(string); +#elif defined(OS_POSIX) || defined(OS_STARBOARD) + return string; +#endif +} + + +#if defined(OS_WIN) +// Quote a string as necessary for CommandLineToArgvW compatiblity *on Windows*. +std::wstring QuoteForCommandLineToArgvW(const std::wstring& arg) { + // We follow the quoting rules of CommandLineToArgvW. + // http://msdn.microsoft.com/en-us/library/17w5ykft.aspx + if (arg.find_first_of(L" \\\"") == std::wstring::npos) { + // No quoting necessary. + return arg; + } + + std::wstring out; + out.push_back(L'"'); + for (size_t i = 0; i < arg.size(); ++i) { + if (arg[i] == '\\') { + // Find the extent of this run of backslashes. + size_t start = i, end = start + 1; + for (; end < arg.size() && arg[end] == '\\'; ++end) + /* empty */; + size_t backslash_count = end - start; + + // Backslashes are escapes only if the run is followed by a double quote. + // Since we also will end the string with a double quote, we escape for + // either a double quote or the end of the string. + if (end == arg.size() || arg[end] == '"') { + // To quote, we need to output 2x as many backslashes. + backslash_count *= 2; + } + for (size_t j = 0; j < backslash_count; ++j) + out.push_back('\\'); + + // Advance i to one before the end to balance i++ in loop. + i = end - 1; + } else if (arg[i] == '"') { + out.push_back('\\'); + out.push_back('"'); + } else { + out.push_back(arg[i]); + } + } + out.push_back('"'); + + return out; +} +#endif + +} // namespace + +CommandLine::CommandLine(NoProgram no_program) + : argv_(1), + begin_args_(1) { +} + +CommandLine::CommandLine(const FilePath& program) + : argv_(1), + begin_args_(1) { + SetProgram(program); +} + +CommandLine::CommandLine(int argc, const CommandLine::CharType* const* argv) + : argv_(1), + begin_args_(1) { + InitFromArgv(argc, argv); +} + +CommandLine::CommandLine(const StringVector& argv) + : argv_(1), + begin_args_(1) { + InitFromArgv(argv); +} + +CommandLine::~CommandLine() { +} + +// static +bool CommandLine::Init(int argc, const char* const* argv) { + if (current_process_commandline_) { + // If this is intentional, Reset() must be called first. If we are using + // the shared build mode, we have to share a single object across multiple + // shared libraries. + return false; + } + + current_process_commandline_ = new CommandLine(NO_PROGRAM); +#if defined(OS_WIN) + current_process_commandline_->ParseFromString(::GetCommandLineW()); +#elif defined(OS_POSIX) || defined(OS_STARBOARD) + current_process_commandline_->InitFromArgv(argc, argv); +#endif + + return true; +} + +// static +void CommandLine::Reset() { + DCHECK(current_process_commandline_); + delete current_process_commandline_; + current_process_commandline_ = NULL; +} + +// static +CommandLine* CommandLine::ForCurrentProcess() { + DCHECK(current_process_commandline_); + return current_process_commandline_; +} + +#if defined(OS_WIN) +// static +CommandLine CommandLine::FromString(const std::wstring& command_line) { + CommandLine cmd(NO_PROGRAM); + cmd.ParseFromString(command_line); + return cmd; +} +#endif + +void CommandLine::InitFromArgv(int argc, + const CommandLine::CharType* const* argv) { + StringVector new_argv; + for (int i = 0; i < argc; ++i) + new_argv.push_back(argv[i]); + InitFromArgv(new_argv); +} + +void CommandLine::InitFromArgv(const StringVector& argv) { + argv_ = StringVector(1); + begin_args_ = 1; + SetProgram(argv.empty() ? FilePath() : FilePath(argv[0])); + AppendSwitchesAndArguments(*this, argv); +} + +CommandLine::StringType CommandLine::GetCommandLineString() const { + StringType string(argv_[0]); +#if defined(OS_WIN) + string = QuoteForCommandLineToArgvW(string); +#endif + StringType params(GetArgumentsString()); + if (!params.empty()) { + string.append(StringType(FILE_PATH_LITERAL(" "))); + string.append(params); + } + return string; +} + +CommandLine::StringType CommandLine::GetArgumentsString() const { + StringType params; + // Append switches and arguments. + bool parse_switches = true; + for (size_t i = 1; i < argv_.size(); ++i) { + StringType arg = argv_[i]; + StringType switch_string; + StringType switch_value; + parse_switches &= arg != kSwitchTerminator; + if (i > 1) + params.append(StringType(FILE_PATH_LITERAL(" "))); + if (parse_switches && IsSwitch(arg, &switch_string, &switch_value)) { + params.append(switch_string); + if (!switch_value.empty()) { +#if defined(OS_WIN) + switch_value = QuoteForCommandLineToArgvW(switch_value); +#endif + params.append(kSwitchValueSeparator + switch_value); + } + } + else { +#if defined(OS_WIN) + arg = QuoteForCommandLineToArgvW(arg); +#endif + params.append(arg); + } + } + return params; +} + +FilePath CommandLine::GetProgram() const { + return FilePath(argv_[0]); +} + +void CommandLine::SetProgram(const FilePath& program) { + TrimWhitespace(program.value(), TRIM_ALL, &argv_[0]); +} + +bool CommandLine::HasSwitch(const std::string& switch_string) const { + return switches_.find(LowerASCIIOnWindows(switch_string)) != switches_.end(); +} + +std::string CommandLine::GetSwitchValueASCII( + const std::string& switch_string) const { + StringType value = GetSwitchValueNative(switch_string); + if (!IsStringASCII(value)) { + DLOG(WARNING) << "Value of switch (" << switch_string << ") must be ASCII."; + return std::string(); + } +#if defined(OS_WIN) + return WideToASCII(value); +#else + return value; +#endif +} + +FilePath CommandLine::GetSwitchValuePath( + const std::string& switch_string) const { + return FilePath(GetSwitchValueNative(switch_string)); +} + +CommandLine::StringType CommandLine::GetSwitchValueNative( + const std::string& switch_string) const { + SwitchMap::const_iterator result = switches_.end(); + result = switches_.find(LowerASCIIOnWindows(switch_string)); + return result == switches_.end() ? StringType() : result->second; +} + +void CommandLine::AppendSwitch(const std::string& switch_string) { + AppendSwitchNative(switch_string, StringType()); +} + +void CommandLine::AppendSwitchPath(const std::string& switch_string, + const FilePath& path) { + AppendSwitchNative(switch_string, path.value()); +} + +void CommandLine::AppendSwitchNative(const std::string& switch_string, + const CommandLine::StringType& value) { + std::string switch_key(LowerASCIIOnWindows(switch_string)); +#if defined(OS_WIN) + StringType combined_switch_string(ASCIIToWide(switch_key)); +#elif defined(OS_POSIX) || defined(OS_STARBOARD) + StringType combined_switch_string(switch_string); +#endif + size_t prefix_length = GetSwitchPrefixLength(combined_switch_string); + switches_[switch_key.substr(prefix_length)] = value; + // Preserve existing switch prefixes in |argv_|; only append one if necessary. + if (prefix_length == 0) + combined_switch_string = kSwitchPrefixes[0] + combined_switch_string; + if (!value.empty()) + combined_switch_string += kSwitchValueSeparator + value; + // Append the switch and update the switches/arguments divider |begin_args_|. + argv_.insert(argv_.begin() + begin_args_++, combined_switch_string); +} + +void CommandLine::AppendSwitchASCII(const std::string& switch_string, + const std::string& value_string) { +#if defined(OS_WIN) + AppendSwitchNative(switch_string, ASCIIToWide(value_string)); +#elif defined(OS_POSIX) || defined(OS_STARBOARD) + AppendSwitchNative(switch_string, value_string); +#endif +} + +void CommandLine::CopySwitchesFrom(const CommandLine& source, + const char* const switches[], + size_t count) { + for (size_t i = 0; i < count; ++i) { + if (source.HasSwitch(switches[i])) + AppendSwitchNative(switches[i], source.GetSwitchValueNative(switches[i])); + } +} + +CommandLine::StringVector CommandLine::GetArgs() const { + // Gather all arguments after the last switch (may include kSwitchTerminator). + StringVector args(argv_.begin() + begin_args_, argv_.end()); + // Erase only the first kSwitchTerminator (maybe "--" is a legitimate page?) + StringVector::iterator switch_terminator = + std::find(args.begin(), args.end(), kSwitchTerminator); + if (switch_terminator != args.end()) + args.erase(switch_terminator); + return args; +} + +void CommandLine::AppendArg(const std::string& value) { +#if defined(OS_WIN) + DCHECK(IsStringUTF8(value)); + AppendArgNative(UTF8ToWide(value)); +#elif defined(OS_POSIX) || defined(OS_STARBOARD) + AppendArgNative(value); +#endif +} + +void CommandLine::AppendArgPath(const FilePath& path) { + AppendArgNative(path.value()); +} + +void CommandLine::AppendArgNative(const CommandLine::StringType& value) { + argv_.push_back(value); +} + +void CommandLine::AppendArguments(const CommandLine& other, + bool include_program) { + if (include_program) + SetProgram(other.GetProgram()); + AppendSwitchesAndArguments(*this, other.argv()); +} + +void CommandLine::PrependWrapper(const CommandLine::StringType& wrapper) { + if (wrapper.empty()) + return; + // The wrapper may have embedded arguments (like "gdb --args"). In this case, + // we don't pretend to do anything fancy, we just split on spaces. + StringVector wrapper_argv; + base::SplitString(wrapper, FILE_PATH_LITERAL(' '), &wrapper_argv); + // Prepend the wrapper and update the switches/arguments |begin_args_|. + argv_.insert(argv_.begin(), wrapper_argv.begin(), wrapper_argv.end()); + begin_args_ += wrapper_argv.size(); +} + +#if defined(OS_WIN) +void CommandLine::ParseFromString(const std::wstring& command_line) { + std::wstring command_line_string; + TrimWhitespace(command_line, TRIM_ALL, &command_line_string); + if (command_line_string.empty()) + return; + + int num_args = 0; + wchar_t** args = NULL; + args = ::CommandLineToArgvW(command_line_string.c_str(), &num_args); + + DPLOG_IF(FATAL, !args) << "CommandLineToArgvW failed on command line: " + << command_line; + InitFromArgv(num_args, args); + LocalFree(args); +} +#endif
diff --git a/src/base/command_line.h b/src/base/command_line.h new file mode 100644 index 0000000..fcb7032 --- /dev/null +++ b/src/base/command_line.h
@@ -0,0 +1,172 @@ +// 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. + +// This class works with command lines: building and parsing. +// Arguments with prefixes ('--', '-', and on Windows, '/') are switches. +// Switches will precede all other arguments without switch prefixes. +// Switches can optionally have values, delimited by '=', e.g., "-switch=value". +// An argument of "--" will terminate switch parsing during initialization, +// interpreting subsequent tokens as non-switch arguments, regardless of prefix. + +// There is a singleton read-only CommandLine that represents the command line +// that the current process was started with. It must be initialized in main(). + +#ifndef BASE_COMMAND_LINE_H_ +#define BASE_COMMAND_LINE_H_ + +#include <stddef.h> +#include <map> +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "build/build_config.h" + +class FilePath; + +class BASE_EXPORT CommandLine { + public: +#if defined(OS_WIN) + // The native command line string type. + typedef std::wstring StringType; +#elif defined(OS_POSIX) || defined(OS_STARBOARD) + typedef std::string StringType; +#endif + + typedef StringType::value_type CharType; + typedef std::vector<StringType> StringVector; + typedef std::map<std::string, StringType> SwitchMap; + + // A constructor for CommandLines that only carry switches and arguments. + enum NoProgram { NO_PROGRAM }; + explicit CommandLine(NoProgram no_program); + + // Construct a new command line with |program| as argv[0]. + explicit CommandLine(const FilePath& program); + + // Construct a new command line from an argument list. + CommandLine(int argc, const CharType* const* argv); + explicit CommandLine(const StringVector& argv); + + ~CommandLine(); + + // 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 + // called to set up the command line. Returns false if initialization has + // already occurred, and true otherwise. Only the caller receiving a 'true' + // return value should take responsibility for calling Reset. + static bool Init(int argc, const char* const* argv); + + // Destroys the current process CommandLine singleton. This is necessary if + // you want to reset the base library to its initial state (for example, in an + // outer library that needs to be able to terminate, and be re-initialized). + // If Init is called only once, as in main(), Reset() is not necessary. + static void Reset(); + + // Get the singleton CommandLine representing the current process's + // command line. Note: returned value is mutable, but not thread safe; + // only mutate if you know what you're doing! + static CommandLine* ForCurrentProcess(); + +#if defined(OS_WIN) + static CommandLine FromString(const std::wstring& command_line); +#endif + + // Initialize from an argv vector. + void InitFromArgv(int argc, const CharType* const* argv); + void InitFromArgv(const StringVector& argv); + + // Constructs and returns the represented command line string. + // CAUTION! This should be avoided on POSIX because quoting behavior is + // unclear. + StringType GetCommandLineString() const; + + // Constructs and returns the represented arguments string. + // CAUTION! This should be avoided on POSIX because quoting behavior is + // unclear. + StringType GetArgumentsString() const; + + // Returns the original command line string as a vector of strings. + const StringVector& argv() const { return argv_; } + + // Get and Set the program part of the command line string (the first item). + FilePath GetProgram() const; + void SetProgram(const FilePath& program); + + // Returns true if this command line contains the given switch. + // (Switch names are case-insensitive). + bool HasSwitch(const std::string& switch_string) const; + + // Returns the value associated with the given switch. If the switch has no + // value or isn't present, this method returns the empty string. + std::string GetSwitchValueASCII(const std::string& switch_string) const; + FilePath GetSwitchValuePath(const std::string& switch_string) const; + StringType GetSwitchValueNative(const std::string& switch_string) const; + + // Get a copy of all switches, along with their values. + const SwitchMap& GetSwitches() const { return switches_; } + + // Append a switch [with optional value] to the command line. + // Note: Switches will precede arguments regardless of appending order. + void AppendSwitch(const std::string& switch_string); + void AppendSwitchPath(const std::string& switch_string, const FilePath& path); + void AppendSwitchNative(const std::string& switch_string, + const StringType& value); + void AppendSwitchASCII(const std::string& switch_string, + const std::string& value); + + // Copy a set of switches (and any values) from another command line. + // Commonly used when launching a subprocess. + void CopySwitchesFrom(const CommandLine& source, + const char* const switches[], + size_t count); + + // Get the remaining arguments to the command. + StringVector GetArgs() const; + + // Append an argument to the command line. Note that the argument is quoted + // properly such that it is interpreted as one argument to the target command. + // AppendArg is primarily for ASCII; non-ASCII input is interpreted as UTF-8. + // Note: Switches will precede arguments regardless of appending order. + void AppendArg(const std::string& value); + void AppendArgPath(const FilePath& value); + void AppendArgNative(const StringType& value); + + // Append the switches and arguments from another command line to this one. + // If |include_program| is true, include |other|'s program as well. + void AppendArguments(const CommandLine& other, bool include_program); + + // Insert a command before the current command. + // Common for debuggers, like "valgrind" or "gdb --args". + void PrependWrapper(const StringType& wrapper); + +#if defined(OS_WIN) + // Initialize by parsing the given command line string. + // The program name is assumed to be the first item in the string. + void ParseFromString(const std::wstring& command_line); +#endif + + private: + // Disallow default constructor; a program name must be explicitly specified. + CommandLine(); + // Allow the copy constructor. A common pattern is to copy of the current + // process's command line and then add some flags to it. For example: + // CommandLine cl(*CommandLine::ForCurrentProcess()); + // cl.AppendSwitch(...); + + // The singleton CommandLine representing the current process's command line. + static CommandLine* current_process_commandline_; + + // The argv array: { program, [(--|-|/)switch[=value]]*, [--], [argument]* } + StringVector argv_; + + // Parsed-out switch keys and values. + SwitchMap switches_; + + // The index after the program and switches, any arguments start here. + size_t begin_args_; +}; + +#endif // BASE_COMMAND_LINE_H_
diff --git a/src/base/command_line_unittest.cc b/src/base/command_line_unittest.cc new file mode 100644 index 0000000..b9ee650 --- /dev/null +++ b/src/base/command_line_unittest.cc
@@ -0,0 +1,358 @@ +// 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 <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/file_path.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +// To test Windows quoting behavior, we use a string that has some backslashes +// and quotes. +// Consider the command-line argument: q\"bs1\bs2\\bs3q\\\" +// Here it is with C-style escapes. +static const CommandLine::StringType kTrickyQuoted = + FILE_PATH_LITERAL("q\\\"bs1\\bs2\\\\bs3q\\\\\\\""); +// It should be parsed by Windows as: q"bs1\bs2\\bs3q\" +// Here that is with C-style escapes. +static const CommandLine::StringType kTricky = + FILE_PATH_LITERAL("q\"bs1\\bs2\\\\bs3q\\\""); + +TEST(CommandLineTest, CommandLineConstructor) { + const CommandLine::CharType* argv[] = { + FILE_PATH_LITERAL("program"), + FILE_PATH_LITERAL("--foo="), + FILE_PATH_LITERAL("-bAr"), + FILE_PATH_LITERAL("-spaetzel=pierogi"), + FILE_PATH_LITERAL("-baz"), + FILE_PATH_LITERAL("flim"), + FILE_PATH_LITERAL("--other-switches=--dog=canine --cat=feline"), + FILE_PATH_LITERAL("-spaetzle=Crepe"), + FILE_PATH_LITERAL("-=loosevalue"), + FILE_PATH_LITERAL("-"), + FILE_PATH_LITERAL("FLAN"), + FILE_PATH_LITERAL("a"), + FILE_PATH_LITERAL("--input-translation=45--output-rotation"), + FILE_PATH_LITERAL("--"), + FILE_PATH_LITERAL("--"), + FILE_PATH_LITERAL("--not-a-switch"), + FILE_PATH_LITERAL("\"in the time of submarines...\""), + FILE_PATH_LITERAL("unquoted arg-with-space")}; + CommandLine cl(arraysize(argv), argv); + + EXPECT_FALSE(cl.GetCommandLineString().empty()); + EXPECT_FALSE(cl.HasSwitch("cruller")); + EXPECT_FALSE(cl.HasSwitch("flim")); + EXPECT_FALSE(cl.HasSwitch("program")); + EXPECT_FALSE(cl.HasSwitch("dog")); + EXPECT_FALSE(cl.HasSwitch("cat")); + EXPECT_FALSE(cl.HasSwitch("output-rotation")); + EXPECT_FALSE(cl.HasSwitch("not-a-switch")); + EXPECT_FALSE(cl.HasSwitch("--")); + + EXPECT_EQ(FilePath(FILE_PATH_LITERAL("program")).value(), + cl.GetProgram().value()); + + EXPECT_TRUE(cl.HasSwitch("foo")); + EXPECT_TRUE(cl.HasSwitch("bAr")); + EXPECT_TRUE(cl.HasSwitch("baz")); + EXPECT_TRUE(cl.HasSwitch("spaetzle")); +#if defined(OS_WIN) + EXPECT_TRUE(cl.HasSwitch("SPAETZLE")); +#endif + EXPECT_TRUE(cl.HasSwitch("other-switches")); + EXPECT_TRUE(cl.HasSwitch("input-translation")); + + EXPECT_EQ("Crepe", cl.GetSwitchValueASCII("spaetzle")); + EXPECT_EQ("", cl.GetSwitchValueASCII("Foo")); + EXPECT_EQ("", cl.GetSwitchValueASCII("bar")); + EXPECT_EQ("", cl.GetSwitchValueASCII("cruller")); + EXPECT_EQ("--dog=canine --cat=feline", cl.GetSwitchValueASCII( + "other-switches")); + EXPECT_EQ("45--output-rotation", cl.GetSwitchValueASCII("input-translation")); + + const CommandLine::StringVector& args = cl.GetArgs(); + ASSERT_EQ(8U, args.size()); + + std::vector<CommandLine::StringType>::const_iterator iter = args.begin(); + EXPECT_EQ(FILE_PATH_LITERAL("flim"), *iter); + ++iter; + EXPECT_EQ(FILE_PATH_LITERAL("-"), *iter); + ++iter; + EXPECT_EQ(FILE_PATH_LITERAL("FLAN"), *iter); + ++iter; + EXPECT_EQ(FILE_PATH_LITERAL("a"), *iter); + ++iter; + EXPECT_EQ(FILE_PATH_LITERAL("--"), *iter); + ++iter; + EXPECT_EQ(FILE_PATH_LITERAL("--not-a-switch"), *iter); + ++iter; + EXPECT_EQ(FILE_PATH_LITERAL("\"in the time of submarines...\""), *iter); + ++iter; + EXPECT_EQ(FILE_PATH_LITERAL("unquoted arg-with-space"), *iter); + ++iter; + EXPECT_TRUE(iter == args.end()); +} + +TEST(CommandLineTest, CommandLineFromString) { +#if defined(OS_WIN) + CommandLine cl = CommandLine::FromString( + L"program --foo= -bAr /Spaetzel=pierogi /Baz flim " + L"--other-switches=\"--dog=canine --cat=feline\" " + L"-spaetzle=Crepe -=loosevalue FLAN " + L"--input-translation=\"45\"--output-rotation " + L"--quotes=" + kTrickyQuoted + L" " + L"-- -- --not-a-switch " + L"\"in the time of submarines...\""); + + EXPECT_FALSE(cl.GetCommandLineString().empty()); + EXPECT_FALSE(cl.HasSwitch("cruller")); + EXPECT_FALSE(cl.HasSwitch("flim")); + EXPECT_FALSE(cl.HasSwitch("program")); + EXPECT_FALSE(cl.HasSwitch("dog")); + EXPECT_FALSE(cl.HasSwitch("cat")); + EXPECT_FALSE(cl.HasSwitch("output-rotation")); + EXPECT_FALSE(cl.HasSwitch("not-a-switch")); + EXPECT_FALSE(cl.HasSwitch("--")); + + EXPECT_EQ(FilePath(FILE_PATH_LITERAL("program")).value(), + cl.GetProgram().value()); + + EXPECT_TRUE(cl.HasSwitch("foo")); + EXPECT_TRUE(cl.HasSwitch("bar")); + EXPECT_TRUE(cl.HasSwitch("baz")); + EXPECT_TRUE(cl.HasSwitch("spaetzle")); + EXPECT_TRUE(cl.HasSwitch("SPAETZLE")); + EXPECT_TRUE(cl.HasSwitch("other-switches")); + EXPECT_TRUE(cl.HasSwitch("input-translation")); + EXPECT_TRUE(cl.HasSwitch("quotes")); + + EXPECT_EQ("Crepe", cl.GetSwitchValueASCII("spaetzle")); + EXPECT_EQ("", cl.GetSwitchValueASCII("Foo")); + EXPECT_EQ("", cl.GetSwitchValueASCII("bar")); + EXPECT_EQ("", cl.GetSwitchValueASCII("cruller")); + EXPECT_EQ("--dog=canine --cat=feline", cl.GetSwitchValueASCII( + "other-switches")); + EXPECT_EQ("45--output-rotation", cl.GetSwitchValueASCII("input-translation")); + EXPECT_EQ(kTricky, cl.GetSwitchValueNative("quotes")); + + const CommandLine::StringVector& args = cl.GetArgs(); + ASSERT_EQ(5U, args.size()); + + std::vector<CommandLine::StringType>::const_iterator iter = args.begin(); + EXPECT_EQ(FILE_PATH_LITERAL("flim"), *iter); + ++iter; + EXPECT_EQ(FILE_PATH_LITERAL("FLAN"), *iter); + ++iter; + EXPECT_EQ(FILE_PATH_LITERAL("--"), *iter); + ++iter; + EXPECT_EQ(FILE_PATH_LITERAL("--not-a-switch"), *iter); + ++iter; + EXPECT_EQ(FILE_PATH_LITERAL("in the time of submarines..."), *iter); + ++iter; + EXPECT_TRUE(iter == args.end()); + + // Check that a generated string produces an equivalent command line. + CommandLine cl_duplicate = CommandLine::FromString(cl.GetCommandLineString()); + EXPECT_EQ(cl.GetCommandLineString(), cl_duplicate.GetCommandLineString()); +#endif +} + +// Tests behavior with an empty input string. +TEST(CommandLineTest, EmptyString) { +#if defined(OS_WIN) + CommandLine cl_from_string = CommandLine::FromString(L""); + EXPECT_TRUE(cl_from_string.GetCommandLineString().empty()); + EXPECT_TRUE(cl_from_string.GetProgram().empty()); + EXPECT_EQ(1U, cl_from_string.argv().size()); + EXPECT_TRUE(cl_from_string.GetArgs().empty()); +#endif + CommandLine cl_from_argv(0, NULL); + EXPECT_TRUE(cl_from_argv.GetCommandLineString().empty()); + EXPECT_TRUE(cl_from_argv.GetProgram().empty()); + EXPECT_EQ(1U, cl_from_argv.argv().size()); + EXPECT_TRUE(cl_from_argv.GetArgs().empty()); +} + +TEST(CommandLineTest, GetArgumentsString) { + static const FilePath::CharType kPath1[] = + FILE_PATH_LITERAL("C:\\Some File\\With Spaces.ggg"); + static const FilePath::CharType kPath2[] = + FILE_PATH_LITERAL("C:\\no\\spaces.ggg"); + + static const char kFirstArgName[] = "first-arg"; + static const char kSecondArgName[] = "arg2"; + static const char kThirdArgName[] = "arg with space"; + static const char kFourthArgName[] = "nospace"; + + CommandLine cl(CommandLine::NO_PROGRAM); + cl.AppendSwitchPath(kFirstArgName, FilePath(kPath1)); + cl.AppendSwitchPath(kSecondArgName, FilePath(kPath2)); + cl.AppendArg(kThirdArgName); + cl.AppendArg(kFourthArgName); + +#if defined(OS_WIN) + CommandLine::StringType expected_first_arg(UTF8ToUTF16(kFirstArgName)); + CommandLine::StringType expected_second_arg(UTF8ToUTF16(kSecondArgName)); + CommandLine::StringType expected_third_arg(UTF8ToUTF16(kThirdArgName)); + CommandLine::StringType expected_fourth_arg(UTF8ToUTF16(kFourthArgName)); +#elif defined(OS_POSIX) || defined(OS_STARBOARD) + CommandLine::StringType expected_first_arg(kFirstArgName); + CommandLine::StringType expected_second_arg(kSecondArgName); + CommandLine::StringType expected_third_arg(kThirdArgName); + CommandLine::StringType expected_fourth_arg(kFourthArgName); +#endif + +#if defined(OS_WIN) +#define QUOTE_ON_WIN FILE_PATH_LITERAL("\"") +#else +#define QUOTE_ON_WIN FILE_PATH_LITERAL("") +#endif // OS_WIN + + CommandLine::StringType expected_str; + expected_str.append(FILE_PATH_LITERAL("--")) + .append(expected_first_arg) + .append(FILE_PATH_LITERAL("=")) + .append(QUOTE_ON_WIN) + .append(kPath1) + .append(QUOTE_ON_WIN) + .append(FILE_PATH_LITERAL(" ")) + .append(FILE_PATH_LITERAL("--")) + .append(expected_second_arg) + .append(FILE_PATH_LITERAL("=")) + .append(QUOTE_ON_WIN) + .append(kPath2) + .append(QUOTE_ON_WIN) + .append(FILE_PATH_LITERAL(" ")) + .append(QUOTE_ON_WIN) + .append(expected_third_arg) + .append(QUOTE_ON_WIN) + .append(FILE_PATH_LITERAL(" ")) + .append(expected_fourth_arg); + EXPECT_EQ(expected_str, cl.GetArgumentsString()); +} + +// Test methods for appending switches to a command line. +TEST(CommandLineTest, AppendSwitches) { + std::string switch1 = "switch1"; + std::string switch2 = "switch2"; + std::string value2 = "value"; + std::string switch3 = "switch3"; + std::string value3 = "a value with spaces"; + std::string switch4 = "switch4"; + std::string value4 = "\"a value with quotes\""; + std::string switch5 = "quotes"; + CommandLine::StringType value5 = kTricky; + + CommandLine cl(FilePath(FILE_PATH_LITERAL("Program"))); + + cl.AppendSwitch(switch1); + cl.AppendSwitchASCII(switch2, value2); + cl.AppendSwitchASCII(switch3, value3); + cl.AppendSwitchASCII(switch4, value4); + cl.AppendSwitchNative(switch5, value5); + + EXPECT_TRUE(cl.HasSwitch(switch1)); + EXPECT_TRUE(cl.HasSwitch(switch2)); + EXPECT_EQ(value2, cl.GetSwitchValueASCII(switch2)); + EXPECT_TRUE(cl.HasSwitch(switch3)); + EXPECT_EQ(value3, cl.GetSwitchValueASCII(switch3)); + EXPECT_TRUE(cl.HasSwitch(switch4)); + EXPECT_EQ(value4, cl.GetSwitchValueASCII(switch4)); + EXPECT_TRUE(cl.HasSwitch(switch5)); + EXPECT_EQ(value5, cl.GetSwitchValueNative(switch5)); + +#if defined(OS_WIN) + EXPECT_EQ(L"Program " + L"--switch1 " + L"--switch2=value " + L"--switch3=\"a value with spaces\" " + L"--switch4=\"\\\"a value with quotes\\\"\" " + L"--quotes=\"" + kTrickyQuoted + L"\"", + cl.GetCommandLineString()); +#endif +} + +TEST(CommandLineTest, AppendSwitchesDashDash) { + const CommandLine::CharType* raw_argv[] = { FILE_PATH_LITERAL("prog"), + FILE_PATH_LITERAL("--"), + FILE_PATH_LITERAL("--arg1") }; + CommandLine cl(arraysize(raw_argv), raw_argv); + + cl.AppendSwitch("switch1"); + cl.AppendSwitchASCII("switch2", "foo"); + + cl.AppendArg("--arg2"); + + EXPECT_EQ(FILE_PATH_LITERAL("prog --switch1 --switch2=foo -- --arg1 --arg2"), + cl.GetCommandLineString()); + CommandLine::StringVector cl_argv = cl.argv(); + EXPECT_EQ(FILE_PATH_LITERAL("prog"), cl_argv[0]); + EXPECT_EQ(FILE_PATH_LITERAL("--switch1"), cl_argv[1]); + EXPECT_EQ(FILE_PATH_LITERAL("--switch2=foo"), cl_argv[2]); + EXPECT_EQ(FILE_PATH_LITERAL("--"), cl_argv[3]); + EXPECT_EQ(FILE_PATH_LITERAL("--arg1"), cl_argv[4]); + EXPECT_EQ(FILE_PATH_LITERAL("--arg2"), cl_argv[5]); +} + +// Tests that when AppendArguments is called that the program is set correctly +// on the target CommandLine object and the switches from the source +// CommandLine are added to the target. +TEST(CommandLineTest, AppendArguments) { + CommandLine cl1(FilePath(FILE_PATH_LITERAL("Program"))); + cl1.AppendSwitch("switch1"); + cl1.AppendSwitchASCII("switch2", "foo"); + + CommandLine cl2(CommandLine::NO_PROGRAM); + cl2.AppendArguments(cl1, true); + EXPECT_EQ(cl1.GetProgram().value(), cl2.GetProgram().value()); + EXPECT_EQ(cl1.GetCommandLineString(), cl2.GetCommandLineString()); + + CommandLine c1(FilePath(FILE_PATH_LITERAL("Program1"))); + c1.AppendSwitch("switch1"); + CommandLine c2(FilePath(FILE_PATH_LITERAL("Program2"))); + c2.AppendSwitch("switch2"); + + c1.AppendArguments(c2, true); + EXPECT_EQ(c1.GetProgram().value(), c2.GetProgram().value()); + EXPECT_TRUE(c1.HasSwitch("switch1")); + EXPECT_TRUE(c1.HasSwitch("switch2")); +} + +#if defined(OS_WIN) +// Make sure that the command line string program paths are quoted as necessary. +// This only makes sense on Windows and the test is basically here to guard +// against regressions. +TEST(CommandLineTest, ProgramQuotes) { + // Check that quotes are not added for paths without spaces. + const FilePath kProgram(L"Program"); + CommandLine cl_program(kProgram); + EXPECT_EQ(kProgram.value(), cl_program.GetProgram().value()); + EXPECT_EQ(kProgram.value(), cl_program.GetCommandLineString()); + + const FilePath kProgramPath(L"Program Path"); + + // Check that quotes are not returned from GetProgram(). + CommandLine cl_program_path(kProgramPath); + EXPECT_EQ(kProgramPath.value(), cl_program_path.GetProgram().value()); + + // Check that quotes are added to command line string paths containing spaces. + CommandLine::StringType cmd_string(cl_program_path.GetCommandLineString()); + CommandLine::StringType program_string(cl_program_path.GetProgram().value()); + EXPECT_EQ('"', cmd_string[0]); + EXPECT_EQ(program_string, cmd_string.substr(1, program_string.length())); + EXPECT_EQ('"', cmd_string[program_string.length() + 1]); +} +#endif + +// Calling Init multiple times should not modify the previous CommandLine. +TEST(CommandLineTest, Init) { + CommandLine* initial = CommandLine::ForCurrentProcess(); + EXPECT_FALSE(CommandLine::Init(0, NULL)); + CommandLine* current = CommandLine::ForCurrentProcess(); + EXPECT_EQ(initial, current); +}
diff --git a/src/base/compiler_specific.h b/src/base/compiler_specific.h new file mode 100644 index 0000000..789aa1e --- /dev/null +++ b/src/base/compiler_specific.h
@@ -0,0 +1,175 @@ +// 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_COMPILER_SPECIFIC_H_ +#define BASE_COMPILER_SPECIFIC_H_ + +#include "build/build_config.h" + +#if defined(COMPILER_MSVC) + +// Macros for suppressing and disabling warnings on MSVC. +// +// Warning numbers are enumerated at: +// http://msdn.microsoft.com/en-us/library/8x5x43k7(VS.80).aspx +// +// The warning pragma: +// http://msdn.microsoft.com/en-us/library/2c8f766e(VS.80).aspx +// +// Using __pragma instead of #pragma inside macros: +// http://msdn.microsoft.com/en-us/library/d9x1s805.aspx + +// MSVC_SUPPRESS_WARNING disables warning |n| for the remainder of the line and +// for the next line of the source file. +#define MSVC_SUPPRESS_WARNING(n) __pragma(warning(suppress:n)) + +// MSVC_PUSH_DISABLE_WARNING pushes |n| onto a stack of warnings to be disabled. +// The warning remains disabled until popped by MSVC_POP_WARNING. +#define MSVC_PUSH_DISABLE_WARNING(n) __pragma(warning(push)) \ + __pragma(warning(disable:n)) + +// MSVC_PUSH_WARNING_LEVEL pushes |n| as the global warning level. The level +// remains in effect until popped by MSVC_POP_WARNING(). Use 0 to disable all +// warnings. +#define MSVC_PUSH_WARNING_LEVEL(n) __pragma(warning(push, n)) + +// Pop effects of innermost MSVC_PUSH_* macro. +#define MSVC_POP_WARNING() __pragma(warning(pop)) + +#define MSVC_DISABLE_OPTIMIZE() __pragma(optimize("", off)) +#define MSVC_ENABLE_OPTIMIZE() __pragma(optimize("", on)) + +// Allows |this| to be passed as an argument in constructor initializer lists. +// This uses push/pop instead of the seemingly simpler suppress feature to avoid +// having the warning be disabled for more than just |code|. +// +// Example usage: +// Foo::Foo() : x(NULL), ALLOW_THIS_IN_INITIALIZER_LIST(y(this)), z(3) {} +// +// Compiler warning C4355: 'this': used in base member initializer list: +// http://msdn.microsoft.com/en-us/library/3c594ae3(VS.80).aspx +#define ALLOW_THIS_IN_INITIALIZER_LIST(code) MSVC_PUSH_DISABLE_WARNING(4355) \ + code \ + MSVC_POP_WARNING() + +// Allows exporting a class that inherits from a non-exported base class. +// This uses suppress instead of push/pop because the delimiter after the +// declaration (either "," or "{") has to be placed before the pop macro. +// +// Example usage: +// class EXPORT_API Foo : NON_EXPORTED_BASE(public Bar) { +// +// MSVC Compiler warning C4275: +// non dll-interface class 'Bar' used as base for dll-interface class 'Foo'. +// Note that this is intended to be used only when no access to the base class' +// static data is done through derived classes or inline methods. For more info, +// see http://msdn.microsoft.com/en-us/library/3tdb471s(VS.80).aspx +#define NON_EXPORTED_BASE(code) MSVC_SUPPRESS_WARNING(4275) \ + code + +#if !defined(UNREFERENCED_PARAMETER) +#define UNREFERENCED_PARAMETER(x) (x) +#endif +#else // Not MSVC + +#define MSVC_SUPPRESS_WARNING(n) +#define MSVC_PUSH_DISABLE_WARNING(n) +#define MSVC_PUSH_WARNING_LEVEL(n) +#define MSVC_POP_WARNING() +#define MSVC_DISABLE_OPTIMIZE() +#define MSVC_ENABLE_OPTIMIZE() +#define ALLOW_THIS_IN_INITIALIZER_LIST(code) code +#define NON_EXPORTED_BASE(code) code +#if !defined(UNREFERENCED_PARAMETER) +#define UNREFERENCED_PARAMETER(x) \ + do { \ + (void)(x); \ + } while (0) +#endif +#endif // COMPILER_MSVC + + +// 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.) +// Use like: +// int x ALLOW_UNUSED = ...; +#if defined(COMPILER_GCC) +#define ALLOW_UNUSED __attribute__((unused)) +#else +#define ALLOW_UNUSED +#endif + +// Annotate a function indicating it should not be inlined. +// Use like: +// NOINLINE void DoStuff() { ... } +#if defined(COMPILER_GCC) +#define NOINLINE __attribute__((noinline)) +#elif defined(COMPILER_MSVC) +#define NOINLINE __declspec(noinline) +#else +#define NOINLINE +#endif + +// Specify memory alignment for structs, classes, etc. +// Use like: +// class ALIGNAS(16) MyClass { ... } +// ALIGNAS(16) int array[4]; +#if defined(COMPILER_MSVC) +#define ALIGNAS(byte_alignment) __declspec(align(byte_alignment)) +#elif defined(COMPILER_GCC) +#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment))) +#endif + +// Return the byte alignment of the given type (available at compile time). Use +// sizeof(type) prior to checking __alignof to workaround Visual C++ bug: +// http://goo.gl/isH0C +// Use like: +// ALIGNOF(int32) // this would be 4 +#if defined(COMPILER_MSVC) +#define ALIGNOF(type) (sizeof(type) - sizeof(type) + __alignof(type)) +#elif defined(COMPILER_GCC) +#define ALIGNOF(type) __alignof__(type) +#endif + +// Annotate a virtual method indicating it must be overriding a virtual +// method in the parent class. +// Use like: +// virtual void foo() OVERRIDE; +#if defined(COMPILER_MSVC) || defined(COMPILER_GCC) +#define OVERRIDE override +#else +#define OVERRIDE +#endif + +// Annotate a function indicating the caller must examine the return value. +// Use like: +// int foo() WARN_UNUSED_RESULT; +// To explicitly ignore a result, see |ignore_result()| in <base/basictypes.h>. +#if defined(COMPILER_GCC) +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif + +// Tell the compiler a function is using a printf-style format string. +// |format_param| is the one-based index of the format string parameter; +// |dots_param| is the one-based index of the "..." parameter. +// For v*printf functions (which take a va_list), pass 0 for dots_param. +// (This is undocumented but matches what the system C headers do.) +#if defined(COMPILER_GCC) +#define PRINTF_FORMAT(format_param, dots_param) \ + __attribute__((format(printf, format_param, dots_param))) +#else +#define PRINTF_FORMAT(format_param, dots_param) +#endif + +// WPRINTF_FORMAT is the same, but for wide format strings. +// This doesn't appear to yet be implemented in any compiler. +// See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=38308 . +#define WPRINTF_FORMAT(format_param, dots_param) +// If available, it would look like: +// __attribute__((format(wprintf, format_param, dots_param))) + +#endif // BASE_COMPILER_SPECIFIC_H_
diff --git a/src/base/containers/linked_hash_map.h b/src/base/containers/linked_hash_map.h new file mode 100644 index 0000000..070e121 --- /dev/null +++ b/src/base/containers/linked_hash_map.h
@@ -0,0 +1,241 @@ +// 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. +// +// This is a simplistic insertion-ordered map. It behaves similarly to an STL +// map, but only implements a small subset of the map's methods. Internally, we +// just keep a map and a list going in parallel. +// +// This class provides no thread safety guarantees, beyond what you would +// normally see with std::list. +// +// Iterators should be stable in the face of mutations, except for an +// iterator pointing to an element that was just deleted. + +#ifndef BASE_CONTAINERS_LINKED_HASH_MAP_H_ +#define BASE_CONTAINERS_LINKED_HASH_MAP_H_ + +#include <list> +#include <utility> + +#include "base/hash_tables.h" +#include "base/logging.h" + +namespace base { + +// This holds a list of pair<Key, Value> items. This list is what gets +// traversed, and it's iterators from this list that we return from +// begin/end/find. +// +// We also keep a map<Key, list::iterator> for find. Since std::list is a +// doubly-linked list, the iterators should remain stable. +template<class Key, class Value> +class linked_hash_map { + private: + typedef std::list<std::pair<Key, Value> > ListType; + typedef base::hash_map<Key, typename ListType::iterator> MapType; + + public: + typedef typename ListType::iterator iterator; + typedef typename ListType::reverse_iterator reverse_iterator; + typedef typename ListType::const_iterator const_iterator; + typedef typename ListType::const_reverse_iterator const_reverse_iterator; + typedef typename MapType::key_type key_type; + typedef typename ListType::value_type value_type; + typedef typename ListType::size_type size_type; + + linked_hash_map() : map_(), list_() { + } + + // Returns an iterator to the first (insertion-ordered) element. Like a map, + // this can be dereferenced to a pair<Key, Value>. + iterator begin() { + return list_.begin(); + } + const_iterator begin() const { + return list_.begin(); + } + + // Returns an iterator beyond the last element. + iterator end() { + return list_.end(); + } + const_iterator end() const { + return list_.end(); + } + + // Returns an iterator to the last (insertion-ordered) element. Like a map, + // this can be dereferenced to a pair<Key, Value>. + reverse_iterator rbegin() { + return list_.rbegin(); + } + const_reverse_iterator rbegin() const { + return list_.rbegin(); + } + + // Returns an iterator beyond the first element. + reverse_iterator rend() { + return list_.rend(); + } + const_reverse_iterator rend() const { + return list_.rend(); + } + + // Front and back accessors common to many stl containers. + + // Returns the earliest-inserted element + const value_type& front() const { + return list_.front(); + } + + // Returns the earliest-inserted element. + value_type& front() { + return list_.front(); + } + + // Returns the most-recently-inserted element. + const value_type& back() const { + return list_.back(); + } + + // Returns the most-recently-inserted element. + value_type& back() { + return list_.back(); + } + + // Clears the map of all values. + void clear() { + map_.clear(); + list_.clear(); + } + + // Returns true iff the map is empty. + bool empty() const { + return list_.empty(); + } + + // Erases values with the provided key. Returns the number of elements + // erased. In this implementation, this will be 0 or 1. + size_type erase(const Key& key) { + typename MapType::iterator found = map_.find(key); + if (found == map_.end()) return 0; + + list_.erase(found->second); + map_.erase(found); + + return 1; + } + + // Erases the item that 'position' points to. Returns an iterator that points + // to the item that comes immediately after the deleted item in the list, or + // end(). + // If the provided iterator is invalid or there is inconsistency between the + // map and list, a CHECK() error will occur. + iterator erase(iterator position) { + typename MapType::iterator found = map_.find(position->first); + CHECK(found->second == position) + << "Inconsisent iterator for map and list, or the iterator is invalid."; + + map_.erase(found); + return list_.erase(position); + } + + // Erases all the items in the range [first, last). Returns an iterator that + // points to the item that comes immediately after the last deleted item in + // the list, or end(). + iterator erase(iterator first, iterator last) { + while (first != last && first != end()) { + first = erase(first); + } + return first; + } + + // Finds the element with the given key. Returns an iterator to the + // value found, or to end() if the value was not found. Like a map, this + // iterator points to a pair<Key, Value>. + iterator find(const Key& key) { + typename MapType::iterator found = map_.find(key); + if (found == map_.end()) { + return end(); + } + return found->second; + } + + const_iterator find(const Key& key) const { + typename MapType::const_iterator found = map_.find(key); + if (found == map_.end()) { + return end(); + } + return found->second; + } + + // Returns the bounds of a range that includes all the elements in the + // container with a key that compares equal to x. + std::pair<iterator, iterator> equal_range(const key_type& key) { + std::pair<typename MapType::iterator, typename MapType::iterator> eq_range = + map_.equal_range(key); + + return std::make_pair(eq_range.first->second, eq_range.second->second); + } + + std::pair<const_iterator, const_iterator> equal_range( + const key_type& key) const { + std::pair<typename MapType::const_iterator, + typename MapType::const_iterator> eq_range = + map_.equal_range(key); + const const_iterator& start_iter = eq_range.first != map_.end() ? + eq_range.first->second : end(); + const const_iterator& end_iter = eq_range.second != map_.end() ? + eq_range.second->second : end(); + + return std::make_pair(start_iter, end_iter); + } + + // Returns the value mapped to key, or an inserted iterator to that position + // in the map. + Value& operator[](const key_type& key) { + return (*((this->insert(std::make_pair(key, Value()))).first)).second; + } + + // Inserts an element into the map + std::pair<iterator, bool> insert(const std::pair<Key, Value>& pair) { + // First make sure the map doesn't have a key with this value. If it does, + // return a pair with an iterator to it, and false indicating that we + // didn't insert anything. + typename MapType::iterator found = map_.find(pair.first); + if (found != map_.end()) return std::make_pair(found->second, false); + + // Otherwise, insert into the list first. + list_.push_back(pair); + + // Obtain an iterator to the newly added element. We do -- instead of - + // since list::iterator doesn't implement operator-(). + typename ListType::iterator last = list_.end(); + --last; + + CHECK(map_.insert(std::make_pair(pair.first, last)).second) + << "Map and list are inconsistent"; + + return std::make_pair(last, true); + } + + size_type size() const { + return list_.size(); + } + + void swap(linked_hash_map& other) { + map_.swap(other.map_); + list_.swap(other.list_); + } + + private: + // The map component, used for speedy lookups + MapType map_; + + // The list component, used for maintaining insertion order + ListType list_; +}; + +} // namespace base + +#endif // BASE_CONTAINERS_LINKED_HASH_MAP_H_
diff --git a/src/base/containers/linked_list.h b/src/base/containers/linked_list.h new file mode 100644 index 0000000..25bbe76 --- /dev/null +++ b/src/base/containers/linked_list.h
@@ -0,0 +1,164 @@ +// Copyright (c) 2009 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_CONTAINERS_LINKED_LIST_H_ +#define BASE_CONTAINERS_LINKED_LIST_H_ + +// Simple LinkedList type. (See the Q&A section to understand how this +// differs from std::list). +// +// To use, start by declaring the class which will be contained in the linked +// list, as extending LinkNode (this gives it next/previous pointers). +// +// class MyNodeType : public LinkNode<MyNodeType> { +// ... +// }; +// +// Next, to keep track of the list's head/tail, use a LinkedList instance: +// +// LinkedList<MyNodeType> list; +// +// To add elements to the list, use any of LinkedList::Append, +// LinkNode::InsertBefore, or LinkNode::InsertAfter: +// +// LinkNode<MyNodeType>* n1 = ...; +// LinkNode<MyNodeType>* n2 = ...; +// LinkNode<MyNodeType>* n3 = ...; +// +// list.Append(n1); +// list.Append(n3); +// n3->InsertBefore(n3); +// +// Lastly, to iterate through the linked list forwards: +// +// for (LinkNode<MyNodeType>* node = list.head(); +// node != list.end(); +// node = node->next()) { +// MyNodeType* value = node->value(); +// ... +// } +// +// Or to iterate the linked list backwards: +// +// for (LinkNode<MyNodeType>* node = list.tail(); +// node != list.end(); +// node = node->previous()) { +// MyNodeType* value = node->value(); +// ... +// } +// +// Questions and Answers: +// +// Q. Should I use std::list or base::LinkedList? +// +// A. The main reason to use base::LinkedList over std::list is +// performance. If you don't care about the performance differences +// then use an STL container, as it makes for better code readability. +// +// Comparing the performance of base::LinkedList<T> to std::list<T*>: +// +// * Erasing an element of type T* from base::LinkedList<T> is +// an O(1) operation. Whereas for std::list<T*> it is O(n). +// That is because with std::list<T*> you must obtain an +// iterator to the T* element before you can call erase(iterator). +// +// * Insertion operations with base::LinkedList<T> never require +// heap allocations. +// +// Q. How does base::LinkedList implementation differ from std::list? +// +// A. Doubly-linked lists are made up of nodes that contain "next" and +// "previous" pointers that reference other nodes in the list. +// +// With base::LinkedList<T>, the type being inserted already reserves +// space for the "next" and "previous" pointers (base::LinkNode<T>*). +// Whereas with std::list<T> the type can be anything, so the implementation +// needs to glue on the "next" and "previous" pointers using +// some internal node type. + +namespace base { + +template <typename T> +class LinkNode { + public: + LinkNode() : previous_(0), next_(0) {} + LinkNode(LinkNode<T>* previous, LinkNode<T>* next) + : previous_(previous), next_(next) {} + + // Insert |this| into the linked list, before |e|. + void InsertBefore(LinkNode<T>* e) { + this->next_ = e; + this->previous_ = e->previous_; + e->previous_->next_ = this; + e->previous_ = this; + } + + // Insert |this| into the linked list, after |e|. + void InsertAfter(LinkNode<T>* e) { + this->next_ = e->next_; + this->previous_ = e; + e->next_->previous_ = this; + e->next_ = this; + } + + // Remove |this| from the linked list. + void RemoveFromList() { + this->previous_->next_ = this->next_; + this->next_->previous_ = this->previous_; + } + + LinkNode<T>* previous() const { + return previous_; + } + + LinkNode<T>* next() const { + return next_; + } + + // Cast from the node-type to the value type. + const T* value() const { + return static_cast<const T*>(this); + } + + T* value() { + return static_cast<T*>(this); + } + + private: + LinkNode<T>* previous_; + LinkNode<T>* next_; +}; + +template <typename T> +class LinkedList { + public: + // The "root" node is self-referential, and forms the basis of a circular + // list (root_.next() will point back to the start of the list, + // and root_->previous() wraps around to the end of the list). + LinkedList() : root_(&root_, &root_) {} + + // Appends |e| to the end of the linked list. + void Append(LinkNode<T>* e) { + e->InsertBefore(&root_); + } + + LinkNode<T>* head() const { + return root_.next(); + } + + LinkNode<T>* tail() const { + return root_.previous(); + } + + const LinkNode<T>* end() const { + return &root_; + } + + private: + LinkNode<T> root_; +}; + +} // namespace base + +#endif // BASE_CONTAINERS_LINKED_LIST_H_
diff --git a/src/base/containers/linked_list_unittest.cc b/src/base/containers/linked_list_unittest.cc new file mode 100644 index 0000000..801e302 --- /dev/null +++ b/src/base/containers/linked_list_unittest.cc
@@ -0,0 +1,261 @@ +// Copyright (c) 2009 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/containers/linked_list.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +class Node : public LinkNode<Node> { + public: + explicit Node(int id) : id_(id) {} + + int id() const { return id_; } + + private: + int id_; +}; + +class MultipleInheritanceNodeBase { + public: + MultipleInheritanceNodeBase() : field_taking_up_space_(0) {} + int field_taking_up_space_; +}; + +class MultipleInheritanceNode : public MultipleInheritanceNodeBase, + public LinkNode<MultipleInheritanceNode> { + public: + MultipleInheritanceNode() {} +}; + +// Checks that when iterating |list| (either from head to tail, or from +// tail to head, as determined by |forward|), we get back |node_ids|, +// which is an array of size |num_nodes|. +void ExpectListContentsForDirection(const LinkedList<Node>& list, + int num_nodes, const int* node_ids, bool forward) { + int i = 0; + for (const LinkNode<Node>* node = (forward ? list.head() : list.tail()); + node != list.end(); + node = (forward ? node->next() : node->previous())) { + ASSERT_LT(i, num_nodes); + int index_of_id = forward ? i : num_nodes - i - 1; + EXPECT_EQ(node_ids[index_of_id], node->value()->id()); + ++i; + } + EXPECT_EQ(num_nodes, i); +} + +void ExpectListContents(const LinkedList<Node>& list, + int num_nodes, + const int* node_ids) { + { + SCOPED_TRACE("Iterating forward (from head to tail)"); + ExpectListContentsForDirection(list, num_nodes, node_ids, true); + } + { + SCOPED_TRACE("Iterating backward (from tail to head)"); + ExpectListContentsForDirection(list, num_nodes, node_ids, false); + } +} + +TEST(LinkedList, Empty) { + LinkedList<Node> list; + EXPECT_EQ(list.end(), list.head()); + EXPECT_EQ(list.end(), list.tail()); + ExpectListContents(list, 0, NULL); +} + +TEST(LinkedList, Append) { + LinkedList<Node> list; + ExpectListContents(list, 0, NULL); + + Node n1(1); + list.Append(&n1); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n1, list.tail()); + { + const int expected[] = {1}; + ExpectListContents(list, arraysize(expected), expected); + } + + Node n2(2); + list.Append(&n2); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n2, list.tail()); + { + const int expected[] = {1, 2}; + ExpectListContents(list, arraysize(expected), expected); + } + + Node n3(3); + list.Append(&n3); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n3, list.tail()); + { + const int expected[] = {1, 2, 3}; + ExpectListContents(list, arraysize(expected), expected); + } +} + +TEST(LinkedList, RemoveFromList) { + LinkedList<Node> list; + + Node n1(1); + Node n2(2); + Node n3(3); + Node n4(4); + Node n5(5); + + list.Append(&n1); + list.Append(&n2); + list.Append(&n3); + list.Append(&n4); + list.Append(&n5); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n5, list.tail()); + { + const int expected[] = {1, 2, 3, 4, 5}; + ExpectListContents(list, arraysize(expected), expected); + } + + // Remove from the middle. + n3.RemoveFromList(); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n5, list.tail()); + { + const int expected[] = {1, 2, 4, 5}; + ExpectListContents(list, arraysize(expected), expected); + } + + // Remove from the tail. + n5.RemoveFromList(); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n4, list.tail()); + { + const int expected[] = {1, 2, 4}; + ExpectListContents(list, arraysize(expected), expected); + } + + // Remove from the head. + n1.RemoveFromList(); + + EXPECT_EQ(&n2, list.head()); + EXPECT_EQ(&n4, list.tail()); + { + const int expected[] = {2, 4}; + ExpectListContents(list, arraysize(expected), expected); + } + + // Empty the list. + n2.RemoveFromList(); + n4.RemoveFromList(); + + ExpectListContents(list, 0, NULL); + EXPECT_EQ(list.end(), list.head()); + EXPECT_EQ(list.end(), list.tail()); + + // Fill the list once again. + list.Append(&n1); + list.Append(&n2); + list.Append(&n3); + list.Append(&n4); + list.Append(&n5); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n5, list.tail()); + { + const int expected[] = {1, 2, 3, 4, 5}; + ExpectListContents(list, arraysize(expected), expected); + } +} + +TEST(LinkedList, InsertBefore) { + LinkedList<Node> list; + + Node n1(1); + Node n2(2); + Node n3(3); + Node n4(4); + + list.Append(&n1); + list.Append(&n2); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n2, list.tail()); + { + const int expected[] = {1, 2}; + ExpectListContents(list, arraysize(expected), expected); + } + + n3.InsertBefore(&n2); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n2, list.tail()); + { + const int expected[] = {1, 3, 2}; + ExpectListContents(list, arraysize(expected), expected); + } + + n4.InsertBefore(&n1); + + EXPECT_EQ(&n4, list.head()); + EXPECT_EQ(&n2, list.tail()); + { + const int expected[] = {4, 1, 3, 2}; + ExpectListContents(list, arraysize(expected), expected); + } +} + +TEST(LinkedList, InsertAfter) { + LinkedList<Node> list; + + Node n1(1); + Node n2(2); + Node n3(3); + Node n4(4); + + list.Append(&n1); + list.Append(&n2); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n2, list.tail()); + { + const int expected[] = {1, 2}; + ExpectListContents(list, arraysize(expected), expected); + } + + n3.InsertAfter(&n2); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n3, list.tail()); + { + const int expected[] = {1, 2, 3}; + ExpectListContents(list, arraysize(expected), expected); + } + + n4.InsertAfter(&n1); + + EXPECT_EQ(&n1, list.head()); + EXPECT_EQ(&n3, list.tail()); + { + const int expected[] = {1, 4, 2, 3}; + ExpectListContents(list, arraysize(expected), expected); + } +} + +TEST(LinkedList, MultipleInheritanceNode) { + MultipleInheritanceNode node; + EXPECT_EQ(&node, node.value()); +} + +} // namespace +} // namespace base
diff --git a/src/base/containers/mru_cache.h b/src/base/containers/mru_cache.h new file mode 100644 index 0000000..f9fb308 --- /dev/null +++ b/src/base/containers/mru_cache.h
@@ -0,0 +1,305 @@ +// 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 contains a template for a Most Recently Used cache that allows +// constant-time access to items using a key, but easy identification of the +// least-recently-used items for removal. Each key can only be associated with +// one payload item at a time. +// +// The key object will be stored twice, so it should support efficient copying. +// +// NOTE: While all operations are O(1), this code is written for +// legibility rather than optimality. If future profiling identifies this as +// a bottleneck, there is room for smaller values of 1 in the O(1). :] + +#ifndef BASE_CONTAINERS_MRU_CACHE_H_ +#define BASE_CONTAINERS_MRU_CACHE_H_ + +#include <list> +#include <map> +#include <utility> + +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "base/logging.h" + +namespace base { + +// MRUCacheBase ---------------------------------------------------------------- + +// This template is used to standardize map type containers that can be used +// by MRUCacheBase. This level of indirection is necessary because of the way +// that template template params and default template params interact. +template <class KeyType, class ValueType> +struct MRUCacheStandardMap { + typedef std::map<KeyType, ValueType> Type; +}; + +// Base class for the MRU cache specializations defined below. +// The deletor will get called on all payloads that are being removed or +// replaced. +template <class KeyType, class PayloadType, class DeletorType, + template <typename, typename> class MapType = MRUCacheStandardMap> +class MRUCacheBase { + public: + // The payload of the list. This maintains a copy of the key so we can + // efficiently delete things given an element of the list. + typedef std::pair<KeyType, PayloadType> value_type; + + private: + typedef std::list<value_type> PayloadList; + typedef typename MapType<KeyType, + typename PayloadList::iterator>::Type KeyIndex; + + public: + typedef typename PayloadList::size_type size_type; + + typedef typename PayloadList::iterator iterator; + typedef typename PayloadList::const_iterator const_iterator; + typedef typename PayloadList::reverse_iterator reverse_iterator; + typedef typename PayloadList::const_reverse_iterator const_reverse_iterator; + + enum { NO_AUTO_EVICT = 0 }; + + // The max_size is the size at which the cache will prune its members to when + // a new item is inserted. If the caller wants to manager this itself (for + // example, maybe it has special work to do when something is evicted), it + // can pass NO_AUTO_EVICT to not restrict the cache size. + explicit MRUCacheBase(size_type max_size) : max_size_(max_size) { + } + + MRUCacheBase(size_type max_size, const DeletorType& deletor) + : max_size_(max_size), deletor_(deletor) { + } + + virtual ~MRUCacheBase() { + iterator i = begin(); + while (i != end()) + i = Erase(i); + } + + size_type max_size() const { return max_size_; } + + // Inserts a payload item with the given key. If an existing item has + // the same key, it is removed prior to insertion. An iterator indicating the + // inserted item will be returned (this will always be the front of the list). + // + // The payload will be copied. In the case of an OwningMRUCache, this function + // will take ownership of the pointer. + iterator Put(const KeyType& key, const PayloadType& payload) { + // Remove any existing payload with that key. + typename KeyIndex::iterator index_iter = index_.find(key); + if (index_iter != index_.end()) { + // Erase the reference to it. This will call the deletor on the removed + // element. The index reference will be replaced in the code below. + Erase(index_iter->second); + } else if (max_size_ != NO_AUTO_EVICT) { + // New item is being inserted which might make it larger than the maximum + // size: kick the oldest thing out if necessary. + ShrinkToSize(max_size_ - 1); + } + + ordering_.push_front(value_type(key, payload)); + index_.insert(std::make_pair(key, ordering_.begin())); + return ordering_.begin(); + } + + // Retrieves the contents of the given key, or end() if not found. This method + // has the side effect of moving the requested item to the front of the + // recency list. + // + // TODO(brettw) We may want a const version of this function in the future. + iterator Get(const KeyType& key) { + typename KeyIndex::iterator index_iter = index_.find(key); + if (index_iter == index_.end()) + return end(); + typename PayloadList::iterator iter = index_iter->second; + + // Move the touched item to the front of the recency ordering. + ordering_.splice(ordering_.begin(), ordering_, iter); + return ordering_.begin(); + } + + // 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()) + 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) { + deletor_(pos->second); + index_.erase(pos->first); + return ordering_.erase(pos); + } + + // MRUCache entries are often processed in reverse order, so we add this + // convenience function (not typically defined by STL containers). + reverse_iterator Erase(reverse_iterator pos) { + // We have to actually give it the incremented iterator to delete, since + // the forward iterator that base() returns is actually one past the item + // being iterated over. + return reverse_iterator(Erase((++pos).base())); + } + + // Shrinks the cache so it only holds |new_size| items. If |new_size| is + // bigger or equal to the current number of items, this will do nothing. + void ShrinkToSize(size_type new_size) { + for (size_type i = size(); i > new_size; i--) + Erase(rbegin()); + } + + // Deletes everything from the cache. + void Clear() { + for (typename PayloadList::iterator i(ordering_.begin()); + i != ordering_.end(); ++i) + deletor_(i->second); + index_.clear(); + ordering_.clear(); + } + + // Returns the number of elements in the cache. + size_type size() const { + // We don't use ordering_.size() for the return value because + // (as a linked list) it can be O(n). + DCHECK(index_.size() == ordering_.size()); + return index_.size(); + } + + // Allows iteration over the list. Forward iteration starts with the most + // recent item and works backwards. + // + // Note that since these iterators are actually iterators over a list, you + // can keep them as you insert or delete things (as long as you don't delete + // the one you are pointing to) and they will still be valid. + iterator begin() { return ordering_.begin(); } + const_iterator begin() const { return ordering_.begin(); } + iterator end() { return ordering_.end(); } + const_iterator end() const { return ordering_.end(); } + + reverse_iterator rbegin() { return ordering_.rbegin(); } + const_reverse_iterator rbegin() const { return ordering_.rbegin(); } + reverse_iterator rend() { return ordering_.rend(); } + const_reverse_iterator rend() const { return ordering_.rend(); } + + bool empty() const { return ordering_.empty(); } + + private: + PayloadList ordering_; + KeyIndex index_; + + size_type max_size_; + + DeletorType deletor_; + + DISALLOW_COPY_AND_ASSIGN(MRUCacheBase); +}; + +// MRUCache -------------------------------------------------------------------- + +// A functor that does nothing. Used by the MRUCache. +template<class PayloadType> +class MRUCacheNullDeletor { + public: + void operator()(PayloadType& /*payload*/) { + } +}; + +// A container that does not do anything to free its data. Use this when storing +// value types (as opposed to pointers) in the list. +template <class KeyType, class PayloadType> +class MRUCache : public MRUCacheBase<KeyType, + PayloadType, + MRUCacheNullDeletor<PayloadType> > { + private: + typedef MRUCacheBase<KeyType, PayloadType, + MRUCacheNullDeletor<PayloadType> > ParentType; + + public: + // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT. + explicit MRUCache(typename ParentType::size_type max_size) + : ParentType(max_size) { + } + virtual ~MRUCache() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(MRUCache); +}; + +// OwningMRUCache -------------------------------------------------------------- + +template<class PayloadType> +class MRUCachePointerDeletor { + public: + void operator()(PayloadType& payload) { + delete payload; + } +}; + +// A cache that owns the payload type, which must be a non-const pointer type. +// The pointers will be deleted when they are removed, replaced, or when the +// cache is destroyed. +template <class KeyType, class PayloadType> +class OwningMRUCache + : public MRUCacheBase<KeyType, + PayloadType, + MRUCachePointerDeletor<PayloadType> > { + private: + typedef MRUCacheBase<KeyType, PayloadType, + MRUCachePointerDeletor<PayloadType> > ParentType; + + public: + // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT. + explicit OwningMRUCache(typename ParentType::size_type max_size) + : ParentType(max_size) { + } + virtual ~OwningMRUCache() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(OwningMRUCache); +}; + +// HashingMRUCache ------------------------------------------------------------ + +template <class KeyType, class ValueType> +struct MRUCacheHashMap { + typedef base::hash_map<KeyType, ValueType> Type; +}; + +// This class is similar to MRUCache, except that it uses base::hash_map as +// the map type instead of std::map. Note that your KeyType must be hashable +// to use this cache. +template <class KeyType, class PayloadType> +class HashingMRUCache : public MRUCacheBase<KeyType, + PayloadType, + MRUCacheNullDeletor<PayloadType>, + MRUCacheHashMap> { + private: + typedef MRUCacheBase<KeyType, PayloadType, + MRUCacheNullDeletor<PayloadType>, + MRUCacheHashMap> ParentType; + + public: + // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT. + explicit HashingMRUCache(typename ParentType::size_type max_size) + : ParentType(max_size) { + } + virtual ~HashingMRUCache() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(HashingMRUCache); +}; + +} // namespace base + +#endif // BASE_CONTAINERS_MRU_CACHE_H_
diff --git a/src/base/containers/mru_cache_unittest.cc b/src/base/containers/mru_cache_unittest.cc new file mode 100644 index 0000000..644f38a --- /dev/null +++ b/src/base/containers/mru_cache_unittest.cc
@@ -0,0 +1,271 @@ +// 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/basictypes.h" +#include "base/containers/mru_cache.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +int cached_item_live_count = 0; + +struct CachedItem { + CachedItem() : value(0) { + cached_item_live_count++; + } + + explicit CachedItem(int new_value) : value(new_value) { + cached_item_live_count++; + } + + CachedItem(const CachedItem& other) : value(other.value) { + cached_item_live_count++; + } + + ~CachedItem() { + cached_item_live_count--; + } + + int value; +}; + +} // namespace + +TEST(MRUCacheTest, Basic) { + typedef base::MRUCache<int, CachedItem> Cache; + Cache cache(Cache::NO_AUTO_EVICT); + + // Check failure conditions + { + CachedItem test_item; + EXPECT_TRUE(cache.Get(0) == cache.end()); + EXPECT_TRUE(cache.Peek(0) == cache.end()); + } + + static const int kItem1Key = 5; + CachedItem item1(10); + Cache::iterator inserted_item = cache.Put(kItem1Key, item1); + EXPECT_EQ(1U, cache.size()); + + // Check that item1 was properly inserted. + { + Cache::iterator found = cache.Get(kItem1Key); + EXPECT_TRUE(inserted_item == cache.begin()); + EXPECT_TRUE(found != cache.end()); + + found = cache.Peek(kItem1Key); + EXPECT_TRUE(found != cache.end()); + + EXPECT_EQ(kItem1Key, found->first); + EXPECT_EQ(item1.value, found->second.value); + } + + static const int kItem2Key = 7; + CachedItem item2(12); + cache.Put(kItem2Key, item2); + EXPECT_EQ(2U, cache.size()); + + // Check that item1 is the oldest since item2 was added afterwards. + { + Cache::reverse_iterator oldest = cache.rbegin(); + ASSERT_TRUE(oldest != cache.rend()); + EXPECT_EQ(kItem1Key, oldest->first); + EXPECT_EQ(item1.value, oldest->second.value); + } + + // Check that item1 is still accessible by key. + { + Cache::iterator test_item = cache.Get(kItem1Key); + ASSERT_TRUE(test_item != cache.end()); + EXPECT_EQ(kItem1Key, test_item->first); + EXPECT_EQ(item1.value, test_item->second.value); + } + + // Check that retrieving item1 pushed item2 to oldest. + { + Cache::reverse_iterator oldest = cache.rbegin(); + ASSERT_TRUE(oldest != cache.rend()); + EXPECT_EQ(kItem2Key, oldest->first); + EXPECT_EQ(item2.value, oldest->second.value); + } + + // Remove the oldest item and check that item1 is now the only member. + { + Cache::reverse_iterator next = cache.Erase(cache.rbegin()); + + EXPECT_EQ(1U, cache.size()); + + EXPECT_TRUE(next == cache.rbegin()); + EXPECT_EQ(kItem1Key, next->first); + EXPECT_EQ(item1.value, next->second.value); + + cache.Erase(cache.begin()); + EXPECT_EQ(0U, cache.size()); + } + + // Check that Clear() works properly. + cache.Put(kItem1Key, item1); + cache.Put(kItem2Key, item2); + EXPECT_EQ(2U, cache.size()); + cache.Clear(); + EXPECT_EQ(0U, cache.size()); +} + +TEST(MRUCacheTest, GetVsPeek) { + typedef base::MRUCache<int, CachedItem> Cache; + Cache cache(Cache::NO_AUTO_EVICT); + + static const int kItem1Key = 1; + CachedItem item1(10); + cache.Put(kItem1Key, item1); + + static const int kItem2Key = 2; + CachedItem item2(20); + cache.Put(kItem2Key, item2); + + // This should do nothing since the size is bigger than the number of items. + cache.ShrinkToSize(100); + + // Check that item1 starts out as oldest + { + Cache::reverse_iterator iter = cache.rbegin(); + ASSERT_TRUE(iter != cache.rend()); + EXPECT_EQ(kItem1Key, iter->first); + EXPECT_EQ(item1.value, iter->second.value); + } + + // Check that Peek doesn't change ordering + { + Cache::iterator peekiter = cache.Peek(kItem1Key); + ASSERT_TRUE(peekiter != cache.end()); + + Cache::reverse_iterator iter = cache.rbegin(); + ASSERT_TRUE(iter != cache.rend()); + EXPECT_EQ(kItem1Key, iter->first); + EXPECT_EQ(item1.value, iter->second.value); + } +} + +TEST(MRUCacheTest, KeyReplacement) { + typedef base::MRUCache<int, CachedItem> Cache; + Cache cache(Cache::NO_AUTO_EVICT); + + static const int kItem1Key = 1; + CachedItem item1(10); + cache.Put(kItem1Key, item1); + + static const int kItem2Key = 2; + CachedItem item2(20); + cache.Put(kItem2Key, item2); + + static const int kItem3Key = 3; + CachedItem item3(30); + cache.Put(kItem3Key, item3); + + static const int kItem4Key = 4; + CachedItem item4(40); + cache.Put(kItem4Key, item4); + + CachedItem item5(50); + cache.Put(kItem3Key, item5); + + EXPECT_EQ(4U, cache.size()); + for (int i = 0; i < 3; ++i) { + Cache::reverse_iterator iter = cache.rbegin(); + ASSERT_TRUE(iter != cache.rend()); + } + + // Make it so only the most important element is there. + cache.ShrinkToSize(1); + + Cache::iterator iter = cache.begin(); + EXPECT_EQ(kItem3Key, iter->first); + EXPECT_EQ(item5.value, iter->second.value); +} + +// Make sure that the owning version release its pointers properly. +TEST(MRUCacheTest, Owning) { + typedef base::OwningMRUCache<int, CachedItem*> Cache; + Cache cache(Cache::NO_AUTO_EVICT); + + int initial_count = cached_item_live_count; + + // First insert and item and then overwrite it. + static const int kItem1Key = 1; + cache.Put(kItem1Key, new CachedItem(20)); + cache.Put(kItem1Key, new CachedItem(22)); + + // There should still be one item, and one extra live item. + Cache::iterator iter = cache.Get(kItem1Key); + EXPECT_EQ(1U, cache.size()); + EXPECT_TRUE(iter != cache.end()); + EXPECT_EQ(initial_count + 1, cached_item_live_count); + + // Now remove it. + cache.Erase(cache.begin()); + EXPECT_EQ(initial_count, cached_item_live_count); + + // Now try another cache that goes out of scope to make sure its pointers + // go away. + { + Cache cache2(Cache::NO_AUTO_EVICT); + cache2.Put(1, new CachedItem(20)); + cache2.Put(2, new CachedItem(20)); + } + + // There should be no objects leaked. + EXPECT_EQ(initial_count, cached_item_live_count); + + // Check that Clear() also frees things correctly. + { + Cache cache2(Cache::NO_AUTO_EVICT); + cache2.Put(1, new CachedItem(20)); + cache2.Put(2, new CachedItem(20)); + EXPECT_EQ(initial_count + 2, cached_item_live_count); + cache2.Clear(); + EXPECT_EQ(initial_count, cached_item_live_count); + } +} + +TEST(MRUCacheTest, AutoEvict) { + typedef base::OwningMRUCache<int, CachedItem*> Cache; + static const Cache::size_type kMaxSize = 3; + + int initial_count = cached_item_live_count; + + { + Cache cache(kMaxSize); + + static const int kItem1Key = 1, kItem2Key = 2, kItem3Key = 3, kItem4Key = 4; + cache.Put(kItem1Key, new CachedItem(20)); + cache.Put(kItem2Key, new CachedItem(21)); + cache.Put(kItem3Key, new CachedItem(22)); + cache.Put(kItem4Key, new CachedItem(23)); + + // The cache should only have kMaxSize items in it even though we inserted + // more. + EXPECT_EQ(kMaxSize, cache.size()); + } + + // There should be no objects leaked. + EXPECT_EQ(initial_count, cached_item_live_count); +} + +TEST(MRUCacheTest, HashingMRUCache) { + // Very simple test to make sure that the hashing cache works correctly. + typedef base::HashingMRUCache<std::string, CachedItem> Cache; + Cache cache(Cache::NO_AUTO_EVICT); + + CachedItem one(1); + cache.Put("First", one); + + CachedItem two(2); + cache.Put("Second", two); + + EXPECT_EQ(one.value, cache.Get("First")->second.value); + EXPECT_EQ(two.value, cache.Get("Second")->second.value); + cache.ShrinkToSize(1); + EXPECT_EQ(two.value, cache.Get("Second")->second.value); + EXPECT_TRUE(cache.Get("First") == cache.end()); +}
diff --git a/src/base/containers/small_map.h b/src/base/containers/small_map.h new file mode 100644 index 0000000..e00e012 --- /dev/null +++ b/src/base/containers/small_map.h
@@ -0,0 +1,677 @@ +// 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_CONTAINERS_SMALL_MAP_H_ +#define BASE_CONTAINERS_SMALL_MAP_H_ + +#include <map> +#include <string> +#include <utility> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/hash_tables.h" +#include "base/logging.h" +#include "base/memory/manual_constructor.h" + +// Disable warning about conversion from '__int64' to 'int'. This value is based +// on the number of elements in the array, and the program would crash from +// running out of memory long before the max int value was reached. +MSVC_PUSH_DISABLE_WARNING(4244) +// Disable warning about calling an uninitialized object's methods. In this +// case, the uninitialized object is ManualConstructor and its method is +// aware that it is uninitialized. +MSVC_PUSH_DISABLE_WARNING(6001) +// Disable warning about reading invalid data from the array. The array offset +// index is determined by the array_iter, which will not go past the end of +// the array. +MSVC_PUSH_DISABLE_WARNING(6385) + +namespace base { + +// An STL-like associative container which starts out backed by a simple +// array but switches to some other container type if it grows beyond a +// fixed size. +// +// WHAT TYPE OF MAP SHOULD YOU USE? +// -------------------------------- +// +// - std::map should be the default if you're not sure, since it's the most +// difficult to mess up. Generally this is backed by a red-black tree. It +// will generate a lot of code (if you use a common key type like int or +// string the linker will probably emiminate the duplicates). It will +// do heap allocations for each element. +// +// - If you only ever keep a couple of items and have very simple usage, +// consider whether a using a vector and brute-force searching it will be +// the most efficient. It's not a lot of generated code (less then a +// red-black tree if your key is "weird" and not eliminated as duplicate of +// something else) and will probably be faster and do fewer heap allocations +// than std::map if you have just a couple of items. +// +// - base::hash_map should be used if you need O(1) lookups. It may waste +// space in the hash table, and it can be easy to write correct-looking +// code with the default hash function being wrong or poorly-behaving. +// +// - SmallMap combines the performance benefits of the brute-force-searched +// vector for small cases (no extra heap allocations), but can efficiently +// fall back if you end up adding many items. It will generate more code +// than std::map (at least 160 bytes for operator[]) which is bad if you +// have a "weird" key where map functions can't be +// duplicate-code-eliminated. If you have a one-off key and aren't in +// performance-critical code, this bloat may negate some of the benefits and +// you should consider on of the other options. +// +// SmallMap will pick up the comparator from the underlying map type. In +// std::map (and in MSVC additionally hash_map) only a "less" operator is +// defined, which requires us to do two comparisons per element when doing the +// brute-force search in the simple array. +// +// We define default overrides for the common map types to avoid this +// double-compare, but you should be aware of this if you use your own +// operator< for your map and supply yor own version of == to the SmallMap. +// You can use regular operator== by just doing: +// +// base::SmallMap<std::map<MyKey, MyValue>, 4, std::equal_to<KyKey> > +// +// +// USAGE +// ----- +// +// NormalMap: The map type to fall back to. This also defines the key +// and value types for the SmallMap. +// kArraySize: The size of the initial array of results. This will be +// allocated with the SmallMap object rather than separately on +// the heap. Once the map grows beyond this size, the map type +// will be used instead. +// EqualKey: A functor which tests two keys for equality. If the wrapped +// map type has a "key_equal" member (hash_map does), then that will +// be used by default. If the wrapped map type has a strict weak +// ordering "key_compare" (std::map does), that will be used to +// implement equality by default. +// MapInit: A functor that takes a ManualConstructor<NormalMap>* and uses it to +// initialize the map. This functor will be called at most once per +// SmallMap, when the map exceeds the threshold of kArraySize and we +// are about to copy values from the array to the map. The functor +// *must* call one of the Init() methods provided by +// ManualConstructor, since after it runs we assume that the NormalMap +// has been initialized. +// +// example: +// base::SmallMap< std::map<string, int> > days; +// days["sunday" ] = 0; +// days["monday" ] = 1; +// days["tuesday" ] = 2; +// days["wednesday"] = 3; +// days["thursday" ] = 4; +// days["friday" ] = 5; +// days["saturday" ] = 6; +// +// You should assume that SmallMap might invalidate all the iterators +// on any call to erase(), insert() and operator[]. + +namespace internal { + +template <typename NormalMap> +class SmallMapDefaultInit { + public: + void operator()(ManualConstructor<NormalMap>* map) const { + map->Init(); + } +}; + +// has_key_equal<M>::value is true iff there exists a type M::key_equal. This is +// used to dispatch to one of the select_equal_key<> metafunctions below. +template <typename M> +struct has_key_equal { + typedef char sml; // "small" is sometimes #defined so we use an abbreviation. + typedef struct { char dummy[2]; } big; + // Two functions, one accepts types that have a key_equal member, and one that + // accepts anything. They each return a value of a different size, so we can + // determine at compile-time which function would have been called. + template <typename U> static big test(typename U::key_equal*); + template <typename> static sml test(...); + // Determines if M::key_equal exists by looking at the size of the return + // type of the compiler-chosen test() function. + static const bool value = (sizeof(test<M>(0)) == sizeof(big)); +}; +template <typename M> const bool has_key_equal<M>::value; + +// Base template used for map types that do NOT have an M::key_equal member, +// e.g., std::map<>. These maps have a strict weak ordering comparator rather +// than an equality functor, so equality will be implemented in terms of that +// comparator. +// +// There's a partial specialization of this template below for map types that do +// have an M::key_equal member. +template <typename M, bool has_key_equal_value> +struct select_equal_key { + struct equal_key { + bool operator()(const typename M::key_type& left, + const typename M::key_type& right) { + // Implements equality in terms of a strict weak ordering comparator. + typename M::key_compare comp; + return !comp(left, right) && !comp(right, left); + } + }; +}; + +// Provide overrides to use operator== for key compare for the "normal" map and +// hash map types. If you override the default comparator or allocator for a +// map or hash_map, or use another type of map, this won't get used. +// +// If we switch to using std::unordered_map for base::hash_map, then the +// hash_map specialization can be removed. +template <typename KeyType, typename ValueType> +struct select_equal_key< std::map<KeyType, ValueType>, false> { + struct equal_key { + bool operator()(const KeyType& left, const KeyType& right) { + return left == right; + } + }; +}; +template <typename KeyType, typename ValueType> +struct select_equal_key< base::hash_map<KeyType, ValueType>, false> { + struct equal_key { + bool operator()(const KeyType& left, const KeyType& right) { + return left == right; + } + }; +}; + +// Partial template specialization handles case where M::key_equal exists, e.g., +// hash_map<>. +template <typename M> +struct select_equal_key<M, true> { + typedef typename M::key_equal equal_key; +}; + +} // namespace internal + +template <typename NormalMap, + int kArraySize = 4, + typename EqualKey = + typename internal::select_equal_key< + NormalMap, + internal::has_key_equal<NormalMap>::value>::equal_key, + typename MapInit = internal::SmallMapDefaultInit<NormalMap> > +class SmallMap { + // We cannot rely on the compiler to reject array of size 0. In + // particular, gcc 2.95.3 does it but later versions allow 0-length + // arrays. Therefore, we explicitly reject non-positive kArraySize + // here. + COMPILE_ASSERT(kArraySize > 0, default_initial_size_should_be_positive); + + public: + typedef typename NormalMap::key_type key_type; + typedef typename NormalMap::mapped_type data_type; + typedef typename NormalMap::mapped_type mapped_type; + typedef typename NormalMap::value_type value_type; + typedef EqualKey key_equal; + + SmallMap() : size_(0), functor_(MapInit()) {} + + explicit SmallMap(const MapInit& functor) : size_(0), functor_(functor) {} + + // Allow copy-constructor and assignment, since STL allows them too. + SmallMap(const SmallMap& src) { + // size_ and functor_ are initted in InitFrom() + InitFrom(src); + } + void operator=(const SmallMap& src) { + if (&src == this) return; + + // This is not optimal. If src and dest are both using the small + // array, we could skip the teardown and reconstruct. One problem + // to be resolved is that the value_type itself is pair<const K, + // V>, and const K is not assignable. + Destroy(); + InitFrom(src); + } + ~SmallMap() { + Destroy(); + } + + class const_iterator; + + class iterator { + public: + typedef typename NormalMap::iterator::iterator_category iterator_category; + typedef typename NormalMap::iterator::value_type value_type; + typedef typename NormalMap::iterator::difference_type difference_type; + typedef typename NormalMap::iterator::pointer pointer; + typedef typename NormalMap::iterator::reference reference; + + inline iterator(): array_iter_(NULL) {} + + inline iterator& operator++() { + if (array_iter_ != NULL) { + ++array_iter_; + } else { + ++hash_iter_; + } + return *this; + } + inline iterator operator++(int /*unused*/) { + iterator result(*this); + ++(*this); + return result; + } + inline iterator& operator--() { + if (array_iter_ != NULL) { + --array_iter_; + } else { + --hash_iter_; + } + return *this; + } + inline iterator operator--(int /*unused*/) { + iterator result(*this); + --(*this); + return result; + } + inline value_type* operator->() const { + if (array_iter_ != NULL) { + return array_iter_->get(); + } else { + return hash_iter_.operator->(); + } + } + + inline value_type& operator*() const { + if (array_iter_ != NULL) { + return *array_iter_->get(); + } else { + return *hash_iter_; + } + } + + inline bool operator==(const iterator& other) const { + if (array_iter_ != NULL) { + return array_iter_ == other.array_iter_; + } else { + return other.array_iter_ == NULL && hash_iter_ == other.hash_iter_; + } + } + + inline bool operator!=(const iterator& other) const { + return !(*this == other); + } + + bool operator==(const const_iterator& other) const; + bool operator!=(const const_iterator& other) const; + + private: + friend class SmallMap; + friend class const_iterator; + inline explicit iterator(ManualConstructor<value_type>* init) + : array_iter_(init) {} + inline explicit iterator(const typename NormalMap::iterator& init) + : array_iter_(NULL), hash_iter_(init) {} + + ManualConstructor<value_type>* array_iter_; + typename NormalMap::iterator hash_iter_; + }; + + class const_iterator { + public: + typedef typename NormalMap::const_iterator::iterator_category + iterator_category; + typedef typename NormalMap::const_iterator::value_type value_type; + typedef typename NormalMap::const_iterator::difference_type difference_type; + typedef typename NormalMap::const_iterator::pointer pointer; + typedef typename NormalMap::const_iterator::reference reference; + + inline const_iterator(): array_iter_(NULL) {} + // Non-explicit ctor lets us convert regular iterators to const iterators + inline const_iterator(const iterator& other) + : array_iter_(other.array_iter_), hash_iter_(other.hash_iter_) {} + + inline const_iterator& operator++() { + if (array_iter_ != NULL) { + ++array_iter_; + } else { + ++hash_iter_; + } + return *this; + } + inline const_iterator operator++(int /*unused*/) { + const_iterator result(*this); + ++(*this); + return result; + } + + inline const_iterator& operator--() { + if (array_iter_ != NULL) { + --array_iter_; + } else { + --hash_iter_; + } + return *this; + } + inline const_iterator operator--(int /*unused*/) { + const_iterator result(*this); + --(*this); + return result; + } + + inline const value_type* operator->() const { + if (array_iter_ != NULL) { + return array_iter_->get(); + } else { + return hash_iter_.operator->(); + } + } + + inline const value_type& operator*() const { + if (array_iter_ != NULL) { + return *array_iter_->get(); + } else { + return *hash_iter_; + } + } + + inline bool operator==(const const_iterator& other) const { + if (array_iter_ != NULL) { + return array_iter_ == other.array_iter_; + } else { + return other.array_iter_ == NULL && hash_iter_ == other.hash_iter_; + } + } + + inline bool operator!=(const const_iterator& other) const { + return !(*this == other); + } + + private: + friend class SmallMap; + inline explicit const_iterator( + const ManualConstructor<value_type>* init) + : array_iter_(init) {} + inline explicit const_iterator( + const typename NormalMap::const_iterator& init) + : array_iter_(NULL), hash_iter_(init) {} + + const ManualConstructor<value_type>* array_iter_; + typename NormalMap::const_iterator hash_iter_; + }; + + iterator find(const key_type& key) { + key_equal compare; + if (size_ >= 0) { + for (int i = 0; i < size_; i++) { + if (compare(array_[i]->first, key)) { + return iterator(array_ + i); + } + } + return iterator(array_ + size_); + } else { + return iterator(map()->find(key)); + } + } + + const_iterator find(const key_type& key) const { + key_equal compare; + if (size_ >= 0) { + for (int i = 0; i < size_; i++) { + if (compare(array_[i]->first, key)) { + return const_iterator(array_ + i); + } + } + return const_iterator(array_ + size_); + } else { + return const_iterator(map()->find(key)); + } + } + + // Invalidates iterators. + data_type& operator[](const key_type& key) { + key_equal compare; + + if (size_ >= 0) { + // operator[] searches backwards, favoring recently-added + // elements. + for (int i = size_-1; i >= 0; --i) { + if (compare(array_[i]->first, key)) { + return array_[i]->second; + } + } + if (size_ == kArraySize) { + ConvertToRealMap(); + return (*map_)[key]; + } else { + array_[size_].Init(key, data_type()); + return array_[size_++]->second; + } + } else { + return (*map_)[key]; + } + } + + // Invalidates iterators. + std::pair<iterator, bool> insert(const value_type& x) { + key_equal compare; + + if (size_ >= 0) { + for (int i = 0; i < size_; i++) { + if (compare(array_[i]->first, x.first)) { + return std::make_pair(iterator(array_ + i), false); + } + } + if (size_ == kArraySize) { + ConvertToRealMap(); // Invalidates all iterators! + std::pair<typename NormalMap::iterator, bool> ret = map_->insert(x); + return std::make_pair(iterator(ret.first), ret.second); + } else { + array_[size_].Init(x); + return std::make_pair(iterator(array_ + size_++), true); + } + } else { + std::pair<typename NormalMap::iterator, bool> ret = map_->insert(x); + return std::make_pair(iterator(ret.first), ret.second); + } + } + + // Invalidates iterators. + template <class InputIterator> + void insert(InputIterator f, InputIterator l) { + while (f != l) { + insert(*f); + ++f; + } + } + + iterator begin() { + if (size_ >= 0) { + return iterator(array_); + } else { + return iterator(map_->begin()); + } + } + const_iterator begin() const { + if (size_ >= 0) { + return const_iterator(array_); + } else { + return const_iterator(map_->begin()); + } + } + + iterator end() { + if (size_ >= 0) { + return iterator(array_ + size_); + } else { + return iterator(map_->end()); + } + } + const_iterator end() const { + if (size_ >= 0) { + return const_iterator(array_ + size_); + } else { + return const_iterator(map_->end()); + } + } + + void clear() { + if (size_ >= 0) { + for (int i = 0; i < size_; i++) { + array_[i].Destroy(); + } + } else { + map_.Destroy(); + } + size_ = 0; + } + + // Invalidates iterators. + void erase(const iterator& position) { + if (size_ >= 0) { +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wshorten-64-to-32" +#endif + int i = position.array_iter_ - array_; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + array_[i].Destroy(); + --size_; + if (i != size_) { + array_[i].Init(*array_[size_]); + array_[size_].Destroy(); + } + } else { + map_->erase(position.hash_iter_); + } + } + + size_t erase(const key_type& key) { + iterator iter = find(key); + if (iter == end()) return 0u; + erase(iter); + return 1u; + } + + size_t count(const key_type& key) const { + return (find(key) == end()) ? 0 : 1; + } + + size_t size() const { + if (size_ >= 0) { + return static_cast<size_t>(size_); + } else { + return map_->size(); + } + } + + bool empty() const { + if (size_ >= 0) { + return (size_ == 0); + } else { + return map_->empty(); + } + } + + // Returns true if we have fallen back to using the underlying map + // representation. + bool UsingFullMap() const { + return size_ < 0; + } + + inline NormalMap* map() { + CHECK(UsingFullMap()); + return map_.get(); + } + inline const NormalMap* map() const { + CHECK(UsingFullMap()); + return map_.get(); + } + + private: + int size_; // negative = using hash_map + + MapInit functor_; + + // We want to call constructors and destructors manually, but we don't + // want to allocate and deallocate the memory used for them separately. + // So, we use this crazy ManualConstructor class. + // + // Since array_ and map_ are mutually exclusive, we'll put them in a + // union, too. We add in a dummy_ value which quiets MSVC from otherwise + // giving an erroneous "union member has copy constructor" error message + // (C2621). This dummy member has to come before array_ to quiet the + // compiler. + // + // TODO(brettw) remove this and use C++11 unions when we require C++11. + union { + ManualConstructor<value_type> dummy_; + ManualConstructor<value_type> array_[kArraySize]; + ManualConstructor<NormalMap> map_; + }; + + void ConvertToRealMap() { + // Move the current elements into a temporary array. + ManualConstructor<value_type> temp_array[kArraySize]; + + for (int i = 0; i < kArraySize; i++) { + temp_array[i].Init(*array_[i]); + array_[i].Destroy(); + } + + // Initialize the map. + size_ = -1; + functor_(&map_); + + // Insert elements into it. + for (int i = 0; i < kArraySize; i++) { + map_->insert(*temp_array[i]); + temp_array[i].Destroy(); + } + } + + // Helpers for constructors and destructors. + void InitFrom(const SmallMap& src) { + functor_ = src.functor_; + size_ = src.size_; + if (src.size_ >= 0) { + for (int i = 0; i < size_; i++) { + array_[i].Init(*src.array_[i]); + } + } else { + functor_(&map_); + (*map_.get()) = (*src.map_.get()); + } + } + void Destroy() { + if (size_ >= 0) { + for (int i = 0; i < size_; i++) { + array_[i].Destroy(); + } + } else { + map_.Destroy(); + } + } +}; + +template <typename NormalMap, int kArraySize, typename EqualKey, + typename Functor> +inline bool SmallMap<NormalMap, kArraySize, EqualKey, + Functor>::iterator::operator==( + const const_iterator& other) const { + return other == *this; +} +template <typename NormalMap, int kArraySize, typename EqualKey, + typename Functor> +inline bool SmallMap<NormalMap, kArraySize, EqualKey, + Functor>::iterator::operator!=( + const const_iterator& other) const { + return other != *this; +} + +} // namespace base + +MSVC_POP_WARNING() +MSVC_POP_WARNING() +MSVC_POP_WARNING() + +#endif // BASE_CONTAINERS_SMALL_MAP_H_
diff --git a/src/base/containers/small_map_unittest.cc b/src/base/containers/small_map_unittest.cc new file mode 100644 index 0000000..d1c8680 --- /dev/null +++ b/src/base/containers/small_map_unittest.cc
@@ -0,0 +1,491 @@ +// 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/containers/small_map.h" + +#include <stddef.h> + +#include <algorithm> +#include <functional> +#include <map> + +#include "base/logging.h" +#include "base/hash_tables.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(SmallMap, General) { + SmallMap<hash_map<int, int> > m; + + EXPECT_TRUE(m.empty()); + + m[0] = 5; + + EXPECT_FALSE(m.empty()); + EXPECT_EQ(m.size(), 1u); + + m[9] = 2; + + EXPECT_FALSE(m.empty()); + EXPECT_EQ(m.size(), 2u); + + EXPECT_EQ(m[9], 2); + EXPECT_EQ(m[0], 5); + EXPECT_FALSE(m.UsingFullMap()); + + SmallMap<hash_map<int, int> >::iterator iter(m.begin()); + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 0); + EXPECT_EQ(iter->second, 5); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ((*iter).first, 9); + EXPECT_EQ((*iter).second, 2); + ++iter; + EXPECT_TRUE(iter == m.end()); + + m[8] = 23; + m[1234] = 90; + m[-5] = 6; + + EXPECT_EQ(m[ 9], 2); + EXPECT_EQ(m[ 0], 5); + EXPECT_EQ(m[1234], 90); + EXPECT_EQ(m[ 8], 23); + EXPECT_EQ(m[ -5], 6); + EXPECT_EQ(m.size(), 5u); + EXPECT_FALSE(m.empty()); + EXPECT_TRUE(m.UsingFullMap()); + + iter = m.begin(); + for (int i = 0; i < 5; i++) { + EXPECT_TRUE(iter != m.end()); + ++iter; + } + EXPECT_TRUE(iter == m.end()); + + const SmallMap<hash_map<int, int> >& ref = m; + EXPECT_TRUE(ref.find(1234) != m.end()); + EXPECT_TRUE(ref.find(5678) == m.end()); +} + +TEST(SmallMap, PostFixIteratorIncrement) { + SmallMap<hash_map<int, int> > m; + m[0] = 5; + m[2] = 3; + + { + SmallMap<hash_map<int, int> >::iterator iter(m.begin()); + SmallMap<hash_map<int, int> >::iterator last(iter++); + ++last; + EXPECT_TRUE(last == iter); + } + + { + SmallMap<hash_map<int, int> >::const_iterator iter(m.begin()); + SmallMap<hash_map<int, int> >::const_iterator last(iter++); + ++last; + EXPECT_TRUE(last == iter); + } +} + +// Based on the General testcase. +TEST(SmallMap, CopyConstructor) { + SmallMap<hash_map<int, int> > src; + + { + SmallMap<hash_map<int, int> > m(src); + EXPECT_TRUE(m.empty()); + } + + src[0] = 5; + + { + SmallMap<hash_map<int, int> > m(src); + EXPECT_FALSE(m.empty()); + EXPECT_EQ(m.size(), 1u); + } + + src[9] = 2; + + { + SmallMap<hash_map<int, int> > m(src); + EXPECT_FALSE(m.empty()); + EXPECT_EQ(m.size(), 2u); + + EXPECT_EQ(m[9], 2); + EXPECT_EQ(m[0], 5); + EXPECT_FALSE(m.UsingFullMap()); + } + + src[8] = 23; + src[1234] = 90; + src[-5] = 6; + + { + SmallMap<hash_map<int, int> > m(src); + EXPECT_EQ(m[ 9], 2); + EXPECT_EQ(m[ 0], 5); + EXPECT_EQ(m[1234], 90); + EXPECT_EQ(m[ 8], 23); + EXPECT_EQ(m[ -5], 6); + EXPECT_EQ(m.size(), 5u); + EXPECT_FALSE(m.empty()); + EXPECT_TRUE(m.UsingFullMap()); + } +} + +template<class inner> +static void SmallMapToMap(SmallMap<inner> const& src, inner* dest) { + typename SmallMap<inner>::const_iterator it; + for (it = src.begin(); it != src.end(); ++it) { + dest->insert(std::make_pair(it->first, it->second)); + } +} + +template<class inner> +static bool SmallMapEqual(SmallMap<inner> const& a, + SmallMap<inner> const& b) { + inner ia, ib; + SmallMapToMap(a, &ia); + SmallMapToMap(b, &ib); + + // On most systems we can use operator== here, but under some lesser STL + // implementations it doesn't seem to work. So we manually compare. + if (ia.size() != ib.size()) + return false; + for (typename inner::iterator ia_it = ia.begin(), ib_it = ib.begin(); + ia_it != ia.end(); ++ia_it, ++ib_it) { + if (*ia_it != *ib_it) + return false; + } + return true; +} + +TEST(SmallMap, AssignmentOperator) { + SmallMap<hash_map<int, int> > src_small; + SmallMap<hash_map<int, int> > src_large; + + src_small[1] = 20; + src_small[2] = 21; + src_small[3] = 22; + EXPECT_FALSE(src_small.UsingFullMap()); + + src_large[1] = 20; + src_large[2] = 21; + src_large[3] = 22; + src_large[5] = 23; + src_large[6] = 24; + src_large[7] = 25; + EXPECT_TRUE(src_large.UsingFullMap()); + + // Assignments to empty. + SmallMap<hash_map<int, int> > dest_small; + dest_small = src_small; + EXPECT_TRUE(SmallMapEqual(dest_small, src_small)); + EXPECT_EQ(dest_small.UsingFullMap(), + src_small.UsingFullMap()); + + SmallMap<hash_map<int, int> > dest_large; + dest_large = src_large; + EXPECT_TRUE(SmallMapEqual(dest_large, src_large)); + EXPECT_EQ(dest_large.UsingFullMap(), + src_large.UsingFullMap()); + + // Assignments which assign from full to small, and vice versa. + dest_small = src_large; + EXPECT_TRUE(SmallMapEqual(dest_small, src_large)); + EXPECT_EQ(dest_small.UsingFullMap(), + src_large.UsingFullMap()); + + dest_large = src_small; + EXPECT_TRUE(SmallMapEqual(dest_large, src_small)); + EXPECT_EQ(dest_large.UsingFullMap(), + src_small.UsingFullMap()); + + // Double check that SmallMapEqual works: + dest_large[42] = 666; + EXPECT_FALSE(SmallMapEqual(dest_large, src_small)); +} + +TEST(SmallMap, Insert) { + SmallMap<hash_map<int, int> > sm; + + // loop through the transition from small map to map. + for (int i = 1; i <= 10; ++i) { + VLOG(1) << "Iteration " << i; + // insert an element + std::pair<SmallMap<hash_map<int, int> >::iterator, + bool> ret; + ret = sm.insert(std::make_pair(i, 100*i)); + EXPECT_TRUE(ret.second); + EXPECT_TRUE(ret.first == sm.find(i)); + EXPECT_EQ(ret.first->first, i); + EXPECT_EQ(ret.first->second, 100*i); + + // try to insert it again with different value, fails, but we still get an + // iterator back with the original value. + ret = sm.insert(std::make_pair(i, -i)); + EXPECT_FALSE(ret.second); + EXPECT_TRUE(ret.first == sm.find(i)); + EXPECT_EQ(ret.first->first, i); + EXPECT_EQ(ret.first->second, 100*i); + + // check the state of the map. + for (int j = 1; j <= i; ++j) { + SmallMap<hash_map<int, int> >::iterator it = sm.find(j); + EXPECT_TRUE(it != sm.end()); + EXPECT_EQ(it->first, j); + EXPECT_EQ(it->second, j * 100); + } + EXPECT_EQ(sm.size(), static_cast<size_t>(i)); + EXPECT_FALSE(sm.empty()); + } +} + +TEST(SmallMap, InsertRange) { + // loop through the transition from small map to map. + for (int elements = 0; elements <= 10; ++elements) { + VLOG(1) << "Elements " << elements; + hash_map<int, int> normal_map; + for (int i = 1; i <= elements; ++i) { + normal_map.insert(std::make_pair(i, 100*i)); + } + + SmallMap<hash_map<int, int> > sm; + sm.insert(normal_map.begin(), normal_map.end()); + EXPECT_EQ(normal_map.size(), sm.size()); + for (int i = 1; i <= elements; ++i) { + VLOG(1) << "Iteration " << i; + EXPECT_TRUE(sm.find(i) != sm.end()); + EXPECT_EQ(sm.find(i)->first, i); + EXPECT_EQ(sm.find(i)->second, 100*i); + } + } +} + +TEST(SmallMap, Erase) { + SmallMap<hash_map<std::string, int> > m; + SmallMap<hash_map<std::string, int> >::iterator iter; + + m["monday"] = 1; + m["tuesday"] = 2; + m["wednesday"] = 3; + + EXPECT_EQ(m["monday" ], 1); + EXPECT_EQ(m["tuesday" ], 2); + EXPECT_EQ(m["wednesday"], 3); + EXPECT_EQ(m.count("tuesday"), 1u); + EXPECT_FALSE(m.UsingFullMap()); + + iter = m.begin(); + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, "monday"); + EXPECT_EQ(iter->second, 1); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, "tuesday"); + EXPECT_EQ(iter->second, 2); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, "wednesday"); + EXPECT_EQ(iter->second, 3); + ++iter; + EXPECT_TRUE(iter == m.end()); + + EXPECT_EQ(m.erase("tuesday"), 1u); + + EXPECT_EQ(m["monday" ], 1); + EXPECT_EQ(m["wednesday"], 3); + EXPECT_EQ(m.count("tuesday"), 0u); + EXPECT_EQ(m.erase("tuesday"), 0u); + + iter = m.begin(); + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, "monday"); + EXPECT_EQ(iter->second, 1); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, "wednesday"); + EXPECT_EQ(iter->second, 3); + ++iter; + EXPECT_TRUE(iter == m.end()); + + m["thursday"] = 4; + m["friday"] = 5; + EXPECT_EQ(m.size(), 4u); + EXPECT_FALSE(m.empty()); + EXPECT_FALSE(m.UsingFullMap()); + + m["saturday"] = 6; + EXPECT_TRUE(m.UsingFullMap()); + + EXPECT_EQ(m.count("friday"), 1u); + EXPECT_EQ(m.erase("friday"), 1u); + EXPECT_TRUE(m.UsingFullMap()); + EXPECT_EQ(m.count("friday"), 0u); + EXPECT_EQ(m.erase("friday"), 0u); + + EXPECT_EQ(m.size(), 4u); + EXPECT_FALSE(m.empty()); + EXPECT_EQ(m.erase("monday"), 1u); + EXPECT_EQ(m.size(), 3u); + EXPECT_FALSE(m.empty()); + + m.clear(); + EXPECT_FALSE(m.UsingFullMap()); + EXPECT_EQ(m.size(), 0u); + EXPECT_TRUE(m.empty()); +} + +TEST(SmallMap, NonHashMap) { + SmallMap<std::map<int, int>, 4, std::equal_to<int> > m; + EXPECT_TRUE(m.empty()); + + m[9] = 2; + m[0] = 5; + + EXPECT_EQ(m[9], 2); + EXPECT_EQ(m[0], 5); + EXPECT_EQ(m.size(), 2u); + EXPECT_FALSE(m.empty()); + EXPECT_FALSE(m.UsingFullMap()); + + SmallMap<std::map<int, int>, 4, std::equal_to<int> >::iterator iter( + m.begin()); + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 9); + EXPECT_EQ(iter->second, 2); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 0); + EXPECT_EQ(iter->second, 5); + ++iter; + EXPECT_TRUE(iter == m.end()); + --iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 0); + EXPECT_EQ(iter->second, 5); + + m[8] = 23; + m[1234] = 90; + m[-5] = 6; + + EXPECT_EQ(m[ 9], 2); + EXPECT_EQ(m[ 0], 5); + EXPECT_EQ(m[1234], 90); + EXPECT_EQ(m[ 8], 23); + EXPECT_EQ(m[ -5], 6); + EXPECT_EQ(m.size(), 5u); + EXPECT_FALSE(m.empty()); + EXPECT_TRUE(m.UsingFullMap()); + + iter = m.begin(); + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, -5); + EXPECT_EQ(iter->second, 6); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 0); + EXPECT_EQ(iter->second, 5); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 8); + EXPECT_EQ(iter->second, 23); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 9); + EXPECT_EQ(iter->second, 2); + ++iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 1234); + EXPECT_EQ(iter->second, 90); + ++iter; + EXPECT_TRUE(iter == m.end()); + --iter; + ASSERT_TRUE(iter != m.end()); + EXPECT_EQ(iter->first, 1234); + EXPECT_EQ(iter->second, 90); +} + +TEST(SmallMap, DefaultEqualKeyWorks) { + // If these tests compile, they pass. The EXPECT calls are only there to avoid + // unused variable warnings. + SmallMap<hash_map<int, int> > hm; + EXPECT_EQ(0u, hm.size()); + SmallMap<std::map<int, int> > m; + EXPECT_EQ(0u, m.size()); +} + +namespace { + +class hash_map_add_item : public hash_map<int, int> { + public: + hash_map_add_item() : hash_map<int, int>() {} + hash_map_add_item(const std::pair<int, int>& item) : hash_map<int, int>() { + insert(item); + } +}; + +void InitMap(ManualConstructor<hash_map_add_item>* map_ctor) { + map_ctor->Init(std::make_pair(0, 0)); +} + +class hash_map_add_item_initializer { + public: + explicit hash_map_add_item_initializer(int item_to_add) + : item_(item_to_add) {} + hash_map_add_item_initializer() + : item_(0) {} + void operator()(ManualConstructor<hash_map_add_item>* map_ctor) const { + map_ctor->Init(std::make_pair(item_, item_)); + } + + int item_; +}; + +} // anonymous namespace + +TEST(SmallMap, SubclassInitializationWithFunctionPointer) { + SmallMap<hash_map_add_item, 4, std::equal_to<int>, + void (&)(ManualConstructor<hash_map_add_item>*)> m(InitMap); + + EXPECT_TRUE(m.empty()); + + m[1] = 1; + m[2] = 2; + m[3] = 3; + m[4] = 4; + + EXPECT_EQ(4u, m.size()); + EXPECT_EQ(0u, m.count(0)); + + m[5] = 5; + EXPECT_EQ(6u, m.size()); + // Our function adds an extra item when we convert to a map. + EXPECT_EQ(1u, m.count(0)); +} + +TEST(SmallMap, SubclassInitializationWithFunctionObject) { + SmallMap<hash_map_add_item, 4, std::equal_to<int>, + hash_map_add_item_initializer> m(hash_map_add_item_initializer(-1)); + + EXPECT_TRUE(m.empty()); + + m[1] = 1; + m[2] = 2; + m[3] = 3; + m[4] = 4; + + EXPECT_EQ(4u, m.size()); + EXPECT_EQ(0u, m.count(-1)); + + m[5] = 5; + EXPECT_EQ(6u, m.size()); + // Our functor adds an extra item when we convert to a map. + EXPECT_EQ(1u, m.count(-1)); +} + +} // namespace base
diff --git a/src/base/containers/stack_container.h b/src/base/containers/stack_container.h new file mode 100644 index 0000000..b3e508b --- /dev/null +++ b/src/base/containers/stack_container.h
@@ -0,0 +1,258 @@ +// 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_CONTAINERS_STACK_CONTAINER_H_ +#define BASE_CONTAINERS_STACK_CONTAINER_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "build/build_config.h" +#include "base/memory/aligned_memory.h" +#include "base/string16.h" + +namespace base { + +// This allocator can be used with STL containers to provide a stack buffer +// from which to allocate memory and overflows onto the heap. This stack buffer +// would be allocated on the stack and allows us to avoid heap operations in +// some situations. +// +// STL likes to make copies of allocators, so the allocator itself can't hold +// the data. Instead, we make the creator responsible for creating a +// StackAllocator::Source which contains the data. Copying the allocator +// merely copies the pointer to this shared source, so all allocators created +// based on our allocator will share the same stack buffer. +// +// This stack buffer implementation is very simple. The first allocation that +// fits in the stack buffer will use the stack buffer. Any subsequent +// allocations will not use the stack buffer, even if there is unused room. +// This makes it appropriate for array-like containers, but the caller should +// be sure to reserve() in the container up to the stack buffer size. Otherwise +// the container will allocate a small array which will "use up" the stack +// buffer. +template<typename T, size_t stack_capacity> +class StackAllocator : public std::allocator<T> { + public: + typedef typename std::allocator<T>::pointer pointer; + typedef typename std::allocator<T>::size_type size_type; + + // Backing store for the allocator. The container owner is responsible for + // maintaining this for as long as any containers using this allocator are + // live. + struct Source { + Source() : used_stack_buffer_(false) { + } + + // Casts the buffer in its right type. + T* stack_buffer() { return stack_buffer_.template data_as<T>(); } + const T* stack_buffer() const { + return stack_buffer_.template data_as<T>(); + } + + // The buffer itself. It is not of type T because we don't want the + // constructors and destructors to be automatically called. Define a POD + // buffer of the right size instead. + base::AlignedMemory<sizeof(T[stack_capacity]), ALIGNOF(T)> stack_buffer_; +#if defined(OS_ANDROID) + COMPILE_ASSERT(ALIGNOF(T) <= 16, crbug_115612); +#endif + + // Set when the stack buffer is used for an allocation. We do not track + // how much of the buffer is used, only that somebody is using it. + bool used_stack_buffer_; + }; + + // Used by containers when they want to refer to an allocator of type U. + template<typename U> + struct rebind { + typedef StackAllocator<U, stack_capacity> other; + }; + + // For the straight up copy c-tor, we can share storage. + StackAllocator(const StackAllocator<T, stack_capacity>& rhs) + : std::allocator<T>(), source_(rhs.source_) { + } + + // ISO C++ requires the following constructor to be defined, + // and std::vector in VC++2008SP1 Release fails with an error + // in the class _Container_base_aux_alloc_real (from <xutility>) + // if the constructor does not exist. + // For this constructor, we cannot share storage; there's + // no guarantee that the Source buffer of Ts is large enough + // for Us. + // TODO: If we were fancy pants, perhaps we could share storage + // iff sizeof(T) == sizeof(U). + template<typename U, size_t other_capacity> + StackAllocator(const StackAllocator<U, other_capacity>& other) + : source_(NULL) { + } + + explicit StackAllocator(Source* source) : source_(source) { + } + + // Actually do the allocation. Use the stack buffer if nobody has used it yet + // and the size requested fits. Otherwise, fall through to the standard + // allocator. + pointer allocate(size_type n, void* hint = 0) { + if (source_ != NULL && !source_->used_stack_buffer_ + && n <= stack_capacity) { + source_->used_stack_buffer_ = true; + return source_->stack_buffer(); + } else { + return std::allocator<T>::allocate(n, hint); + } + } + + // Free: when trying to free the stack buffer, just mark it as free. For + // non-stack-buffer pointers, just fall though to the standard allocator. + void deallocate(pointer p, size_type n) { + if (source_ != NULL && p == source_->stack_buffer()) + source_->used_stack_buffer_ = false; + else + std::allocator<T>::deallocate(p, n); + } + + private: + Source* source_; +}; + +// A wrapper around STL containers that maintains a stack-sized buffer that the +// initial capacity of the vector is based on. Growing the container beyond the +// stack capacity will transparently overflow onto the heap. The container must +// support reserve(). +// +// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this +// type. This object is really intended to be used only internally. You'll want +// to use the wrappers below for different types. +template<typename TContainerType, int stack_capacity> +class StackContainer { + public: + typedef TContainerType ContainerType; + typedef typename ContainerType::value_type ContainedType; + typedef StackAllocator<ContainedType, stack_capacity> Allocator; + + // Allocator must be constructed before the container! + StackContainer() : allocator_(&stack_data_), container_(allocator_) { + // Make the container use the stack allocation by reserving our buffer size + // before doing anything else. + container_.reserve(stack_capacity); + } + + // Getters for the actual container. + // + // Danger: any copies of this made using the copy constructor must have + // shorter lifetimes than the source. The copy will share the same allocator + // and therefore the same stack buffer as the original. Use std::copy to + // copy into a "real" container for longer-lived objects. + ContainerType& container() { return container_; } + const ContainerType& container() const { return container_; } + + // Support operator-> to get to the container. This allows nicer syntax like: + // StackContainer<...> foo; + // std::sort(foo->begin(), foo->end()); + ContainerType* operator->() { return &container_; } + const ContainerType* operator->() const { return &container_; } + +#ifdef UNIT_TEST + // Retrieves the stack source so that that unit tests can verify that the + // buffer is being used properly. + const typename Allocator::Source& stack_data() const { + return stack_data_; + } +#endif + + protected: + typename Allocator::Source stack_data_; + Allocator allocator_; + ContainerType container_; + + DISALLOW_COPY_AND_ASSIGN(StackContainer); +}; + +// StackString ----------------------------------------------------------------- + +template<size_t stack_capacity> +class StackString : public StackContainer< + std::basic_string<char, + std::char_traits<char>, + StackAllocator<char, stack_capacity> >, + stack_capacity> { + public: + StackString() : StackContainer< + std::basic_string<char, + std::char_traits<char>, + StackAllocator<char, stack_capacity> >, + stack_capacity>() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(StackString); +}; + +// StackStrin16 ---------------------------------------------------------------- + +template<size_t stack_capacity> +class StackString16 : public StackContainer< + std::basic_string<char16, + base::string16_char_traits, + StackAllocator<char16, stack_capacity> >, + stack_capacity> { + public: + StackString16() : StackContainer< + std::basic_string<char16, + base::string16_char_traits, + StackAllocator<char16, stack_capacity> >, + stack_capacity>() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(StackString16); +}; + +// StackVector ----------------------------------------------------------------- + +// Example: +// StackVector<int, 16> foo; +// foo->push_back(22); // we have overloaded operator-> +// foo[0] = 10; // as well as operator[] +template<typename T, size_t stack_capacity> +class StackVector : public StackContainer< + std::vector<T, StackAllocator<T, stack_capacity> >, + stack_capacity> { + public: + StackVector() : StackContainer< + std::vector<T, StackAllocator<T, stack_capacity> >, + stack_capacity>() { + } + + // We need to put this in STL containers sometimes, which requires a copy + // constructor. We can't call the regular copy constructor because that will + // take the stack buffer from the original. Here, we create an empty object + // and make a stack buffer of its own. + StackVector(const StackVector<T, stack_capacity>& other) + : StackContainer< + std::vector<T, StackAllocator<T, stack_capacity> >, + stack_capacity>() { + this->container().assign(other->begin(), other->end()); + } + + StackVector<T, stack_capacity>& operator=( + const StackVector<T, stack_capacity>& other) { + this->container().assign(other->begin(), other->end()); + return *this; + } + + // Vectors are commonly indexed, which isn't very convenient even with + // operator-> (using "->at()" does exception stuff we don't want). + T& operator[](size_t i) { return this->container().operator[](i); } + const T& operator[](size_t i) const { + return this->container().operator[](i); + } +}; + +} // namespace base + +#endif // BASE_CONTAINERS_STACK_CONTAINER_H_
diff --git a/src/base/containers/stack_container_unittest.cc b/src/base/containers/stack_container_unittest.cc new file mode 100644 index 0000000..3a1b3ed --- /dev/null +++ b/src/base/containers/stack_container_unittest.cc
@@ -0,0 +1,142 @@ +// 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/containers/stack_container.h" + +#include <algorithm> + +#include "base/memory/aligned_memory.h" +#include "base/memory/ref_counted.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +class Dummy : public base::RefCounted<Dummy> { + public: + explicit Dummy(int* alive) : alive_(alive) { + ++*alive_; + } + + private: + friend class base::RefCounted<Dummy>; + + ~Dummy() { + --*alive_; + } + + int* const alive_; +}; + +} // namespace + +TEST(StackContainer, Vector) { + const int stack_size = 3; + StackVector<int, stack_size> vect; + const int* stack_buffer = &vect.stack_data().stack_buffer()[0]; + + // The initial |stack_size| elements should appear in the stack buffer. + EXPECT_EQ(static_cast<size_t>(stack_size), vect.container().capacity()); + for (int i = 0; i < stack_size; i++) { + vect.container().push_back(i); + EXPECT_EQ(stack_buffer, &vect.container()[0]); + EXPECT_TRUE(vect.stack_data().used_stack_buffer_); + } + + // Adding more elements should push the array onto the heap. + for (int i = 0; i < stack_size; i++) { + vect.container().push_back(i + stack_size); + EXPECT_NE(stack_buffer, &vect.container()[0]); + EXPECT_FALSE(vect.stack_data().used_stack_buffer_); + } + + // The array should still be in order. + for (int i = 0; i < stack_size * 2; i++) + EXPECT_EQ(i, vect.container()[i]); + + // Resize to smaller. Our STL implementation won't reallocate in this case, + // otherwise it might use our stack buffer. We reserve right after the resize + // to guarantee it isn't using the stack buffer, even though it doesn't have + // much data. + vect.container().resize(stack_size); + vect.container().reserve(stack_size * 2); + EXPECT_FALSE(vect.stack_data().used_stack_buffer_); + + // Copying the small vector to another should use the same allocator and use + // the now-unused stack buffer. GENERALLY CALLERS SHOULD NOT DO THIS since + // they have to get the template types just right and it can cause errors. + std::vector<int, StackAllocator<int, stack_size> > other(vect.container()); + EXPECT_EQ(stack_buffer, &other.front()); + EXPECT_TRUE(vect.stack_data().used_stack_buffer_); + for (int i = 0; i < stack_size; i++) + EXPECT_EQ(i, other[i]); +} + +TEST(StackContainer, VectorDoubleDelete) { + // Regression testing for double-delete. + typedef StackVector<scoped_refptr<Dummy>, 2> Vector; + typedef Vector::ContainerType Container; + Vector vect; + + int alive = 0; + scoped_refptr<Dummy> dummy(new Dummy(&alive)); + EXPECT_EQ(alive, 1); + + vect->push_back(dummy); + EXPECT_EQ(alive, 1); + + Dummy* dummy_unref = dummy.get(); + dummy = NULL; + EXPECT_EQ(alive, 1); + + Container::iterator itr = std::find(vect->begin(), vect->end(), dummy_unref); + EXPECT_EQ(itr->get(), dummy_unref); + vect->erase(itr); + EXPECT_EQ(alive, 0); + + // Shouldn't crash at exit. +} + +namespace { + +template <size_t alignment> +class AlignedData { + public: + AlignedData() { memset(data_.void_data(), 0, alignment); } + ~AlignedData() {} + base::AlignedMemory<alignment, alignment> data_; +}; + +} // anonymous namespace + +#define EXPECT_ALIGNED(ptr, align) \ + EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(ptr) & (align - 1)) + +TEST(StackContainer, BufferAlignment) { + StackVector<wchar_t, 16> text; + text->push_back(L'A'); + EXPECT_ALIGNED(&text[0], ALIGNOF(wchar_t)); + + StackVector<double, 1> doubles; + doubles->push_back(0.0); + EXPECT_ALIGNED(&doubles[0], ALIGNOF(double)); + + StackVector<AlignedData<16>, 1> aligned16; + aligned16->push_back(AlignedData<16>()); + EXPECT_ALIGNED(&aligned16[0], 16); + +#if !defined(OS_ANDROID) + // It seems that android doesn't respect greater than 16 byte alignment for + // non-POD data on the stack, even though ALIGNOF(aligned256) == 256. + StackVector<AlignedData<256>, 1> aligned256; + aligned256->push_back(AlignedData<256>()); + EXPECT_ALIGNED(&aligned256[0], 256); +#endif +} + +template class StackVector<int, 2>; +template class StackVector<scoped_refptr<Dummy>, 2>; + +} // namespace base
diff --git a/src/base/cpu.cc b/src/base/cpu.cc new file mode 100644 index 0000000..cf4f2f1 --- /dev/null +++ b/src/base/cpu.cc
@@ -0,0 +1,140 @@ +// 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/cpu.h" + +#include <string.h> + +#include <algorithm> + +#include "build/build_config.h" + +#if defined(ARCH_CPU_X86_FAMILY) +#if defined(_MSC_VER) +#include <intrin.h> +#endif +#endif + +namespace base { + +CPU::CPU() + : type_(0), + family_(0), + model_(0), + stepping_(0), + ext_model_(0), + ext_family_(0), + has_mmx_(false), + has_sse_(false), + has_sse2_(false), + has_sse3_(false), + has_ssse3_(false), + has_sse41_(false), + has_sse42_(false), + cpu_vendor_("unknown") { + Initialize(); +} + +#if defined(ARCH_CPU_X86_FAMILY) +#ifndef _MSC_VER + +#if defined(__pic__) && defined(__i386__) + +void __cpuid(int cpu_info[4], int info_type) { + __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) + ); +} + +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) { + __asm__ volatile ( + "cpuid \n\t" + : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3]) + : "a"(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 +#endif // _MSC_VER +#endif // ARCH_CPU_X86_FAMILY + +void CPU::Initialize() { +#if defined(ARCH_CPU_X86_FAMILY) + int cpu_info[4] = {-1}; + char cpu_string[48]; + + // __cpuid with an InfoType argument of 0 returns the number of + // valid Ids in CPUInfo[0] and the CPU identification string in + // the other three array elements. The CPU identification string is + // not in linear order. The code below arranges the information + // in a human readable form. The human readable order is CPUInfo[1] | + // CPUInfo[3] | CPUInfo[2]. CPUInfo[2] and CPUInfo[3] are swapped + // before using memcpy to copy these three array elements to cpu_string. + __cpuid(cpu_info, 0); + int num_ids = cpu_info[0]; + std::swap(cpu_info[2], cpu_info[3]); + memcpy(cpu_string, &cpu_info[1], 3 * sizeof(cpu_info[1])); + cpu_vendor_.assign(cpu_string, 3 * sizeof(cpu_info[1])); + + // Interpret CPU feature information. + if (num_ids > 0) { + __cpuid(cpu_info, 1); + stepping_ = cpu_info[0] & 0xf; + model_ = ((cpu_info[0] >> 4) & 0xf) + ((cpu_info[0] >> 12) & 0xf0); + family_ = (cpu_info[0] >> 8) & 0xf; + 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_ssse3_ = (cpu_info[2] & 0x00000200) != 0; + has_sse41_ = (cpu_info[2] & 0x00080000) != 0; + has_sse42_ = (cpu_info[2] & 0x00100000) != 0; + } + + // Get the brand string of the cpu. + __cpuid(cpu_info, 0x80000000); + const int parameter_end = 0x80000004; + + if (cpu_info[0] >= parameter_end) { + char* cpu_string_ptr = cpu_string; + + for (int parameter = 0x80000002; parameter <= parameter_end && + cpu_string_ptr < &cpu_string[sizeof(cpu_string)]; parameter++) { + __cpuid(cpu_info, parameter); + memcpy(cpu_string_ptr, cpu_info, sizeof(cpu_info)); + cpu_string_ptr += sizeof(cpu_info); + } + cpu_brand_.assign(cpu_string, cpu_string_ptr - cpu_string); + } +#endif +} + +} // namespace base
diff --git a/src/base/cpu.h b/src/base/cpu.h new file mode 100644 index 0000000..957b1a5 --- /dev/null +++ b/src/base/cpu.h
@@ -0,0 +1,60 @@ +// 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_CPU_H_ +#define BASE_CPU_H_ + +#include <string> + +#include "base/base_export.h" + +namespace base { + +// Query information about the processor. +class BASE_EXPORT CPU { + public: + // Constructor + CPU(); + + // Accessors for CPU information. + const std::string& vendor_name() const { return cpu_vendor_; } + int stepping() const { return stepping_; } + int model() const { return model_; } + int family() const { return family_; } + int type() const { return type_; } + int extended_model() const { return ext_model_; } + int extended_family() const { return ext_family_; } + bool has_mmx() const { return has_mmx_; } + bool has_sse() const { return has_sse_; } + bool has_sse2() const { return has_sse2_; } + bool has_sse3() const { return has_sse3_; } + bool has_ssse3() const { return has_ssse3_; } + bool has_sse41() const { return has_sse41_; } + bool has_sse42() const { return has_sse42_; } + const std::string& cpu_brand() const { return cpu_brand_; } + + private: + // Query the processor for CPUID information. + void Initialize(); + + int type_; // process type + int family_; // family of the processor + int model_; // model of processor + int stepping_; // processor revision number + int ext_model_; + int ext_family_; + bool has_mmx_; + bool has_sse_; + bool has_sse2_; + bool has_sse3_; + bool has_ssse3_; + bool has_sse41_; + bool has_sse42_; + std::string cpu_vendor_; + std::string cpu_brand_; +}; + +} // namespace base + +#endif // BASE_CPU_H_
diff --git a/src/base/cpu_unittest.cc b/src/base/cpu_unittest.cc new file mode 100644 index 0000000..d059dee --- /dev/null +++ b/src/base/cpu_unittest.cc
@@ -0,0 +1,92 @@ +// 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/cpu.h" +#include "build/build_config.h" + +#include "testing/gtest/include/gtest/gtest.h" + +// Tests whether we can run extended instructions represented by the CPU +// information. This test actually executes some extended instructions (such as +// MMX, SSE, etc.) supported by the CPU and sees we can run them without +// "undefined instruction" exceptions. That is, this test succeeds when this +// test finishes without a crash. +TEST(CPU, RunExtendedInstructions) { +#if defined(ARCH_CPU_X86_FAMILY) + // Retrieve the CPU information. + base::CPU cpu; + +#if defined(OS_WIN) + ASSERT_TRUE(cpu.has_mmx()); + + // Execute an MMX instruction. + __asm emms; + + if (cpu.has_sse()) { + // Execute an SSE instruction. + __asm xorps xmm0, xmm0; + } + + if (cpu.has_sse2()) { + // Execute an SSE 2 instruction. + __asm psrldq xmm0, 0; + } + + if (cpu.has_sse3()) { + // Execute an SSE 3 instruction. + __asm addsubpd xmm0, xmm0; + } + + if (cpu.has_ssse3()) { + // Execute a Supplimental SSE 3 instruction. + __asm psignb xmm0, xmm0; + } + + if (cpu.has_sse41()) { + // Execute an SSE 4.1 instruction. + __asm pmuldq xmm0, xmm0; + } + + if (cpu.has_sse42()) { + // Execute an SSE 4.2 instruction. + __asm crc32 eax, eax; + } +#elif defined(OS_POSIX) && defined(__x86_64__) + ASSERT_TRUE(cpu.has_mmx()); + + // Execute an MMX instruction. + __asm__ __volatile__("emms\n" : : : "mm0"); + + if (cpu.has_sse()) { + // Execute an SSE instruction. + __asm__ __volatile__("xorps %%xmm0, %%xmm0\n" : : : "xmm0"); + } + + if (cpu.has_sse2()) { + // Execute an SSE 2 instruction. + __asm__ __volatile__("psrldq $0, %%xmm0\n" : : : "xmm0"); + } + + if (cpu.has_sse3()) { + // Execute an SSE 3 instruction. + __asm__ __volatile__("addsubpd %%xmm0, %%xmm0\n" : : : "xmm0"); + } + + if (cpu.has_ssse3()) { + // Execute a Supplimental SSE 3 instruction. + __asm__ __volatile__("psignb %%xmm0, %%xmm0\n" : : : "xmm0"); + } + + if (cpu.has_sse41()) { + // Execute an SSE 4.1 instruction. + __asm__ __volatile__("pmuldq %%xmm0, %%xmm0\n" : : : "xmm0"); + } + + if (cpu.has_sse42()) { + // Execute an SSE 4.2 instruction. + __asm__ __volatile__("crc32 %%eax, %%eax\n" : : : "eax"); + } +#endif +#endif +}
diff --git a/src/base/critical_closure.h b/src/base/critical_closure.h new file mode 100644 index 0000000..ca51ed5 --- /dev/null +++ b/src/base/critical_closure.h
@@ -0,0 +1,37 @@ +// 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_CRITICAL_CLOSURE_H_ +#define BASE_CRITICAL_CLOSURE_H_ + +#include "base/callback.h" + +namespace base { + +// Returns a closure that will continue to run for a period of time when the +// application goes to the background if possible on platforms where +// applications don't execute while backgrounded, otherwise the original task is +// returned. +// +// Example: +// file_message_loop_proxy_->PostTask( +// FROM_HERE, +// MakeCriticalClosure(base::Bind(&WriteToDiskTask, path_, data))); +// +// Note new closures might be posted in this closure. If the new closures need +// background running time, |MakeCriticalClosure| should be applied on them +// before posting. +#if defined(OS_IOS) +base::Closure MakeCriticalClosure(const base::Closure& closure); +#else +inline base::Closure MakeCriticalClosure(const base::Closure& closure) { + // No-op for platforms where the application does not need to acquire + // background time for closures to finish when it goes into the background. + return closure; +} +#endif // !defined(OS_IOS) + +} // namespace base + +#endif // BASE_CRITICAL_CLOSURE_H_
diff --git a/src/base/critical_closure_ios.mm b/src/base/critical_closure_ios.mm new file mode 100644 index 0000000..156612b --- /dev/null +++ b/src/base/critical_closure_ios.mm
@@ -0,0 +1,52 @@ +// 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/critical_closure.h" + +#import <UIKit/UIKit.h> + +#include "base/bind.h" +#include "base/ios/scoped_critical_action.h" +#include "base/memory/ref_counted.h" + +namespace { + +// This class wraps a closure so it can continue to run for a period of time +// when the application goes to the background by using +// |base::ios::ScopedCriticalAction|. +class CriticalClosure : public base::RefCountedThreadSafe<CriticalClosure> { + public: + explicit CriticalClosure(base::Closure* closure) : closure_(closure) { + background_scope_.reset(new base::ios::ScopedCriticalAction()); + } + + void Run() { + closure_->Run(); + + background_scope_.reset(); + } + + private: + friend class base::RefCountedThreadSafe<CriticalClosure>; + + virtual ~CriticalClosure() {} + + scoped_ptr<base::Closure> closure_; + scoped_ptr<base::ios::ScopedCriticalAction> background_scope_; + + DISALLOW_COPY_AND_ASSIGN(CriticalClosure); +}; + +} // namespace + +namespace base { + +base::Closure MakeCriticalClosure(const base::Closure& closure) { + DCHECK([[UIDevice currentDevice] isMultitaskingSupported]); + scoped_refptr<CriticalClosure> critical_closure( + new CriticalClosure(new base::Closure(closure))); + return base::Bind(&CriticalClosure::Run, critical_closure.get()); +} + +} // namespace base
diff --git a/src/base/data/file_util_unittest/binary_file.bin b/src/base/data/file_util_unittest/binary_file.bin new file mode 100644 index 0000000..f53cc82 --- /dev/null +++ b/src/base/data/file_util_unittest/binary_file.bin Binary files differ
diff --git a/src/base/data/file_util_unittest/binary_file_diff.bin b/src/base/data/file_util_unittest/binary_file_diff.bin new file mode 100644 index 0000000..103b26d --- /dev/null +++ b/src/base/data/file_util_unittest/binary_file_diff.bin Binary files differ
diff --git a/src/base/data/file_util_unittest/binary_file_same.bin b/src/base/data/file_util_unittest/binary_file_same.bin new file mode 100644 index 0000000..f53cc82 --- /dev/null +++ b/src/base/data/file_util_unittest/binary_file_same.bin Binary files differ
diff --git a/src/base/data/file_util_unittest/blank_line.txt b/src/base/data/file_util_unittest/blank_line.txt new file mode 100644 index 0000000..8892069 --- /dev/null +++ b/src/base/data/file_util_unittest/blank_line.txt
@@ -0,0 +1,3 @@ +The next line is blank. + +But this one isn't.
diff --git a/src/base/data/file_util_unittest/blank_line_crlf.txt b/src/base/data/file_util_unittest/blank_line_crlf.txt new file mode 100644 index 0000000..3aefe52 --- /dev/null +++ b/src/base/data/file_util_unittest/blank_line_crlf.txt
@@ -0,0 +1,3 @@ +The next line is blank. + +But this one isn't.
diff --git a/src/base/data/file_util_unittest/crlf.txt b/src/base/data/file_util_unittest/crlf.txt new file mode 100644 index 0000000..0e62728 --- /dev/null +++ b/src/base/data/file_util_unittest/crlf.txt
@@ -0,0 +1 @@ +This file is the same.
diff --git a/src/base/data/file_util_unittest/different.txt b/src/base/data/file_util_unittest/different.txt new file mode 100644 index 0000000..5b9f9c4 --- /dev/null +++ b/src/base/data/file_util_unittest/different.txt
@@ -0,0 +1 @@ +This file is different.
diff --git a/src/base/data/file_util_unittest/different_first.txt b/src/base/data/file_util_unittest/different_first.txt new file mode 100644 index 0000000..8661d66 --- /dev/null +++ b/src/base/data/file_util_unittest/different_first.txt
@@ -0,0 +1 @@ +this file is the same.
diff --git a/src/base/data/file_util_unittest/different_last.txt b/src/base/data/file_util_unittest/different_last.txt new file mode 100644 index 0000000..e8b3e5a --- /dev/null +++ b/src/base/data/file_util_unittest/different_last.txt
@@ -0,0 +1 @@ +This file is the same. \ No newline at end of file
diff --git a/src/base/data/file_util_unittest/empty1.txt b/src/base/data/file_util_unittest/empty1.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/base/data/file_util_unittest/empty1.txt
diff --git a/src/base/data/file_util_unittest/empty2.txt b/src/base/data/file_util_unittest/empty2.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/base/data/file_util_unittest/empty2.txt
diff --git a/src/base/data/file_util_unittest/first1.txt b/src/base/data/file_util_unittest/first1.txt new file mode 100644 index 0000000..2c6e300 --- /dev/null +++ b/src/base/data/file_util_unittest/first1.txt
@@ -0,0 +1,2 @@ +The first line is the same. +The second line is different.
diff --git a/src/base/data/file_util_unittest/first2.txt b/src/base/data/file_util_unittest/first2.txt new file mode 100644 index 0000000..e39b5ec --- /dev/null +++ b/src/base/data/file_util_unittest/first2.txt
@@ -0,0 +1,2 @@ +The first line is the same. +The second line is not.
diff --git a/src/base/data/file_util_unittest/original.txt b/src/base/data/file_util_unittest/original.txt new file mode 100644 index 0000000..4422f57 --- /dev/null +++ b/src/base/data/file_util_unittest/original.txt
@@ -0,0 +1 @@ +This file is the same.
diff --git a/src/base/data/file_util_unittest/same.txt b/src/base/data/file_util_unittest/same.txt new file mode 100644 index 0000000..4422f57 --- /dev/null +++ b/src/base/data/file_util_unittest/same.txt
@@ -0,0 +1 @@ +This file is the same.
diff --git a/src/base/data/file_util_unittest/same_length.txt b/src/base/data/file_util_unittest/same_length.txt new file mode 100644 index 0000000..157405c --- /dev/null +++ b/src/base/data/file_util_unittest/same_length.txt
@@ -0,0 +1 @@ +This file is not same.
diff --git a/src/base/data/file_util_unittest/shortened.txt b/src/base/data/file_util_unittest/shortened.txt new file mode 100644 index 0000000..2bee82c --- /dev/null +++ b/src/base/data/file_util_unittest/shortened.txt
@@ -0,0 +1 @@ +This file is the \ No newline at end of file
diff --git a/src/base/data/file_version_info_unittest/FileVersionInfoTest1.dll b/src/base/data/file_version_info_unittest/FileVersionInfoTest1.dll new file mode 100755 index 0000000..bdf8dc0 --- /dev/null +++ b/src/base/data/file_version_info_unittest/FileVersionInfoTest1.dll Binary files differ
diff --git a/src/base/data/file_version_info_unittest/FileVersionInfoTest2.dll b/src/base/data/file_version_info_unittest/FileVersionInfoTest2.dll new file mode 100755 index 0000000..51e7966 --- /dev/null +++ b/src/base/data/file_version_info_unittest/FileVersionInfoTest2.dll Binary files differ
diff --git a/src/base/data/json/bom_feff.json b/src/base/data/json/bom_feff.json new file mode 100644 index 0000000..b05ae50 --- /dev/null +++ b/src/base/data/json/bom_feff.json
@@ -0,0 +1,10 @@ +{ + "appName": { + "message": "Gmail", + "description": "App name." + }, + "appDesc": { + "message": "بريد إلكتروني يوفر إمكانية البحث مع مقدار أقل من الرسائل غير المرغوب فيها.", + "description":"App description." + } +} \ No newline at end of file
diff --git a/src/base/debug/alias.cc b/src/base/debug/alias.cc new file mode 100644 index 0000000..6b0caaa --- /dev/null +++ b/src/base/debug/alias.cc
@@ -0,0 +1,23 @@ +// 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/debug/alias.h" +#include "build/build_config.h" + +namespace base { +namespace debug { + +#if defined(COMPILER_MSVC) +#pragma optimize("", off) +#endif + +void Alias(const void* var) { +} + +#if defined(COMPILER_MSVC) +#pragma optimize("", on) +#endif + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/alias.h b/src/base/debug/alias.h new file mode 100644 index 0000000..3b2ab64 --- /dev/null +++ b/src/base/debug/alias.h
@@ -0,0 +1,21 @@ +// 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. + +#ifndef BASE_DEBUG_ALIAS_H_ +#define BASE_DEBUG_ALIAS_H_ + +#include "base/base_export.h" + +namespace base { +namespace debug { + +// Make the optimizer think that var is aliased. This is to prevent it from +// optimizing out variables that that would not otherwise be live at the point +// of a potential crash. +void BASE_EXPORT Alias(const void* var); + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_ALIAS_H_
diff --git a/src/base/debug/debug_on_start_win.cc b/src/base/debug/debug_on_start_win.cc new file mode 100644 index 0000000..6ca88dd --- /dev/null +++ b/src/base/debug/debug_on_start_win.cc
@@ -0,0 +1,74 @@ +// 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/debug/debug_on_start_win.h" + +#include <windows.h> + +#include "base/base_switches.h" +#include "base/basictypes.h" +#include "base/debug/debugger.h" + +namespace base { +namespace debug { + +// Minimalist implementation to try to find a command line argument. We can use +// kernel32 exported functions but not the CRT functions because we're too early +// in the process startup. +// The code is not that bright and will find things like ---argument or +// /-/argument. +// Note: command_line is non-destructively modified. +bool DebugOnStart::FindArgument(wchar_t* command_line, const char* argument_c) { + wchar_t argument[50] = {}; + for (int i = 0; argument_c[i]; ++i) + argument[i] = argument_c[i]; + + int argument_len = lstrlen(argument); + int command_line_len = lstrlen(command_line); + while (command_line_len > argument_len) { + wchar_t first_char = command_line[0]; + wchar_t last_char = command_line[argument_len+1]; + // Try to find an argument. + if ((first_char == L'-' || first_char == L'/') && + (last_char == L' ' || last_char == 0 || last_char == L'=')) { + command_line[argument_len+1] = 0; + // Skip the - or / + if (lstrcmpi(command_line+1, argument) == 0) { + // Found it. + command_line[argument_len+1] = last_char; + return true; + } + // Fix back. + command_line[argument_len+1] = last_char; + } + // Continue searching. + ++command_line; + --command_line_len; + } + return false; +} + +// static +int __cdecl DebugOnStart::Init() { + // Try to find the argument. + if (FindArgument(GetCommandLine(), switches::kDebugOnStart)) { + // We can do 2 things here: + // - Ask for a debugger to attach to us. This involve reading the registry + // key and creating the process. + // - Do a int3. + + // It will fails if we run in a sandbox. That is expected. + base::debug::SpawnDebuggerOnProcess(GetCurrentProcessId()); + + // Wait for a debugger to come take us. + base::debug::WaitForDebugger(60, false); + } else if (FindArgument(GetCommandLine(), switches::kWaitForDebugger)) { + // Wait for a debugger to come take us. + base::debug::WaitForDebugger(60, true); + } + return 0; +} + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/debug_on_start_win.h b/src/base/debug/debug_on_start_win.h new file mode 100644 index 0000000..edcaa0a --- /dev/null +++ b/src/base/debug/debug_on_start_win.h
@@ -0,0 +1,83 @@ +// 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. + +// Define the necessary code and global data to look for kDebugOnStart command +// line argument. When the command line argument is detected, it invokes the +// debugger, if no system-wide debugger is registered, a debug break is done. + +#ifndef BASE_DEBUG_DEBUG_ON_START_WIN_H_ +#define BASE_DEBUG_DEBUG_ON_START_WIN_H_ + +#include "base/basictypes.h" +#include "build/build_config.h" + +// This only works on Windows. It's legal to include on other platforms, but +// will be a NOP. +#if defined(OS_WIN) + +#ifndef DECLSPEC_SELECTANY +#define DECLSPEC_SELECTANY __declspec(selectany) +#endif + +namespace base { +namespace debug { + +// There is no way for this code, as currently implemented, to work across DLLs. +// TODO(rvargas): It looks like we really don't use this code, at least not for +// Chrome. Figure out if it's really worth implementing something simpler. +#if !defined(COMPONENT_BUILD) + +// Debug on start functions and data. +class DebugOnStart { + public: + // Expected function type in the .CRT$XI* section. + // Note: See VC\crt\src\internal.h for reference. + typedef int (__cdecl *PIFV)(void); + + // Looks at the command line for kDebugOnStart argument. If found, it invokes + // the debugger, if this fails, it crashes. + static int __cdecl Init(); + + // Returns true if the 'argument' is present in the 'command_line'. It does + // not use the CRT, only Kernel32 functions. + static bool FindArgument(wchar_t* command_line, const char* argument); +}; + +// Set the function pointer to our function to look for a crash on start. The +// XIB section is started pretty early in the program initialization so in +// theory it should be called before any user created global variable +// initialization code and CRT initialization code. +// Note: See VC\crt\src\defsects.inc and VC\crt\src\crt0.c for reference. +#ifdef _WIN64 + +// "Fix" the segment. On x64, the .CRT segment is merged into the .rdata segment +// so it contains const data only. +#pragma const_seg(push, ".CRT$XIB") +// Declare the pointer so the CRT will find it. +extern const DebugOnStart::PIFV debug_on_start; +DECLSPEC_SELECTANY const DebugOnStart::PIFV debug_on_start = + &DebugOnStart::Init; +// Fix back the segment. +#pragma const_seg(pop) + +#else // _WIN64 + +// "Fix" the segment. On x86, the .CRT segment is merged into the .data segment +// so it contains non-const data only. +#pragma data_seg(push, ".CRT$XIB") +// Declare the pointer so the CRT will find it. +DECLSPEC_SELECTANY DebugOnStart::PIFV debug_on_start = &DebugOnStart::Init; +// Fix back the segment. +#pragma data_seg(pop) + +#endif // _WIN64 + +#endif // defined(COMPONENT_BUILD) + +} // namespace debug +} // namespace base + +#endif // defined(OS_WIN) + +#endif // BASE_DEBUG_DEBUG_ON_START_WIN_H_
diff --git a/src/base/debug/debugger.cc b/src/base/debug/debugger.cc new file mode 100644 index 0000000..a297ed1 --- /dev/null +++ b/src/base/debug/debugger.cc
@@ -0,0 +1,47 @@ +// 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/debug/debugger.h" +#include "base/logging.h" +#include "base/threading/platform_thread.h" + +namespace base { +namespace debug { + +static bool is_debug_ui_suppressed = false; + +bool WaitForDebugger(int wait_seconds, bool silent) { +#if defined(OS_ANDROID) + // The pid from which we know which process to attach to are not output by + // android ddms, so we have to print it out explicitly. + DLOG(INFO) << "DebugUtil::WaitForDebugger(pid=" << static_cast<int>(getpid()) + << ")"; +#endif + for (int i = 0; i < wait_seconds * 10; ++i) { + if (BeingDebugged()) { + if (!silent) { +#if defined(__LB_ANDROID__) + // Hack around a race condition where the debugger is still attaching + // and isn't ready to catch the trap thrown by BreakDebugger yet. + PlatformThread::Sleep(TimeDelta::FromSeconds(5)); +#endif + BreakDebugger(); + } + return true; + } + PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); + } + return false; +} + +void SetSuppressDebugUI(bool suppress) { + is_debug_ui_suppressed = suppress; +} + +bool IsDebugUISuppressed() { + return is_debug_ui_suppressed; +} + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/debugger.h b/src/base/debug/debugger.h new file mode 100644 index 0000000..4f368d9 --- /dev/null +++ b/src/base/debug/debugger.h
@@ -0,0 +1,48 @@ +// 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 is a cross platform interface for helper functions related to +// debuggers. You should use this to test if you're running under a debugger, +// and if you would like to yield (breakpoint) into the debugger. + +#ifndef BASE_DEBUG_DEBUGGER_H +#define BASE_DEBUG_DEBUGGER_H + +#include "base/base_export.h" + +namespace base { +namespace debug { + +// Starts the registered system-wide JIT debugger to attach it to specified +// process. +BASE_EXPORT bool SpawnDebuggerOnProcess(unsigned process_id); + +// Waits wait_seconds seconds for a debugger to attach to the current process. +// When silent is false, an exception is thrown when a debugger is detected. +BASE_EXPORT bool WaitForDebugger(int wait_seconds, bool silent); + +// Returns true if the given process is being run under a debugger. +// +// On OS X, the underlying mechanism doesn't work when the sandbox is enabled. +// To get around this, this function caches its value. +// +// WARNING: Because of this, on OS X, a call MUST be made to this function +// BEFORE the sandbox is enabled. +BASE_EXPORT bool BeingDebugged(); + +// Break into the debugger, assumes a debugger is present. +BASE_EXPORT void BreakDebugger(); + +// Used in test code, this controls whether showing dialogs and breaking into +// the debugger is suppressed for debug errors, even in debug mode (normally +// release mode doesn't do this stuff -- this is controlled separately). +// Normally UI is not suppressed. This is normally used when running automated +// tests where we want a crash rather than a dialog or a debugger. +BASE_EXPORT void SetSuppressDebugUI(bool suppress); +BASE_EXPORT bool IsDebugUISuppressed(); + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_DEBUGGER_H
diff --git a/src/base/debug/debugger_posix.cc b/src/base/debug/debugger_posix.cc new file mode 100644 index 0000000..edfbbe1 --- /dev/null +++ b/src/base/debug/debugger_posix.cc
@@ -0,0 +1,273 @@ +// 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/debug/debugger.h" +#include "build/build_config.h" + +#if defined(__LB_SHELL__) +#include "lb_platform.h" +#endif + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> +#include <vector> + +#if !defined(OS_ANDROID) && !defined(OS_NACL) +#include <execinfo.h> +#endif + +#if defined(__GLIBCXX__) +#include <cxxabi.h> +#endif + +#if defined(OS_MACOSX) +#include <AvailabilityMacros.h> +#endif + +#if defined(OS_MACOSX) || defined(OS_BSD) +#include <sys/sysctl.h> +#endif + +#if defined(OS_FREEBSD) +#include <sys/user.h> +#endif + +#include <ostream> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "base/safe_strerror_posix.h" +#include "base/string_piece.h" +#include "base/stringprintf.h" + +#if defined(USE_SYMBOLIZE) +#include "base/third_party/symbolize/symbolize.h" +#endif + +#if defined(OS_ANDROID) +#include "base/threading/platform_thread.h" +#endif + +namespace base { +namespace debug { + +bool SpawnDebuggerOnProcess(unsigned process_id) { +#if OS_ANDROID || OS_NACL || defined(__LB_SHELL__) + NOTIMPLEMENTED(); + return false; +#else + const std::string debug_cmd = + StringPrintf("xterm -e 'gdb --pid=%u' &", process_id); + LOG(WARNING) << "Starting debugger on pid " << process_id + << " with command `" << debug_cmd << "`"; + int ret = system(debug_cmd.c_str()); + if (ret == -1) + return false; + return true; +#endif +} + +#if defined(OS_MACOSX) || defined(OS_BSD) + +// Based on Apple's recommended method as described in +// http://developer.apple.com/qa/qa2004/qa1361.html +bool BeingDebugged() { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + // + // While some code used below may be async-signal unsafe, note how + // the result is cached (see |is_set| and |being_debugged| static variables + // right below). If this code is properly warmed-up early + // in the start-up process, it should be safe to use later. + + // If the process is sandboxed then we can't use the sysctl, so cache the + // value. + static bool is_set = false; + static bool being_debugged = false; + + if (is_set) + return being_debugged; + + // Initialize mib, which tells sysctl what info we want. In this case, + // we're looking for information about a specific process ID. + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid() +#if defined(OS_OPENBSD) + , sizeof(struct kinfo_proc), + 0 +#endif + }; + + // Caution: struct kinfo_proc is marked __APPLE_API_UNSTABLE. The source and + // binary interfaces may change. + struct kinfo_proc info; + size_t info_size = sizeof(info); + +#if defined(OS_OPENBSD) + if (sysctl(mib, arraysize(mib), NULL, &info_size, NULL, 0) < 0) + return -1; + + mib[5] = (info_size / sizeof(struct kinfo_proc)); +#endif + + int sysctl_result = sysctl(mib, arraysize(mib), &info, &info_size, NULL, 0); + DCHECK_EQ(sysctl_result, 0); + if (sysctl_result != 0) { + is_set = true; + being_debugged = false; + return being_debugged; + } + + // This process is being debugged if the P_TRACED flag is set. + is_set = true; +#if defined(OS_FREEBSD) + being_debugged = (info.ki_flag & P_TRACED) != 0; +#elif defined(OS_BSD) + being_debugged = (info.p_flag & P_TRACED) != 0; +#else + being_debugged = (info.kp_proc.p_flag & P_TRACED) != 0; +#endif + return being_debugged; +} + +#elif defined(OS_LINUX) || defined(OS_ANDROID) + +// We can look in /proc/self/status for TracerPid. We are likely used in crash +// handling, so we are careful not to use the heap or have side effects. +// Another option that is common is to try to ptrace yourself, but then we +// can't detach without forking(), and that's not so great. +// static +bool BeingDebugged() { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + + int status_fd = open("/proc/self/status", O_RDONLY); + if (status_fd == -1) + return false; + + // We assume our line will be in the first 1024 characters and that we can + // read this much all at once. In practice this will generally be true. + // This simplifies and speeds up things considerably. + char buf[1024]; + + ssize_t num_read = HANDLE_EINTR(read(status_fd, buf, sizeof(buf))); + if (HANDLE_EINTR(close(status_fd)) < 0) + return false; + + if (num_read <= 0) + return false; + + StringPiece status(buf, num_read); + StringPiece tracer("TracerPid:\t"); + + StringPiece::size_type pid_index = status.find(tracer); + if (pid_index == StringPiece::npos) + return false; + + // Our pid is 0 without a debugger, assume this for any pid starting with 0. + pid_index += tracer.size(); + return pid_index < status.size() && status[pid_index] != '0'; +} + +#elif defined(__LB_SHELL__) + +bool BeingDebugged() { + return LB::Platform::BeingDebugged(); +} + +#else + +bool BeingDebugged() { + NOTIMPLEMENTED(); + return false; +} + +#endif + +// We want to break into the debugger in Debug mode, and cause a crash dump in +// Release mode. Breakpad behaves as follows: +// +// +-------+-----------------+-----------------+ +// | OS | Dump on SIGTRAP | Dump on SIGABRT | +// +-------+-----------------+-----------------+ +// | Linux | N | Y | +// | Mac | Y | N | +// +-------+-----------------+-----------------+ +// +// Thus we do the following: +// Linux: Debug mode, send SIGTRAP; Release mode, send SIGABRT. +// Mac: Always send SIGTRAP. + +#if defined(__LB_SHELL__) +#define DEBUG_BREAK() LB::Platform::DEBUG_BREAK() +#elif defined(NDEBUG) && !defined(OS_MACOSX) && !defined(OS_ANDROID) +#define DEBUG_BREAK() abort() +#elif defined(OS_NACL) +// The NaCl verifier doesn't let use use int3. For now, we call abort(). We +// 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(ARCH_CPU_ARM_FAMILY) +#if defined(OS_ANDROID) +// Though Android has a "helpful" process called debuggerd to catch native +// signals on the general assumption that they are fatal errors. The bkpt +// instruction appears to cause SIGBUS which is trapped by debuggerd, and +// we've had great difficulty continuing in a debugger once we stop from +// SIG triggered by native code. +// +// Use GDB to set |go| to 1 to resume execution. +#define DEBUG_BREAK() do { \ + if (!BeingDebugged()) { \ + abort(); \ + } else { \ + volatile int go = 0; \ + while (!go) { \ + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); \ + } \ + } \ +} while (0) +#else +// ARM && !ANDROID +#define DEBUG_BREAK() asm("bkpt 0") +#endif +#elif defined(ARCH_CPU_MIPS_FAMILY) +#define DEBUG_BREAK() asm("break 2") +#else +#define DEBUG_BREAK() asm("int3") +#endif + +void BreakDebugger() { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + + DEBUG_BREAK(); +#if (defined(OS_ANDROID) && !defined(OFFICIAL_BUILD)) || \ + (defined(__LB_ANDROID__) && !defined(__LB_SHELL__FOR_RELEASE__)) + // For Android development we always build release (debug builds are + // unmanageably large), so the unofficial build is used for debugging. It is + // helpful to be able to insert BreakDebugger() statements in the source, + // attach the debugger, inspect the state of the program and then resume it by + // setting the 'go' variable above. +#elif defined(NDEBUG) + // Terminate the program after signaling the debug break. + _exit(1); +#endif +} + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/debugger_shell.cc b/src/base/debug/debugger_shell.cc new file mode 100644 index 0000000..855777b --- /dev/null +++ b/src/base/debug/debugger_shell.cc
@@ -0,0 +1,20 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// We use the same code as posix version +#if !defined(COBALT_WIN) +#include "debugger_posix.cc" +#endif
diff --git a/src/base/debug/debugger_starboard.cc b/src/base/debug/debugger_starboard.cc new file mode 100644 index 0000000..ee9e762 --- /dev/null +++ b/src/base/debug/debugger_starboard.cc
@@ -0,0 +1,41 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/debug/stack_trace.h" + +#include "base/logging.h" +#include "starboard/system.h" + +namespace base { +namespace debug { + +bool SpawnDebuggerOnProcess(unsigned process_id) { + NOTIMPLEMENTED(); + return false; +} + +bool BeingDebugged() { +#if defined(__LB_SHELL__FOR_RELEASE__) + return false; +#else + return SbSystemIsDebuggerAttached(); +#endif +} + +void BreakDebugger() { + SbSystemBreakIntoDebugger(); +} + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/debugger_win.cc b/src/base/debug/debugger_win.cc new file mode 100644 index 0000000..b13dbfd --- /dev/null +++ b/src/base/debug/debugger_win.cc
@@ -0,0 +1,114 @@ +// Copyright (c) 2010 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/debug/debugger.h" + +#include <windows.h> +#include <dbghelp.h> + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace base { +namespace debug { + +namespace { + +// Minimalist key reader. +// Note: Does not use the CRT. +bool RegReadString(HKEY root, const wchar_t* subkey, + const wchar_t* value_name, wchar_t* buffer, int* len) { + HKEY key = NULL; + DWORD res = RegOpenKeyEx(root, subkey, 0, KEY_READ, &key); + if (ERROR_SUCCESS != res || key == NULL) + return false; + + DWORD type = 0; + DWORD buffer_size = *len * sizeof(wchar_t); + // We don't support REG_EXPAND_SZ. + res = RegQueryValueEx(key, value_name, NULL, &type, + reinterpret_cast<BYTE*>(buffer), &buffer_size); + if (ERROR_SUCCESS == res && buffer_size != 0 && type == REG_SZ) { + // Make sure the buffer is NULL terminated. + buffer[*len - 1] = 0; + *len = lstrlen(buffer); + RegCloseKey(key); + return true; + } + RegCloseKey(key); + return false; +} + +// Replaces each "%ld" in input per a value. Not efficient but it works. +// Note: Does not use the CRT. +bool StringReplace(const wchar_t* input, int value, wchar_t* output, + int output_len) { + memset(output, 0, output_len*sizeof(wchar_t)); + int input_len = lstrlen(input); + + for (int i = 0; i < input_len; ++i) { + int current_output_len = lstrlen(output); + + if (input[i] == L'%' && input[i + 1] == L'l' && input[i + 2] == L'd') { + // Make sure we have enough place left. + if ((current_output_len + 12) >= output_len) + return false; + + // Cheap _itow(). + wsprintf(output+current_output_len, L"%d", value); + i += 2; + } else { + if (current_output_len >= output_len) + return false; + output[current_output_len] = input[i]; + } + } + return true; +} + +} // namespace + +// Note: Does not use the CRT. +bool SpawnDebuggerOnProcess(unsigned process_id) { + wchar_t reg_value[1026]; + int len = arraysize(reg_value); + if (RegReadString(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug", + L"Debugger", reg_value, &len)) { + wchar_t command_line[1026]; + if (StringReplace(reg_value, process_id, command_line, + arraysize(command_line))) { + // We don't mind if the debugger is present because it will simply fail + // to attach to this process. + STARTUPINFO startup_info = {0}; + startup_info.cb = sizeof(startup_info); + PROCESS_INFORMATION process_info = {0}; + + if (CreateProcess(NULL, command_line, NULL, NULL, FALSE, 0, NULL, NULL, + &startup_info, &process_info)) { + CloseHandle(process_info.hThread); + WaitForInputIdle(process_info.hProcess, 10000); + CloseHandle(process_info.hProcess); + return true; + } + } + } + return false; +} + +bool BeingDebugged() { + return ::IsDebuggerPresent() != 0; +} + +void BreakDebugger() { + if (IsDebugUISuppressed()) + _exit(1); + __debugbreak(); +#if defined(NDEBUG) + _exit(1); +#endif +} + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/leak_annotations.h b/src/base/debug/leak_annotations.h new file mode 100644 index 0000000..97be127 --- /dev/null +++ b/src/base/debug/leak_annotations.h
@@ -0,0 +1,56 @@ +// 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. + +#ifndef BASE_DEBUG_LEAK_ANNOTATIONS_H_ +#define BASE_DEBUG_LEAK_ANNOTATIONS_H_ + +#include "build/build_config.h" + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_NACL) && \ + defined(USE_HEAPCHECKER) + +#include "third_party/tcmalloc/chromium/src/gperftools/heap-checker.h" + +// Annotate a program scope as having memory leaks. Tcmalloc's heap leak +// checker will ignore them. Note that these annotations may mask real bugs +// and should not be used in the production code. +#define ANNOTATE_SCOPED_MEMORY_LEAK \ + HeapLeakChecker::Disabler heap_leak_checker_disabler + +// Annotate an object pointer as referencing a leaky object. This object and all +// the heap objects referenced by it will be ignored by the heap checker. +// +// X should be referencing an active allocated object. If it is not, the +// annotation will be ignored. +// No object should be annotated with ANNOTATE_SCOPED_MEMORY_LEAK twice. +// Once an object is annotated with ANNOTATE_SCOPED_MEMORY_LEAK, it cannot be +// deleted. +#define ANNOTATE_LEAKING_OBJECT_PTR(X) \ + HeapLeakChecker::IgnoreObject(X) + +#elif defined(ADDRESS_SANITIZER) +#include <sanitizer/lsan_interface.h> + +class ScopedLeakSanitizerDisabler { + public: + ScopedLeakSanitizerDisabler() { __lsan_disable(); } + ~ScopedLeakSanitizerDisabler() { __lsan_enable(); } + private: + DISALLOW_COPY_AND_ASSIGN(ScopedLeakSanitizerDisabler); +}; + +#define ANNOTATE_SCOPED_MEMORY_LEAK \ + ScopedLeakSanitizerDisabler leak_sanitizer_disabler; static_cast<void>(0) + +#define ANNOTATE_LEAKING_OBJECT_PTR(X) __lsan_ignore_object(X); + +#else + +// If tcmalloc is not used, the annotations should be no-ops. +#define ANNOTATE_SCOPED_MEMORY_LEAK ((void)0) +#define ANNOTATE_LEAKING_OBJECT_PTR(X) ((void)0) + +#endif + +#endif // BASE_DEBUG_LEAK_ANNOTATIONS_H_
diff --git a/src/base/debug/leak_tracker.h b/src/base/debug/leak_tracker.h new file mode 100644 index 0000000..93cea39 --- /dev/null +++ b/src/base/debug/leak_tracker.h
@@ -0,0 +1,136 @@ +// 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_DEBUG_LEAK_TRACKER_H_ +#define BASE_DEBUG_LEAK_TRACKER_H_ + +// Only enable leak tracking in debug builds. +#ifndef NDEBUG +#define ENABLE_LEAK_TRACKER +#endif + +#ifdef ENABLE_LEAK_TRACKER +#include "base/containers/linked_list.h" +#include "base/debug/stack_trace.h" +#include "base/logging.h" +#endif // ENABLE_LEAK_TRACKER + +// LeakTracker is a helper to verify that all instances of a class +// have been destroyed. +// +// It is particularly useful for classes that are bound to a single thread -- +// before destroying that thread, one can check that there are no remaining +// instances of that class. +// +// For example, to enable leak tracking for class net::URLRequest, start by +// adding a member variable of type LeakTracker<net::URLRequest>. +// +// class URLRequest { +// ... +// private: +// base::LeakTracker<URLRequest> leak_tracker_; +// }; +// +// +// Next, when we believe all instances of net::URLRequest have been deleted: +// +// LeakTracker<net::URLRequest>::CheckForLeaks(); +// +// Should the check fail (because there are live instances of net::URLRequest), +// then the allocation callstack for each leaked instances is dumped to +// the error log. +// +// If ENABLE_LEAK_TRACKER is not defined, then the check has no effect. + +namespace base { +namespace debug { + +#ifndef ENABLE_LEAK_TRACKER + +// If leak tracking is disabled, do nothing. +template<typename T> +class LeakTracker { + public: + ~LeakTracker() {} + static void CheckForLeaks() {} + static int NumLiveInstances() { return -1; } +}; + +#else + +// If leak tracking is enabled we track where the object was allocated from. + +template<typename T> +class LeakTracker : public LinkNode<LeakTracker<T> > { + public: + LeakTracker() { + instances()->Append(this); + } + + ~LeakTracker() { + this->RemoveFromList(); + } + + static void CheckForLeaks() { + // Walk the allocation list and print each entry it contains. + size_t count = 0; + + // Copy the first 3 leak allocation callstacks onto the stack. + // This way if we hit the CHECK() in a release build, the leak + // information will be available in mini-dump. + const size_t kMaxStackTracesToCopyOntoStack = 3; + StackTrace stacktraces[kMaxStackTracesToCopyOntoStack]; + + for (LinkNode<LeakTracker<T> >* node = instances()->head(); + node != instances()->end(); + node = node->next()) { + StackTrace& allocation_stack = node->value()->allocation_stack_; + + if (count < kMaxStackTracesToCopyOntoStack) + stacktraces[count] = allocation_stack; + + ++count; + if (LOG_IS_ON(ERROR)) { + LOG_STREAM(ERROR) << "Leaked " << node << " which was allocated by:"; + allocation_stack.OutputToStream(&LOG_STREAM(ERROR)); + } + } + + CHECK_EQ(0u, count); + + // Hack to keep |stacktraces| and |count| alive (so compiler + // doesn't optimize it out, and it will appear in mini-dumps). + if (count == 0x1234) { + for (size_t i = 0; i < kMaxStackTracesToCopyOntoStack; ++i) + stacktraces[i].PrintBacktrace(); + } + } + + static int NumLiveInstances() { + // Walk the allocation list and count how many entries it has. + int count = 0; + for (LinkNode<LeakTracker<T> >* node = instances()->head(); + node != instances()->end(); + node = node->next()) { + ++count; + } + return count; + } + + private: + // Each specialization of LeakTracker gets its own static storage. + static LinkedList<LeakTracker<T> >* instances() { + static LinkedList<LeakTracker<T> > list; + return &list; + } + + StackTrace allocation_stack_; +}; + +#endif // ENABLE_LEAK_TRACKER + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_LEAK_TRACKER_H_
diff --git a/src/base/debug/leak_tracker_unittest.cc b/src/base/debug/leak_tracker_unittest.cc new file mode 100644 index 0000000..99df4c1 --- /dev/null +++ b/src/base/debug/leak_tracker_unittest.cc
@@ -0,0 +1,113 @@ +// 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/debug/leak_tracker.h" +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace debug { + +namespace { + +class ClassA { + private: + LeakTracker<ClassA> leak_tracker_; +}; + +class ClassB { + private: + LeakTracker<ClassB> leak_tracker_; +}; + +#ifndef ENABLE_LEAK_TRACKER + +// If leak tracking is disabled, we should do nothing. +TEST(LeakTrackerTest, NotEnabled) { + EXPECT_EQ(-1, LeakTracker<ClassA>::NumLiveInstances()); + EXPECT_EQ(-1, LeakTracker<ClassB>::NumLiveInstances()); + + // Use scoped_ptr so compiler doesn't complain about unused variables. + scoped_ptr<ClassA> a1(new ClassA); + scoped_ptr<ClassB> b1(new ClassB); + scoped_ptr<ClassB> b2(new ClassB); + + EXPECT_EQ(-1, LeakTracker<ClassA>::NumLiveInstances()); + EXPECT_EQ(-1, LeakTracker<ClassB>::NumLiveInstances()); +} + +#else + +TEST(LeakTrackerTest, Basic) { + { + ClassA a1; + + EXPECT_EQ(1, LeakTracker<ClassA>::NumLiveInstances()); + EXPECT_EQ(0, LeakTracker<ClassB>::NumLiveInstances()); + + ClassB b1; + ClassB b2; + + EXPECT_EQ(1, LeakTracker<ClassA>::NumLiveInstances()); + EXPECT_EQ(2, LeakTracker<ClassB>::NumLiveInstances()); + + scoped_ptr<ClassA> a2(new ClassA); + + EXPECT_EQ(2, LeakTracker<ClassA>::NumLiveInstances()); + EXPECT_EQ(2, LeakTracker<ClassB>::NumLiveInstances()); + + a2.reset(); + + EXPECT_EQ(1, LeakTracker<ClassA>::NumLiveInstances()); + EXPECT_EQ(2, LeakTracker<ClassB>::NumLiveInstances()); + } + + EXPECT_EQ(0, LeakTracker<ClassA>::NumLiveInstances()); + EXPECT_EQ(0, LeakTracker<ClassB>::NumLiveInstances()); +} + +// Try some orderings of create/remove to hit different cases in the linked-list +// assembly. +TEST(LeakTrackerTest, LinkedList) { + EXPECT_EQ(0, LeakTracker<ClassB>::NumLiveInstances()); + + scoped_ptr<ClassA> a1(new ClassA); + scoped_ptr<ClassA> a2(new ClassA); + scoped_ptr<ClassA> a3(new ClassA); + scoped_ptr<ClassA> a4(new ClassA); + + EXPECT_EQ(4, LeakTracker<ClassA>::NumLiveInstances()); + + // Remove the head of the list (a1). + a1.reset(); + EXPECT_EQ(3, LeakTracker<ClassA>::NumLiveInstances()); + + // Remove the tail of the list (a4). + a4.reset(); + EXPECT_EQ(2, LeakTracker<ClassA>::NumLiveInstances()); + + // Append to the new tail of the list (a3). + scoped_ptr<ClassA> a5(new ClassA); + EXPECT_EQ(3, LeakTracker<ClassA>::NumLiveInstances()); + + a2.reset(); + a3.reset(); + + EXPECT_EQ(1, LeakTracker<ClassA>::NumLiveInstances()); + + a5.reset(); + EXPECT_EQ(0, LeakTracker<ClassA>::NumLiveInstances()); +} + +TEST(LeakTrackerTest, NoOpCheckForLeaks) { + // There are no live instances of ClassA, so this should do nothing. + LeakTracker<ClassA>::CheckForLeaks(); +} + +#endif // ENABLE_LEAK_TRACKER + +} // namespace + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/profiler.cc b/src/base/debug/profiler.cc new file mode 100644 index 0000000..de79124 --- /dev/null +++ b/src/base/debug/profiler.cc
@@ -0,0 +1,177 @@ +// 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/debug/profiler.h" + +#include <string> + +#include "base/process_util.h" +#include "base/string_util.h" +#include "base/stringprintf.h" + +#if defined(OS_WIN) +#include "base/win/pe_image.h" +#endif // defined(OS_WIN) + +#if defined(ENABLE_PROFILING) && !defined(NO_TCMALLOC) +#include "third_party/tcmalloc/chromium/src/gperftools/profiler.h" +#endif + +namespace base { +namespace debug { + +#if defined(ENABLE_PROFILING) && !defined(NO_TCMALLOC) + +static int profile_count = 0; + +void StartProfiling(const std::string& name) { + ++profile_count; + std::string full_name(name); + std::string pid = StringPrintf("%d", GetCurrentProcId()); + std::string count = StringPrintf("%d", profile_count); + ReplaceSubstringsAfterOffset(&full_name, 0, "{pid}", pid); + ReplaceSubstringsAfterOffset(&full_name, 0, "{count}", count); + ProfilerStart(full_name.c_str()); +} + +void StopProfiling() { + ProfilerFlush(); + ProfilerStop(); +} + +void FlushProfiling() { + ProfilerFlush(); +} + +bool BeingProfiled() { + return ProfilingIsEnabledForAllThreads(); +} + +void RestartProfilingAfterFork() { + ProfilerRegisterThread(); +} + +#else + +void StartProfiling(const std::string& name) { +} + +void StopProfiling() { +} + +void FlushProfiling() { +} + +bool BeingProfiled() { + return false; +} + +void RestartProfilingAfterFork() { +} + +#endif + +#if !defined(OS_WIN) + +bool IsBinaryInstrumented() { + return false; +} + +ReturnAddressLocationResolver GetProfilerReturnAddrResolutionFunc() { + return NULL; +} + +#else // defined(OS_WIN) + +// http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx +extern "C" IMAGE_DOS_HEADER __ImageBase; + +bool IsBinaryInstrumented() { + enum InstrumentationCheckState { + UNINITIALIZED, + INSTRUMENTED_IMAGE, + NON_INSTRUMENTED_IMAGE, + }; + + static InstrumentationCheckState state = UNINITIALIZED; + + if (state == UNINITIALIZED) { + HMODULE this_module = reinterpret_cast<HMODULE>(&__ImageBase); + base::win::PEImage image(this_module); + + // Check to be sure our image is structured as we'd expect. + DCHECK(image.VerifyMagic()); + + // Syzygy-instrumented binaries contain a PE image section named ".thunks", + // and all Syzygy-modified binaries contain the ".syzygy" image section. + // This is a very fast check, as it only looks at the image header. + if ((image.GetImageSectionHeaderByName(".thunks") != NULL) && + (image.GetImageSectionHeaderByName(".syzygy") != NULL)) { + state = INSTRUMENTED_IMAGE; + } else { + state = NON_INSTRUMENTED_IMAGE; + } + } + DCHECK(state != UNINITIALIZED); + + return state == INSTRUMENTED_IMAGE; +} + +// Callback function to PEImage::EnumImportChunks. +static bool FindResolutionFunctionInImports( + const base::win::PEImage &image, const char* module_name, + PIMAGE_THUNK_DATA unused_name_table, PIMAGE_THUNK_DATA import_address_table, + PVOID cookie) { + // Our import address table contains pointers to the functions we import + // at this point. Let's retrieve the first such function and use it to + // find the module this import was resolved to by the loader. + const wchar_t* function_in_module = + reinterpret_cast<const wchar_t*>(import_address_table->u1.Function); + + // Retrieve the module by a function in the module. + const DWORD kFlags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT; + HMODULE module = NULL; + if (!::GetModuleHandleEx(kFlags, function_in_module, &module)) { + // This can happen if someone IAT patches us to a thunk. + return true; + } + + // See whether this module exports the function we're looking for. + ReturnAddressLocationResolver exported_func = + reinterpret_cast<ReturnAddressLocationResolver>( + ::GetProcAddress(module, "ResolveReturnAddressLocation")); + + if (exported_func != NULL) { + ReturnAddressLocationResolver* resolver_func = + reinterpret_cast<ReturnAddressLocationResolver*>(cookie); + DCHECK(resolver_func != NULL); + DCHECK(*resolver_func == NULL); + + // We found it, return the function and terminate the enumeration. + *resolver_func = exported_func; + return false; + } + + // Keep going. + return true; +} + +ReturnAddressLocationResolver GetProfilerReturnAddrResolutionFunc() { + if (!IsBinaryInstrumented()) + return NULL; + + HMODULE this_module = reinterpret_cast<HMODULE>(&__ImageBase); + base::win::PEImage image(this_module); + + ReturnAddressLocationResolver resolver_func = NULL; + image.EnumImportChunks(FindResolutionFunctionInImports, &resolver_func); + + return resolver_func; +} + +#endif // defined(OS_WIN) + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/profiler.h b/src/base/debug/profiler.h new file mode 100644 index 0000000..d703876 --- /dev/null +++ b/src/base/debug/profiler.h
@@ -0,0 +1,67 @@ +// 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_DEBUG_PROFILER_H +#define BASE_DEBUG_PROFILER_H + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" + +// The Profiler functions allow usage of the underlying sampling based +// profiler. If the application has not been built with the necessary +// flags (-DENABLE_PROFILING and not -DNO_TCMALLOC) then these functions +// are noops. +namespace base { +namespace debug { + +// Start profiling with the supplied name. +// {pid} will be replaced by the process' pid and {count} will be replaced +// by the count of the profile run (starts at 1 with each process). +BASE_EXPORT void StartProfiling(const std::string& name); + +// Stop profiling and write out data. +BASE_EXPORT void StopProfiling(); + +// Force data to be written to file. +BASE_EXPORT void FlushProfiling(); + +// Returns true if process is being profiled. +BASE_EXPORT bool BeingProfiled(); + +// Reset profiling after a fork, which disables timers. +BASE_EXPORT void RestartProfilingAfterFork(); + +// Returns true iff this executable is instrumented with the Syzygy profiler. +BASE_EXPORT bool IsBinaryInstrumented(); + +// There's a class of profilers that use "return address swizzling" to get a +// hook on function exits. This class of profilers uses some form of entry hook, +// like e.g. binary instrumentation, or a compiler flag, that calls a hook each +// time a function is invoked. The hook then switches the return address on the +// stack for the address of an exit hook function, and pushes the original +// return address to a shadow stack of some type. When in due course the CPU +// executes a return to the exit hook, the exit hook will do whatever work it +// does on function exit, then arrange to return to the original return address. +// This class of profiler does not play well with programs that look at the +// return address, as does e.g. V8. V8 uses the return address to certain +// runtime functions to find the JIT code that called it, and from there finds +// the V8 data structures associated to the JS function involved. +// A return address resolution function is used to fix this. It allows such +// programs to resolve a location on stack where a return address originally +// resided, to the shadow stack location where the profiler stashed it. +typedef uintptr_t (*ReturnAddressLocationResolver)( + uintptr_t return_addr_location); + +// If this binary is instrumented and the instrumentation supplies a return +// address resolution function, finds and returns the address resolution +// function. Otherwise returns NULL. +BASE_EXPORT ReturnAddressLocationResolver + GetProfilerReturnAddrResolutionFunc(); + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_DEBUGGER_H
diff --git a/src/base/debug/stack_trace.cc b/src/base/debug/stack_trace.cc new file mode 100644 index 0000000..6fab183 --- /dev/null +++ b/src/base/debug/stack_trace.cc
@@ -0,0 +1,41 @@ +// 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/debug/stack_trace.h" + +#include "base/basictypes.h" + +#include <string.h> + +#include <algorithm> +#include <sstream> + +namespace base { +namespace debug { + +StackTrace::StackTrace(const void* const* trace, size_t count) { + count = std::min(count, arraysize(trace_)); + if (count) + memcpy(trace_, trace, count * sizeof(trace_[0])); + count_ = count; +} + +StackTrace::~StackTrace() { +} + +const void *const *StackTrace::Addresses(size_t* count) const { + *count = count_; + if (count_) + return trace_; + return NULL; +} + +std::string StackTrace::ToString() const { + std::stringstream stream; + OutputToStream(&stream); + return stream.str(); +} + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/stack_trace.h b/src/base/debug/stack_trace.h new file mode 100644 index 0000000..be3395d --- /dev/null +++ b/src/base/debug/stack_trace.h
@@ -0,0 +1,97 @@ +// 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_DEBUG_STACK_TRACE_H_ +#define BASE_DEBUG_STACK_TRACE_H_ + +#include <iosfwd> +#include <string> + +#include "base/base_export.h" +#include "build/build_config.h" + +#if defined(OS_POSIX) +#include <unistd.h> +#endif + +#if defined(OS_WIN) || defined(COMPILER_MSVC) +struct _EXCEPTION_POINTERS; +#endif + +namespace base { +namespace debug { + +// Enables stack dump to console output on exception and signals. +// When enabled, the process will quit immediately. This is meant to be used in +// unit_tests only! This is not thread-safe: only call from main thread. +BASE_EXPORT bool EnableInProcessStackDumping(); + +// A stacktrace can be helpful in debugging. For example, you can include a +// stacktrace member in a object (probably around #ifndef NDEBUG) so that you +// can later see where the given object was created from. +class BASE_EXPORT StackTrace { + public: + // Creates a stacktrace from the current location. + StackTrace(); + + // Creates a stacktrace from an existing array of instruction + // pointers (such as returned by Addresses()). |count| will be + // trimmed to |kMaxTraces|. + StackTrace(const void* const* trace, size_t count); + +#if defined(OS_WIN) || defined(COMPILER_MSVC) + // Creates a stacktrace for an exception. + // Note: this function will throw an import not found (StackWalk64) exception + // on system without dbghelp 5.1. + StackTrace(_EXCEPTION_POINTERS* exception_pointers); +#endif + + // Copying and assignment are allowed with the default functions. + + ~StackTrace(); + + // Gets an array of instruction pointer values. |*count| will be set to the + // number of elements in the returned array. + const void* const* Addresses(size_t* count) const; + + // Prints a backtrace to stderr + void PrintBacktrace() const; + + // Resolves backtrace to symbols and write to stream. + void OutputToStream(std::ostream* os) const; + + // Resolves backtrace to symbols and returns as string. + std::string ToString() const; + + private: + // From http://msdn.microsoft.com/en-us/library/bb204633.aspx, + // the sum of FramesToSkip and FramesToCapture must be less than 63, + // so set it to 62. Even if on POSIX it could be a larger value, it usually + // doesn't give much more information. + static const int kMaxTraces = 62; + + void* trace_[kMaxTraces]; + + // The number of valid frames in |trace_|. + size_t count_; +}; + +namespace internal { + +#if defined(OS_POSIX) && !defined(OS_ANDROID) +// POSIX doesn't define any async-signal safe function for converting +// an integer to ASCII. We'll have to define our own version. +// itoa_r() converts a (signed) integer to ASCII. It returns "buf", if the +// conversion was successful or NULL otherwise. It never writes more than "sz" +// bytes. Output will be truncated as needed, and a NUL character is always +// appended. +BASE_EXPORT char *itoa_r(intptr_t i, char *buf, size_t sz, int base); +#endif // defined(OS_POSIX) && !defined(OS_ANDROID) + +} // namespace internal + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_STACK_TRACE_H_
diff --git a/src/base/debug/stack_trace_android.cc b/src/base/debug/stack_trace_android.cc new file mode 100644 index 0000000..cc03d60 --- /dev/null +++ b/src/base/debug/stack_trace_android.cc
@@ -0,0 +1,60 @@ +// 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/debug/stack_trace.h" + +#include <signal.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/logging.h" + +namespace base { +namespace debug { + +bool EnableInProcessStackDumping() { + // When running in an application, our code typically expects SIGPIPE + // to be ignored. Therefore, when testing that same code, it should run + // with SIGPIPE ignored as well. + // TODO(phajdan.jr): De-duplicate this SIGPIPE code. + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = SIG_IGN; + sigemptyset(&action.sa_mask); + return (sigaction(SIGPIPE, &action, NULL) == 0); +} + +StackTrace::StackTrace() { +} + +// Sends fake SIGSTKFLT signals to let the Android linker and debuggerd dump +// stack. See inlined comments and Android bionic/linker/debugger.c and +// system/core/debuggerd/debuggerd.c for details. +void StackTrace::PrintBacktrace() const { + // Get the current handler of SIGSTKFLT for later use. + sighandler_t sig_handler = signal(SIGSTKFLT, SIG_DFL); + signal(SIGSTKFLT, sig_handler); + + // The Android linker will handle this signal and send a stack dumping request + // to debuggerd which will ptrace_attach this process. Before returning from + // the signal handler, the linker sets the signal handler to SIG_IGN. + kill(gettid(), SIGSTKFLT); + + // Because debuggerd will wait for the process to be stopped by the actual + // signal in crashing scenarios, signal is sent again to met the expectation. + // Debuggerd will dump stack into the system log and /data/tombstones/ files. + // NOTE: If this process runs in the interactive shell, it will be put + // in the background. To resume it in the foreground, use 'fg' command. + kill(gettid(), SIGSTKFLT); + + // Restore the signal handler so that this method can work the next time. + signal(SIGSTKFLT, sig_handler); +} + +void StackTrace::OutputToStream(std::ostream* os) const { + NOTIMPLEMENTED(); +} + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/stack_trace_ios.mm b/src/base/debug/stack_trace_ios.mm new file mode 100644 index 0000000..ab0abc4 --- /dev/null +++ b/src/base/debug/stack_trace_ios.mm
@@ -0,0 +1,55 @@ +// 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/process_util.h" + +#import <Foundation/Foundation.h> +#include <mach/task.h> +#include <stdio.h> + +#include "base/logging.h" + +// This is just enough of a shim to let the support needed by test_support +// link. + +namespace base { +namespace debug { + +namespace { + +void StackDumpSignalHandler(int signal) { + // TODO(phajdan.jr): Fix async-signal unsafety. + LOG(ERROR) << "Received signal " << signal; + NSArray *stack_symbols = [NSThread callStackSymbols]; + for (NSString* stack_symbol in stack_symbols) { + fprintf(stderr, "\t%s\n", [stack_symbol UTF8String]); + } + _exit(1); +} + +} // namespace + +// TODO(phajdan.jr): Deduplicate, see copy in stack_trace_posix.cc. +bool EnableInProcessStackDumping() { + // When running in an application, our code typically expects SIGPIPE + // to be ignored. Therefore, when testing that same code, it should run + // with SIGPIPE ignored as well. + struct sigaction action; + action.sa_handler = SIG_IGN; + action.sa_flags = 0; + sigemptyset(&action.sa_mask); + bool success = (sigaction(SIGPIPE, &action, NULL) == 0); + + success &= (signal(SIGILL, &StackDumpSignalHandler) != SIG_ERR); + success &= (signal(SIGABRT, &StackDumpSignalHandler) != SIG_ERR); + success &= (signal(SIGFPE, &StackDumpSignalHandler) != SIG_ERR); + success &= (signal(SIGBUS, &StackDumpSignalHandler) != SIG_ERR); + success &= (signal(SIGSEGV, &StackDumpSignalHandler) != SIG_ERR); + success &= (signal(SIGSYS, &StackDumpSignalHandler) != SIG_ERR); + + return success; +} + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/stack_trace_posix.cc b/src/base/debug/stack_trace_posix.cc new file mode 100644 index 0000000..fd0bb34 --- /dev/null +++ b/src/base/debug/stack_trace_posix.cc
@@ -0,0 +1,425 @@ +// 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/debug/stack_trace.h" + +#include <errno.h> +#include <execinfo.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <ostream> + +#if defined(__GLIBCXX__) +#include <cxxabi.h> +#endif + +#if defined(OS_MACOSX) +#include <AvailabilityMacros.h> +#endif + +#include "base/basictypes.h" +#include "base/debug/debugger.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "base/string_number_conversions.h" + +#if defined(USE_SYMBOLIZE) +#include "base/third_party/symbolize/symbolize.h" +#endif + +namespace base { +namespace debug { + +namespace { + +#if defined(__LB_SHELL__) +typedef int sig_atomic_t; +#endif + +volatile sig_atomic_t in_signal_handler = 0; + +// The prefix used for mangled symbols, per the Itanium C++ ABI: +// http://www.codesourcery.com/cxx-abi/abi.html#mangling +const char kMangledSymbolPrefix[] = "_Z"; + +// Characters that can be used for symbols, generated by Ruby: +// (('a'..'z').to_a+('A'..'Z').to_a+('0'..'9').to_a + ['_']).join +const char kSymbolCharacters[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; + +#if !defined(USE_SYMBOLIZE) +// Demangles C++ symbols in the given text. Example: +// +// "out/Debug/base_unittests(_ZN10StackTraceC1Ev+0x20) [0x817778c]" +// => +// "out/Debug/base_unittests(StackTrace::StackTrace()+0x20) [0x817778c]" +void DemangleSymbols(std::string* text) { + // Note: code in this function is NOT async-signal safe (std::string uses + // malloc internally). + +#if defined(__GLIBCXX__) + + std::string::size_type search_from = 0; + while (search_from < text->size()) { + // Look for the start of a mangled symbol, from search_from. + std::string::size_type mangled_start = + text->find(kMangledSymbolPrefix, search_from); + if (mangled_start == std::string::npos) { + break; // Mangled symbol not found. + } + + // Look for the end of the mangled symbol. + std::string::size_type mangled_end = + text->find_first_not_of(kSymbolCharacters, mangled_start); + if (mangled_end == std::string::npos) { + mangled_end = text->size(); + } + std::string mangled_symbol = + text->substr(mangled_start, mangled_end - mangled_start); + + // Try to demangle the mangled symbol candidate. + int status = 0; + scoped_ptr_malloc<char> demangled_symbol( + abi::__cxa_demangle(mangled_symbol.c_str(), NULL, 0, &status)); + if (status == 0) { // Demangling is successful. + // Remove the mangled symbol. + text->erase(mangled_start, mangled_end - mangled_start); + // Insert the demangled symbol. + text->insert(mangled_start, demangled_symbol.get()); + // Next time, we'll start right after the demangled symbol we inserted. + search_from = mangled_start + strlen(demangled_symbol.get()); + } else { + // Failed to demangle. Retry after the "_Z" we just found. + search_from = mangled_start + 2; + } + } + +#endif // defined(__GLIBCXX__) +} +#endif // !defined(USE_SYMBOLIZE) + +class BacktraceOutputHandler { + public: + virtual void HandleOutput(const char* output) = 0; + + protected: + virtual ~BacktraceOutputHandler() {} +}; + +void OutputPointer(void* pointer, BacktraceOutputHandler* handler) { + char buf[1024] = { '\0' }; + handler->HandleOutput(" [0x"); + internal::itoa_r(reinterpret_cast<intptr_t>(pointer), buf, sizeof(buf), 16); + handler->HandleOutput(buf); + handler->HandleOutput("]"); +} + +void ProcessBacktrace(void *const *trace, + int size, + BacktraceOutputHandler* handler) { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + +#if defined(USE_SYMBOLIZE) + for (int i = 0; i < size; ++i) { + handler->HandleOutput("\t"); + + char buf[1024] = { '\0' }; + + // Subtract by one as return address of function may be in the next + // function when a function is annotated as noreturn. + void* address = static_cast<char*>(trace[i]) - 1; + if (google::Symbolize(address, buf, sizeof(buf))) + handler->HandleOutput(buf); + else + handler->HandleOutput("<unknown>"); + + OutputPointer(trace[i], handler); + handler->HandleOutput("\n"); + } +#else + bool printed = false; + + // Below part is async-signal unsafe (uses malloc), so execute it only + // when we are not executing the signal handler. + if (in_signal_handler == 0) { +#if defined(__LB_PS3__) + std::string symbols; + bool success = ResolveSymbolsFromHostPS3(trace, size, &symbols); + if (success && symbols.length() > 0) { + handler->HandleOutput(symbols.c_str()); + printed = true; + } +#else + scoped_ptr_malloc<char*> trace_symbols(backtrace_symbols(trace, size)); + if (trace_symbols.get()) { + for (int i = 0; i < size; ++i) { + std::string trace_symbol = trace_symbols.get()[i]; + DemangleSymbols(&trace_symbol); + handler->HandleOutput(trace_symbol.c_str()); + handler->HandleOutput("\n"); + } + + printed = true; + } +#endif + } + + if (!printed) { + for (int i = 0; i < size; ++i) { + OutputPointer(trace[i], handler); + handler->HandleOutput("\n"); + } + } +#endif // defined(USE_SYMBOLIZE) +} + +#if !defined(__LB_SHELL__) +void StackDumpSignalHandler(int signal, siginfo_t* info, ucontext_t* context) { + // NOTE: This code MUST be async-signal safe. + // NO malloc or stdio is allowed here. + + // Record the fact that we are in the signal handler now, so that the rest + // of StackTrace can behave in an async-signal-safe manner. + in_signal_handler = 1; + + if (BeingDebugged()) + BreakDebugger(); + + char buf[1024] = "Received signal "; + size_t buf_len = strlen(buf); + internal::itoa_r(signal, buf + buf_len, sizeof(buf) - buf_len, 10); + RAW_LOG(ERROR, buf); + + debug::StackTrace().PrintBacktrace(); + + // TODO(shess): Port to Linux. +#if defined(OS_MACOSX) + // TODO(shess): Port to 64-bit. +#if ARCH_CPU_X86_FAMILY && ARCH_CPU_32_BITS + size_t len; + + // NOTE: Even |snprintf()| is not on the approved list for signal + // handlers, but buffered I/O is definitely not on the list due to + // potential for |malloc()|. + len = static_cast<size_t>( + snprintf(buf, sizeof(buf), + "ax: %x, bx: %x, cx: %x, dx: %x\n", + context->uc_mcontext->__ss.__eax, + context->uc_mcontext->__ss.__ebx, + context->uc_mcontext->__ss.__ecx, + context->uc_mcontext->__ss.__edx)); + write(STDERR_FILENO, buf, std::min(len, sizeof(buf) - 1)); + + len = static_cast<size_t>( + snprintf(buf, sizeof(buf), + "di: %x, si: %x, bp: %x, sp: %x, ss: %x, flags: %x\n", + context->uc_mcontext->__ss.__edi, + context->uc_mcontext->__ss.__esi, + context->uc_mcontext->__ss.__ebp, + context->uc_mcontext->__ss.__esp, + context->uc_mcontext->__ss.__ss, + context->uc_mcontext->__ss.__eflags)); + write(STDERR_FILENO, buf, std::min(len, sizeof(buf) - 1)); + + len = static_cast<size_t>( + snprintf(buf, sizeof(buf), + "ip: %x, cs: %x, ds: %x, es: %x, fs: %x, gs: %x\n", + context->uc_mcontext->__ss.__eip, + context->uc_mcontext->__ss.__cs, + context->uc_mcontext->__ss.__ds, + context->uc_mcontext->__ss.__es, + context->uc_mcontext->__ss.__fs, + context->uc_mcontext->__ss.__gs)); + write(STDERR_FILENO, buf, std::min(len, sizeof(buf) - 1)); +#endif // ARCH_CPU_32_BITS +#endif // defined(OS_MACOSX) + _exit(1); +} +#endif + +class PrintBacktraceOutputHandler : public BacktraceOutputHandler { + public: + PrintBacktraceOutputHandler() {} + + virtual void HandleOutput(const char* output) { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + ignore_result(HANDLE_EINTR(write(STDERR_FILENO, output, strlen(output)))); + } + + private: + DISALLOW_COPY_AND_ASSIGN(PrintBacktraceOutputHandler); +}; + +class StreamBacktraceOutputHandler : public BacktraceOutputHandler { + public: + StreamBacktraceOutputHandler(std::ostream* os) : os_(os) { + } + + virtual void HandleOutput(const char* output) { + (*os_) << output; + } + + private: + std::ostream* os_; + + DISALLOW_COPY_AND_ASSIGN(StreamBacktraceOutputHandler); +}; + +void WarmUpBacktrace() { + // Warm up stack trace infrastructure. It turns out that on the first + // call glibc initializes some internal data structures using pthread_once, + // and even backtrace() can call malloc(), leading to hangs. + // + // Example stack trace snippet (with tcmalloc): + // + // #8 0x0000000000a173b5 in tc_malloc + // at ./third_party/tcmalloc/chromium/src/debugallocation.cc:1161 + // #9 0x00007ffff7de7900 in _dl_map_object_deps at dl-deps.c:517 + // #10 0x00007ffff7ded8a9 in dl_open_worker at dl-open.c:262 + // #11 0x00007ffff7de9176 in _dl_catch_error at dl-error.c:178 + // #12 0x00007ffff7ded31a in _dl_open (file=0x7ffff625e298 "libgcc_s.so.1") + // at dl-open.c:639 + // #13 0x00007ffff6215602 in do_dlopen at dl-libc.c:89 + // #14 0x00007ffff7de9176 in _dl_catch_error at dl-error.c:178 + // #15 0x00007ffff62156c4 in dlerror_run at dl-libc.c:48 + // #16 __GI___libc_dlopen_mode at dl-libc.c:165 + // #17 0x00007ffff61ef8f5 in init + // at ../sysdeps/x86_64/../ia64/backtrace.c:53 + // #18 0x00007ffff6aad400 in pthread_once + // at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_once.S:104 + // #19 0x00007ffff61efa14 in __GI___backtrace + // at ../sysdeps/x86_64/../ia64/backtrace.c:104 + // #20 0x0000000000752a54 in base::debug::StackTrace::StackTrace + // at base/debug/stack_trace_posix.cc:175 + // #21 0x00000000007a4ae5 in + // base::(anonymous namespace)::StackDumpSignalHandler + // at base/process_util_posix.cc:172 + // #22 <signal handler called> + StackTrace stack_trace; +} + +} // namespace + +#if !defined(OS_IOS) && !defined(__LB_SHELL__) +bool EnableInProcessStackDumping() { + // When running in an application, our code typically expects SIGPIPE + // to be ignored. Therefore, when testing that same code, it should run + // with SIGPIPE ignored as well. + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = SIG_IGN; + sigemptyset(&action.sa_mask); + bool success = (sigaction(SIGPIPE, &action, NULL) == 0); + + // Avoid hangs during backtrace initialization, see above. + WarmUpBacktrace(); + + sig_t handler = reinterpret_cast<sig_t>(&StackDumpSignalHandler); + success &= (signal(SIGILL, handler) != SIG_ERR); + success &= (signal(SIGABRT, handler) != SIG_ERR); + success &= (signal(SIGFPE, handler) != SIG_ERR); + success &= (signal(SIGBUS, handler) != SIG_ERR); + success &= (signal(SIGSEGV, handler) != SIG_ERR); + success &= (signal(SIGSYS, handler) != SIG_ERR); + + return success; +} +#endif // !defined(OS_IOS) + +StackTrace::StackTrace() { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + + // Though the backtrace API man page does not list any possible negative + // return values, we take no chance. + count_ = std::max(backtrace(trace_, arraysize(trace_)), 0); +} + +void StackTrace::PrintBacktrace() const { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + + PrintBacktraceOutputHandler handler; + ProcessBacktrace(trace_, count_, &handler); +} + +void StackTrace::OutputToStream(std::ostream* os) const { + StreamBacktraceOutputHandler handler(os); + ProcessBacktrace(trace_, count_, &handler); +} + +namespace internal { + +// NOTE: code from sandbox/linux/seccomp-bpf/demo.cc. +char *itoa_r(intptr_t i, char *buf, size_t sz, int base) { + // Make sure we can write at least one NUL byte. + size_t n = 1; + if (n > sz) + return NULL; + + if (base < 2 || base > 16) { + buf[0] = '\000'; + return NULL; + } + + char *start = buf; + + uintptr_t j = i; + + // Handle negative numbers (only for base 10). + if (i < 0 && base == 10) { + j = -i; + + // Make sure we can write the '-' character. + if (++n > sz) { + buf[0] = '\000'; + return NULL; + } + *start++ = '-'; + } + + // Loop until we have converted the entire number. Output at least one + // character (i.e. '0'). + char *ptr = start; + do { + // Make sure there is still enough space left in our output buffer. + if (++n > sz) { + buf[0] = '\000'; + return NULL; + } + + // Output the next digit. + *ptr++ = "0123456789abcdef"[j % base]; + j /= base; + } while (j); + + // Terminate the output with a NUL character. + *ptr = '\000'; + + // Conversion to ASCII actually resulted in the digits being in reverse + // order. We can't easily generate them in forward order, as we can't tell + // the number of characters needed until we are done converting. + // So, now, we reverse the string (except for the possible "-" sign). + while (--ptr > start) { + char ch = *ptr; + *ptr = *start; + *start++ = ch; + } + return buf; +} + +} // namespace internal + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/stack_trace_shell.cc b/src/base/debug/stack_trace_shell.cc new file mode 100644 index 0000000..8c42ae6 --- /dev/null +++ b/src/base/debug/stack_trace_shell.cc
@@ -0,0 +1,46 @@ +// 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. + +#if defined(__LB_XB1__) && !defined(__LB_SHELL__FOR_RELEASE__) + +// Stack tracing is using DbgHelp which doesn't support the TV_APP partition +// at the moment. To make it work, we have to trick the headers into thinking +// that they are compiled for a desktop application. This is the reason +// why the functionality will only be enabled in internal builds. + +#if !defined(COBALT_WIN) +#pragma push_macro("WINAPI_PARTITION_DESKTOP") +#undef WINAPI_PARTITION_DESKTOP +#define WINAPI_PARTITION_DESKTOP 1 +#endif + +#include "stack_trace_win.cc" + +#if !defined(COBALT_WIN) +#pragma pop_macro("WINAPI_PARTITION_DESKTOP") +#endif + +// EnableInProcessStackDumping is implemented in stack_trace_xb1.cc + +#elif !defined(__LB_ANDROID__) +// stack_trace_android.cc is already compiled via base.gypi + +#if defined(__LB_PS3__) +#include "stack_trace_ps3.cc" +#endif +#include "stack_trace_posix.cc" + +namespace base { +namespace debug { + +// But re-implement this: +bool EnableInProcessStackDumping() { + // We need this to return true to run unit tests + return true; +} + +} // namespace debug +} // namespace base + +#endif
diff --git a/src/base/debug/stack_trace_starboard.cc b/src/base/debug/stack_trace_starboard.cc new file mode 100644 index 0000000..163105c --- /dev/null +++ b/src/base/debug/stack_trace_starboard.cc
@@ -0,0 +1,185 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/debug/stack_trace.h" + +#include <ostream> + +#include "base/basictypes.h" +#include "starboard/log.h" +#include "starboard/system.h" + +namespace base { +namespace debug { + +namespace { + +class BacktraceOutputHandler { + public: + virtual void HandleOutput(const char* output) = 0; + + protected: + virtual ~BacktraceOutputHandler() {} +}; + +class PrintBacktraceOutputHandler : public BacktraceOutputHandler { + public: + PrintBacktraceOutputHandler() {} + + virtual void HandleOutput(const char* output) { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + SbLogRaw(output); + } + + private: + DISALLOW_COPY_AND_ASSIGN(PrintBacktraceOutputHandler); +}; + +class StreamBacktraceOutputHandler : public BacktraceOutputHandler { + public: + StreamBacktraceOutputHandler(std::ostream* os) : os_(os) {} + + virtual void HandleOutput(const char* output) { (*os_) << output; } + + private: + std::ostream* os_; + + DISALLOW_COPY_AND_ASSIGN(StreamBacktraceOutputHandler); +}; + +// NOTE: code from sandbox/linux/seccomp-bpf/demo.cc. +char* itoa_r(intptr_t i, char* buf, size_t sz, int base) { + // Make sure we can write at least one NUL byte. + size_t n = 1; + if (n > sz) + return NULL; + + if (base < 2 || base > 16) { + buf[0] = '\000'; + return NULL; + } + + char* start = buf; + + uintptr_t j = i; + + // Handle negative numbers (only for base 10). + if (i < 0 && base == 10) { + j = -i; + + // Make sure we can write the '-' character. + if (++n > sz) { + buf[0] = '\000'; + return NULL; + } + *start++ = '-'; + } + + // Loop until we have converted the entire number. Output at least one + // character (i.e. '0'). + char* ptr = start; + do { + // Make sure there is still enough space left in our output buffer. + if (++n > sz) { + buf[0] = '\000'; + return NULL; + } + + // Output the next digit. + *ptr++ = "0123456789abcdef"[j % base]; + j /= base; + } while (j); + + // Terminate the output with a NUL character. + *ptr = '\000'; + + // Conversion to ASCII actually resulted in the digits being in reverse + // order. We can't easily generate them in forward order, as we can't tell + // the number of characters needed until we are done converting. + // So, now, we reverse the string (except for the possible "-" sign). + while (--ptr > start) { + char ch = *ptr; + *ptr = *start; + *start++ = ch; + } + return buf; +} + +void OutputPointer(void* pointer, BacktraceOutputHandler* handler) { + char buf[1024] = {'\0'}; + handler->HandleOutput(" [0x"); + itoa_r(reinterpret_cast<intptr_t>(pointer), buf, sizeof(buf), 16); + handler->HandleOutput(buf); + handler->HandleOutput("]"); +} + +void ProcessBacktrace(void* const* trace, + int size, + BacktraceOutputHandler* handler) { + for (int i = 0; i < size; ++i) { + handler->HandleOutput("\t"); + + char buf[1024] = {'\0'}; + + // Subtract by one as return address of function may be in the next + // function when a function is annotated as noreturn. + void* address = static_cast<char*>(trace[i]) - 1; + if (SbSystemSymbolize(address, buf, sizeof(buf))) { + handler->HandleOutput(buf); + } else { + handler->HandleOutput("<unknown>"); + } + + OutputPointer(trace[i], handler); + handler->HandleOutput("\n"); + } +} + +} // namespace + +StackTrace::StackTrace() { + // NOTE: This code MUST be async-signal safe (it's used by in-process + // stack dumping signal handler). NO malloc or stdio is allowed here. + + // Though the SbSystemGetStack API documentation does not specify any possible + // negative return values, we take no chance. + count_ = std::max(SbSystemGetStack(trace_, arraysize(trace_)), 0); + if (count_ < 1) { + return; + } + + // We can remove this call from the stack trace, since we know it is always + // going to be in it. + for (int i = 1; i < count_; ++i) { + trace_[i - 1] = trace_[i]; + } +} + +void StackTrace::PrintBacktrace() const { + PrintBacktraceOutputHandler handler; + ProcessBacktrace(trace_, count_, &handler); +} + +void StackTrace::OutputToStream(std::ostream* out_stream) const { + StreamBacktraceOutputHandler handler(out_stream); + ProcessBacktrace(trace_, count_, &handler); +} + +bool EnableInProcessStackDumping() { + return true; +} + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/stack_trace_unittest.cc b/src/base/debug/stack_trace_unittest.cc new file mode 100644 index 0000000..21205ca --- /dev/null +++ b/src/base/debug/stack_trace_unittest.cc
@@ -0,0 +1,211 @@ +// 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 <limits> +#include <sstream> +#include <string> + +#include "base/debug/stack_trace.h" +#include "base/logging.h" +#include "base/process_util.h" +#include "base/test/test_timeouts.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +#if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_IOS) && !defined(__LB_SHELL__) +#include "base/test/multiprocess_test.h" +#endif + +namespace base { +namespace debug { + +#if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_IOS) && !defined(__LB_SHELL__) +typedef MultiProcessTest StackTraceTest; +#else +typedef testing::Test StackTraceTest; +#endif + +// Note: On Linux, this test currently only fully works on Debug builds. +// See comments in the #ifdef soup if you intend to change this. +#if defined(OS_WIN) +// Always fails on Windows: crbug.com/32070 +#define MAYBE_OutputToStream DISABLED_OutputToStream +#else +#define MAYBE_OutputToStream OutputToStream +#endif +TEST_F(StackTraceTest, MAYBE_OutputToStream) { + StackTrace trace; + + // Dump the trace into a string. + std::ostringstream os; + trace.OutputToStream(&os); + std::string backtrace_message = os.str(); + + // ToString() should produce the same output. + EXPECT_EQ(backtrace_message, trace.ToString()); + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && NDEBUG + // Stack traces require an extra data table that bloats our binaries, + // so they're turned off for release builds. We stop the test here, + // at least letting us verify that the calls don't crash. + return; +#endif // defined(OS_POSIX) && !defined(OS_MACOSX) && NDEBUG + + size_t frames_found = 0; + trace.Addresses(&frames_found); + ASSERT_GE(frames_found, 5u) << + "No stack frames found. Skipping rest of test."; + + // Check if the output has symbol initialization warning. If it does, fail. + ASSERT_EQ(backtrace_message.find("Dumping unresolved backtrace"), + std::string::npos) << + "Unable to resolve symbols. Skipping rest of test."; + +#if defined(OS_MACOSX) +#if 0 + // Disabled due to -fvisibility=hidden in build config. + + // Symbol resolution via the backtrace_symbol function does not work well + // in OS X. + // See this thread: + // + // http://lists.apple.com/archives/darwin-dev/2009/Mar/msg00111.html + // + // Just check instead that we find our way back to the "start" symbol + // which should be the first symbol in the trace. + // + // TODO(port): Find a more reliable way to resolve symbols. + + // Expect to at least find main. + EXPECT_TRUE(backtrace_message.find("start") != std::string::npos) + << "Expected to find start in backtrace:\n" + << backtrace_message; + +#endif +#elif defined(USE_SYMBOLIZE) + // This branch is for gcc-compiled code, but not Mac due to the + // above #if. + // Expect a demangled symbol. + EXPECT_TRUE(backtrace_message.find("testing::Test::Run()") != + std::string::npos) + << "Expected a demangled symbol in backtrace:\n" + << backtrace_message; + +#elif 0 + // This is the fall-through case; it used to cover Windows. + // But it's disabled because of varying buildbot configs; + // some lack symbols. + + // Expect to at least find main. + EXPECT_TRUE(backtrace_message.find("main") != std::string::npos) + << "Expected to find main in backtrace:\n" + << backtrace_message; + +#if defined(OS_WIN) +// MSVC doesn't allow the use of C99's __func__ within C++, so we fake it with +// MSVC's __FUNCTION__ macro. +#define __func__ __FUNCTION__ +#endif + + // Expect to find this function as well. + // Note: This will fail if not linked with -rdynamic (aka -export_dynamic) + EXPECT_TRUE(backtrace_message.find(__func__) != std::string::npos) + << "Expected to find " << __func__ << " in backtrace:\n" + << backtrace_message; + +#endif // define(OS_MACOSX) +} + +// The test is used for manual testing, e.g., to see the raw output. +TEST_F(StackTraceTest, DebugOutputToStream) { + StackTrace trace; + std::ostringstream os; + trace.OutputToStream(&os); + VLOG(1) << os.str(); +} + +// The test is used for manual testing, e.g., to see the raw output. +TEST_F(StackTraceTest, DebugPrintBacktrace) { + StackTrace().PrintBacktrace(); +} + +#if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(__LB_ANDROID__) +#if !defined(OS_IOS) && !defined(__LB_SHELL__) +MULTIPROCESS_TEST_MAIN(MismatchedMallocChildProcess) { + char* pointer = new char[10]; + delete pointer; + return 2; +} + +// Regression test for StackDumpingSignalHandler async-signal unsafety. +// Combined with tcmalloc's debugallocation, that signal handler +// and e.g. mismatched new[]/delete would cause a hang because +// of re-entering malloc. +TEST_F(StackTraceTest, AsyncSignalUnsafeSignalHandlerHang) { + ProcessHandle child = this->SpawnChild("MismatchedMallocChildProcess", false); + ASSERT_NE(kNullProcessHandle, child); + ASSERT_TRUE(WaitForSingleProcess(child, TestTimeouts::action_timeout())); +} +#endif // !defined(OS_IOS) &&!defined(__LB_SHELL__) + +namespace { + +std::string itoa_r_wrapper(intptr_t i, size_t sz, int base) { + char buffer[1024]; + CHECK_LE(sz, sizeof(buffer)); + + char* result = internal::itoa_r(i, buffer, sz, base); + EXPECT_TRUE(result); + return std::string(buffer); +} + +} // namespace + +TEST_F(StackTraceTest, itoa_r) { + EXPECT_EQ("0", itoa_r_wrapper(0, 128, 10)); + EXPECT_EQ("-1", itoa_r_wrapper(-1, 128, 10)); + + // Test edge cases. + if (sizeof(intptr_t) == 4) { + EXPECT_EQ("ffffffff", itoa_r_wrapper(-1, 128, 16)); + EXPECT_EQ("-2147483648", + itoa_r_wrapper(std::numeric_limits<intptr_t>::min(), 128, 10)); + EXPECT_EQ("2147483647", + itoa_r_wrapper(std::numeric_limits<intptr_t>::max(), 128, 10)); + + EXPECT_EQ("80000000", + itoa_r_wrapper(std::numeric_limits<intptr_t>::min(), 128, 16)); + EXPECT_EQ("7fffffff", + itoa_r_wrapper(std::numeric_limits<intptr_t>::max(), 128, 16)); + } else if (sizeof(intptr_t) == 8) { + EXPECT_EQ("ffffffffffffffff", itoa_r_wrapper(-1, 128, 16)); + EXPECT_EQ("-9223372036854775808", + itoa_r_wrapper(std::numeric_limits<intptr_t>::min(), 128, 10)); + EXPECT_EQ("9223372036854775807", + itoa_r_wrapper(std::numeric_limits<intptr_t>::max(), 128, 10)); + + EXPECT_EQ("8000000000000000", + itoa_r_wrapper(std::numeric_limits<intptr_t>::min(), 128, 16)); + EXPECT_EQ("7fffffffffffffff", + itoa_r_wrapper(std::numeric_limits<intptr_t>::max(), 128, 16)); + } else { + ADD_FAILURE() << "Missing test case for your size of intptr_t (" + << sizeof(intptr_t) << ")"; + } + + // Test hex output. + EXPECT_EQ("688", itoa_r_wrapper(0x688, 128, 16)); + EXPECT_EQ("deadbeef", itoa_r_wrapper(0xdeadbeef, 128, 16)); + + // Check that itoa_r respects passed buffer size limit. + char buffer[1024]; + EXPECT_TRUE(internal::itoa_r(0xdeadbeef, buffer, 10, 16)); + EXPECT_TRUE(internal::itoa_r(0xdeadbeef, buffer, 9, 16)); + EXPECT_FALSE(internal::itoa_r(0xdeadbeef, buffer, 8, 16)); + EXPECT_FALSE(internal::itoa_r(0xdeadbeef, buffer, 7, 16)); +} +#endif // defined(OS_POSIX) && !defined(OS_ANDROID) + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/stack_trace_win.cc b/src/base/debug/stack_trace_win.cc new file mode 100644 index 0000000..d26b492 --- /dev/null +++ b/src/base/debug/stack_trace_win.cc
@@ -0,0 +1,245 @@ +// 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/debug/stack_trace.h" + +#include <windows.h> +#include <dbghelp.h> + +#include <iostream> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "base/process_util.h" +#include "base/synchronization/lock.h" + +namespace base { +namespace debug { + +namespace { + +#if !defined(COBALT_WIN) +// Previous unhandled filter. Will be called if not NULL when we intercept an +// exception. Only used in unit tests. +LPTOP_LEVEL_EXCEPTION_FILTER g_previous_filter = NULL; + +// Prints the exception call stack. +// This is the unit tests exception filter. +long WINAPI StackDumpExceptionFilter(EXCEPTION_POINTERS* info) { + debug::StackTrace(info).PrintBacktrace(); + if (g_previous_filter) + return g_previous_filter(info); + return EXCEPTION_CONTINUE_SEARCH; +} +#endif // !defined(COBALT_WIN) + +// SymbolContext is a threadsafe singleton that wraps the DbgHelp Sym* family +// of functions. The Sym* family of functions may only be invoked by one +// thread at a time. SymbolContext code may access a symbol server over the +// network while holding the lock for this singleton. In the case of high +// latency, this code will adversely affect performance. +// +// There is also a known issue where this backtrace code can interact +// badly with breakpad if breakpad is invoked in a separate thread while +// we are using the Sym* functions. This is because breakpad does now +// share a lock with this function. See this related bug: +// +// http://code.google.com/p/google-breakpad/issues/detail?id=311 +// +// This is a very unlikely edge case, and the current solution is to +// just ignore it. +class SymbolContext { + public: + static SymbolContext* GetInstance() { + // We use a leaky singleton because code may call this during process + // termination. + return + Singleton<SymbolContext, LeakySingletonTraits<SymbolContext> >::get(); + } + + // Returns the error code of a failed initialization. + DWORD init_error() const { + return init_error_; + } + + // For the given trace, attempts to resolve the symbols, and output a trace + // to the ostream os. The format for each line of the backtrace is: + // + // <tab>SymbolName[0xAddress+Offset] (FileName:LineNo) + // + // This function should only be called if Init() has been called. We do not + // LOG(FATAL) here because this code is called might be triggered by a + // LOG(FATAL) itself. + void OutputTraceToStream(const void* const* trace, + size_t count, + std::ostream* os) { + base::AutoLock lock(lock_); + + for (size_t i = 0; (i < count) && os->good(); ++i) { + const int kMaxNameLength = 256; + DWORD_PTR frame = reinterpret_cast<DWORD_PTR>(trace[i]); + + // Code adapted from MSDN example: + // http://msdn.microsoft.com/en-us/library/ms680578(VS.85).aspx + ULONG64 buffer[ + (sizeof(SYMBOL_INFO) + + kMaxNameLength * sizeof(wchar_t) + + sizeof(ULONG64) - 1) / + sizeof(ULONG64)]; + memset(buffer, 0, sizeof(buffer)); + + // Initialize symbol information retrieval structures. + DWORD64 sym_displacement = 0; + PSYMBOL_INFO symbol = reinterpret_cast<PSYMBOL_INFO>(&buffer[0]); + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = kMaxNameLength - 1; + BOOL has_symbol = SymFromAddr(GetCurrentProcess(), frame, + &sym_displacement, symbol); + + // Attempt to retrieve line number information. + DWORD line_displacement = 0; + IMAGEHLP_LINE64 line = {}; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + BOOL has_line = SymGetLineFromAddr64(GetCurrentProcess(), frame, + &line_displacement, &line); + + // Output the backtrace line. + (*os) << "\t"; + if (has_symbol) { + (*os) << symbol->Name << " [0x" << trace[i] << "+" + << sym_displacement << "]"; + } else { + // If there is no symbol information, add a spacer. + (*os) << "(No symbol) [0x" << trace[i] << "]"; + } + if (has_line) { + (*os) << " (" << line.FileName << ":" << line.LineNumber << ")"; + } + (*os) << "\n"; + } + } + + private: + friend struct DefaultSingletonTraits<SymbolContext>; + + SymbolContext() : init_error_(ERROR_SUCCESS) { + // Initializes the symbols for the process. + // Defer symbol load until they're needed, use undecorated names, and + // get line numbers. + SymSetOptions(SYMOPT_DEFERRED_LOADS | + SYMOPT_UNDNAME | + SYMOPT_LOAD_LINES); + if (SymInitialize(GetCurrentProcess(), NULL, TRUE)) { + init_error_ = ERROR_SUCCESS; + } else { + init_error_ = GetLastError(); + // TODO(awong): Handle error: SymInitialize can fail with + // ERROR_INVALID_PARAMETER. + // When it fails, we should not call debugbreak since it kills the current + // process (prevents future tests from running or kills the browser + // process). + DLOG(ERROR) << "SymInitialize failed: " << init_error_; + } + } + + DWORD init_error_; + base::Lock lock_; + DISALLOW_COPY_AND_ASSIGN(SymbolContext); +}; + +} // namespace + +#if !defined(COBALT_WIN) +bool EnableInProcessStackDumping() { + // Add stack dumping support on exception on windows. Similar to OS_POSIX + // signal() handling in process_util_posix.cc. + g_previous_filter = SetUnhandledExceptionFilter(&StackDumpExceptionFilter); + RouteStdioToConsole(); + return true; +} +#endif + +// Disable optimizations for the StackTrace::StackTrace function. It is +// important to disable at least frame pointer optimization ("y"), since +// that breaks CaptureStackBackTrace() and prevents StackTrace from working +// in Release builds (it may still be janky if other frames are using FPO, +// but at least it will make it further). +#if defined(COMPILER_MSVC) +#pragma optimize("", off) +#endif + +StackTrace::StackTrace() { + // When walking our own stack, use CaptureStackBackTrace(). + count_ = CaptureStackBackTrace(0, arraysize(trace_), trace_, NULL); +} + +#if defined(COMPILER_MSVC) +#pragma optimize("", on) +#endif + +StackTrace::StackTrace(EXCEPTION_POINTERS* exception_pointers) { + // StackWalk64 requires symbol information to be loaded. The following + // will make sure that SymbolContext singleton is initialized which will + // load the required symbols. + static volatile SymbolContext* context = SymbolContext::GetInstance(); + (void)context; + + // When walking an exception stack, we need to use StackWalk64(). + count_ = 0; + // Initialize stack walking. + STACKFRAME64 stack_frame; + memset(&stack_frame, 0, sizeof(stack_frame)); +#if defined(_WIN64) + int machine_type = IMAGE_FILE_MACHINE_AMD64; + stack_frame.AddrPC.Offset = exception_pointers->ContextRecord->Rip; + stack_frame.AddrFrame.Offset = exception_pointers->ContextRecord->Rbp; + stack_frame.AddrStack.Offset = exception_pointers->ContextRecord->Rsp; +#else + int machine_type = IMAGE_FILE_MACHINE_I386; + stack_frame.AddrPC.Offset = exception_pointers->ContextRecord->Eip; + stack_frame.AddrFrame.Offset = exception_pointers->ContextRecord->Ebp; + stack_frame.AddrStack.Offset = exception_pointers->ContextRecord->Esp; +#endif + stack_frame.AddrPC.Mode = AddrModeFlat; + stack_frame.AddrFrame.Mode = AddrModeFlat; + stack_frame.AddrStack.Mode = AddrModeFlat; + while (StackWalk64(machine_type, + GetCurrentProcess(), + GetCurrentThread(), + &stack_frame, + exception_pointers->ContextRecord, + NULL, + &SymFunctionTableAccess64, + &SymGetModuleBase64, + NULL) && + count_ < arraysize(trace_)) { + trace_[count_++] = reinterpret_cast<void*>(stack_frame.AddrPC.Offset); + } + + for (size_t i = count_; i < arraysize(trace_); ++i) + trace_[i] = NULL; +} + +void StackTrace::PrintBacktrace() const { + OutputToStream(&std::cerr); +} + +void StackTrace::OutputToStream(std::ostream* os) const { + SymbolContext* context = SymbolContext::GetInstance(); + DWORD error = context->init_error(); + if (error != ERROR_SUCCESS) { + (*os) << "Error initializing symbols (" << error + << "). Dumping unresolved backtrace:\n"; + for (int i = 0; (i < count_) && os->good(); ++i) { + (*os) << "\t" << trace_[i] << "\n"; + } + } else { + (*os) << "Backtrace:\n"; + context->OutputTraceToStream(trace_, count_, os); + } +} + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/trace_event.cc b/src/base/debug/trace_event.cc new file mode 100644 index 0000000..682e065 --- /dev/null +++ b/src/base/debug/trace_event.cc
@@ -0,0 +1,28 @@ +// 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/debug/trace_event.h" + +namespace trace_event_internal { + +void TraceEndOnScopeClose::Initialize(const unsigned char* category_enabled, + const char* name) { + data_.category_enabled = category_enabled; + data_.name = name; + p_data_ = &data_; +} + +void TraceEndOnScopeClose::AddEventIfEnabled() { + // Only called when p_data_ is non-null. + if (*p_data_->category_enabled) { + TRACE_EVENT_API_ADD_TRACE_EVENT( + TRACE_EVENT_PHASE_END, + p_data_->category_enabled, + p_data_->name, kNoEventId, + kZeroNumArgs, NULL, NULL, NULL, + TRACE_EVENT_FLAG_NONE); + } +} + +} // namespace trace_event_internal
diff --git a/src/base/debug/trace_event.h b/src/base/debug/trace_event.h new file mode 100644 index 0000000..67a79a6 --- /dev/null +++ b/src/base/debug/trace_event.h
@@ -0,0 +1,960 @@ +// 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. + +// This header is designed to give you trace_event macros without specifying +// how the events actually get collected and stored. If you need to expose trace +// event to some other universe, you can copy-and-paste this file, +// implement the TRACE_EVENT_API macros, and do any other necessary fixup for +// the target platform. The end result is that multiple libraries can funnel +// events through to a shared trace event collector. + +// Trace events are for tracking application performance and resource usage. +// Macros are provided to track: +// Begin and end of function calls +// Counters +// +// Events are issued against categories. Whereas LOG's +// categories are statically defined, TRACE categories are created +// implicitly with a string. For example: +// TRACE_EVENT_INSTANT0("MY_SUBSYSTEM", "SomeImportantEvent") +// +// Events can be INSTANT, or can be pairs of BEGIN and END in the same scope: +// TRACE_EVENT_BEGIN0("MY_SUBSYSTEM", "SomethingCostly") +// doSomethingCostly() +// TRACE_EVENT_END0("MY_SUBSYSTEM", "SomethingCostly") +// Note: our tools can't always determine the correct BEGIN/END pairs unless +// these are used in the same scope. Use ASYNC_BEGIN/ASYNC_END macros if you +// need them to be in separate scopes. +// +// A common use case is to trace entire function scopes. This +// issues a trace BEGIN and END automatically: +// void doSomethingCostly() { +// TRACE_EVENT0("MY_SUBSYSTEM", "doSomethingCostly"); +// ... +// } +// +// Additional parameters can be associated with an event: +// void doSomethingCostly2(int howMuch) { +// TRACE_EVENT1("MY_SUBSYSTEM", "doSomethingCostly", +// "howMuch", howMuch); +// ... +// } +// +// The trace system will automatically add to this information the +// current process id, thread id, and a timestamp in microseconds. +// +// To trace an asynchronous procedure such as an IPC send/receive, use +// ASYNC_BEGIN and ASYNC_END: +// [single threaded sender code] +// static int send_count = 0; +// ++send_count; +// TRACE_EVENT_ASYNC_BEGIN0("ipc", "message", send_count); +// Send(new MyMessage(send_count)); +// [receive code] +// void OnMyMessage(send_count) { +// TRACE_EVENT_ASYNC_END0("ipc", "message", send_count); +// } +// The third parameter is a unique ID to match ASYNC_BEGIN/ASYNC_END pairs. +// ASYNC_BEGIN and ASYNC_END can occur on any thread of any traced process. +// Pointers can be used for the ID parameter, and they will be mangled +// internally so that the same pointer on two different processes will not +// match. For example: +// class MyTracedClass { +// public: +// MyTracedClass() { +// TRACE_EVENT_ASYNC_BEGIN0("category", "MyTracedClass", this); +// } +// ~MyTracedClass() { +// TRACE_EVENT_ASYNC_END0("category", "MyTracedClass", this); +// } +// } +// +// Trace event also supports counters, which is a way to track a quantity +// as it varies over time. Counters are created with the following macro: +// TRACE_COUNTER1("MY_SUBSYSTEM", "myCounter", g_myCounterValue); +// +// Counters are process-specific. The macro itself can be issued from any +// thread, however. +// +// Sometimes, you want to track two counters at once. You can do this with two +// counter macros: +// TRACE_COUNTER1("MY_SUBSYSTEM", "myCounter0", g_myCounterValue[0]); +// TRACE_COUNTER1("MY_SUBSYSTEM", "myCounter1", g_myCounterValue[1]); +// Or you can do it with a combined macro: +// TRACE_COUNTER2("MY_SUBSYSTEM", "myCounter", +// "bytesPinned", g_myCounterValue[0], +// "bytesAllocated", g_myCounterValue[1]); +// This indicates to the tracing UI that these counters should be displayed +// in a single graph, as a summed area chart. +// +// Since counters are in a global namespace, you may want to disembiguate with a +// unique ID, by using the TRACE_COUNTER_ID* variations. +// +// By default, trace collection is compiled in, but turned off at runtime. +// Collecting trace data is the responsibility of the embedding +// application. In Chrome's case, navigating to about:tracing will turn on +// tracing and display data collected across all active processes. +// +// +// Memory scoping note: +// Tracing copies the pointers, not the string content, of the strings passed +// in for category, name, and arg_names. Thus, the following code will +// cause problems: +// char* str = strdup("impprtantName"); +// TRACE_EVENT_INSTANT0("SUBSYSTEM", str); // BAD! +// free(str); // Trace system now has dangling pointer +// +// To avoid this issue with the |name| and |arg_name| parameters, use the +// TRACE_EVENT_COPY_XXX overloads of the macros at additional runtime overhead. +// Notes: The category must always be in a long-lived char* (i.e. static const). +// The |arg_values|, when used, are always deep copied with the _COPY +// macros. +// +// When are string argument values copied: +// const char* arg_values are only referenced by default: +// TRACE_EVENT1("category", "name", +// "arg1", "literal string is only referenced"); +// Use TRACE_STR_COPY to force copying of a const char*: +// TRACE_EVENT1("category", "name", +// "arg1", TRACE_STR_COPY("string will be copied")); +// std::string arg_values are always copied: +// TRACE_EVENT1("category", "name", +// "arg1", std::string("string will be copied")); +// +// +// Thread Safety: +// A thread safe singleton and mutex are used for thread safety. Category +// enabled flags are used to limit the performance impact when the system +// is not enabled. +// +// TRACE_EVENT macros first cache a pointer to a category. The categories are +// statically allocated and safe at all times, even after exit. Fetching a +// category is protected by the TraceLog::lock_. Multiple threads initializing +// the static variable is safe, as they will be serialized by the lock and +// multiple calls will return the same pointer to the category. +// +// Then the category_enabled flag is checked. This is a unsigned char, and +// not intended to be multithread safe. It optimizes access to AddTraceEvent +// which is threadsafe internally via TraceLog::lock_. The enabled flag may +// cause some threads to incorrectly call or skip calling AddTraceEvent near +// the time of the system being enabled or disabled. This is acceptable as +// we tolerate some data loss while the system is being enabled/disabled and +// because AddTraceEvent is threadsafe internally and checks the enabled state +// again under lock. +// +// Without the use of these static category pointers and enabled flags all +// trace points would carry a significant performance cost of aquiring a lock +// and resolving the category. + + +#ifndef BASE_DEBUG_TRACE_EVENT_H_ +#define BASE_DEBUG_TRACE_EVENT_H_ + +#include <string> + +#include "base/atomicops.h" +#include "base/debug/trace_event_impl.h" +#include "build/build_config.h" + +#if defined(TRACING_DISABLED) + +inline void NullAddTraceEvent( + char phase, + const unsigned char* category_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, + unsigned char flags) { + UNREFERENCED_PARAMETER(phase); + UNREFERENCED_PARAMETER(category_enabled); + UNREFERENCED_PARAMETER(name); + UNREFERENCED_PARAMETER(id); + UNREFERENCED_PARAMETER(num_args); + UNREFERENCED_PARAMETER(arg_names); + UNREFERENCED_PARAMETER(arg_types); + UNREFERENCED_PARAMETER(arg_values); + UNREFERENCED_PARAMETER(flags); +} + +#define TRACE_EVENT_API_ADD_TRACE_EVENT NullAddTraceEvent + +#define INTERNAL_TRACE_EVENT_ADD(phase, category, name, flags, ...) \ + (void)0 +#define INTERNAL_TRACE_EVENT_ADD_SCOPED(category, name, ...) \ + (void)0 +#define INTERNAL_TRACE_EVENT_ADD_WITH_ID(phase, category, name, id, flags, \ + ...) \ + (void)0 + +#endif // #if defined(TRACING_DISABLED) + +// By default, const char* argument values are assumed to have long-lived scope +// and will not be copied. Use this macro to force a const char* to be copied. +#define TRACE_STR_COPY(str) \ + trace_event_internal::TraceStringWithCopy(str) + +// By default, uint64 ID argument values are not mangled with the Process ID in +// TRACE_EVENT_ASYNC macros. Use this macro to force Process ID mangling. +#define TRACE_ID_MANGLE(id) \ + trace_event_internal::TraceID::ForceMangle(id) + +// Records a pair of begin and end events called "name" for the current +// scope, with 0, 1 or 2 associated arguments. If the category is not +// enabled, then this does nothing. +// - category and name strings must have application lifetime (statics or +// literals). They may not include " chars. +#define TRACE_EVENT0(category, name) \ + INTERNAL_TRACE_EVENT_ADD_SCOPED(category, name) +#define TRACE_EVENT1(category, name, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_SCOPED(category, name, arg1_name, arg1_val) +#define TRACE_EVENT2(category, name, arg1_name, arg1_val, arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_SCOPED(category, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) + +// Same as TRACE_EVENT except that they are not included in official builds. +#ifdef OFFICIAL_BUILD +#define UNSHIPPED_TRACE_EVENT0(category, name) (void)0 +#define UNSHIPPED_TRACE_EVENT1(category, name, arg1_name, arg1_val) (void)0 +#define UNSHIPPED_TRACE_EVENT2(category, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) (void)0 +#define UNSHIPPED_TRACE_EVENT_INSTANT0(category, name) (void)0 +#define UNSHIPPED_TRACE_EVENT_INSTANT1(category, name, arg1_name, arg1_val) \ + (void)0 +#define UNSHIPPED_TRACE_EVENT_INSTANT2(category, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) (void)0 +#else +#define UNSHIPPED_TRACE_EVENT0(category, name) \ + TRACE_EVENT0(category, name) +#define UNSHIPPED_TRACE_EVENT1(category, name, arg1_name, arg1_val) \ + TRACE_EVENT1(category, name, arg1_name, arg1_val) +#define UNSHIPPED_TRACE_EVENT2(category, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + TRACE_EVENT2(category, name, arg1_name, arg1_val, arg2_name, arg2_val) +#define UNSHIPPED_TRACE_EVENT_INSTANT0(category, name) \ + TRACE_EVENT_INSTANT0(category, name) +#define UNSHIPPED_TRACE_EVENT_INSTANT1(category, name, arg1_name, arg1_val) \ + TRACE_EVENT_INSTANT1(category, name, arg1_name, arg1_val) +#define UNSHIPPED_TRACE_EVENT_INSTANT2(category, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + TRACE_EVENT_INSTANT2(category, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) +#endif + +// Records a single event called "name" immediately, with 0, 1 or 2 +// associated arguments. If the category is not enabled, then this +// does nothing. +// - category and name strings must have application lifetime (statics or +// literals). They may not include " chars. +#define TRACE_EVENT_INSTANT0(category, name) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_INSTANT, \ + category, name, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_INSTANT1(category, name, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_INSTANT, \ + category, name, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) +#define TRACE_EVENT_INSTANT2(category, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_INSTANT, \ + category, name, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val, \ + arg2_name, arg2_val) +#define TRACE_EVENT_COPY_INSTANT0(category, name) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_INSTANT, \ + category, name, TRACE_EVENT_FLAG_COPY) +#define TRACE_EVENT_COPY_INSTANT1(category, name, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_INSTANT, \ + category, name, TRACE_EVENT_FLAG_COPY, arg1_name, arg1_val) +#define TRACE_EVENT_COPY_INSTANT2(category, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_INSTANT, \ + category, name, TRACE_EVENT_FLAG_COPY, arg1_name, arg1_val, \ + arg2_name, arg2_val) + +// Records a single BEGIN event called "name" immediately, with 0, 1 or 2 +// associated arguments. If the category is not enabled, then this +// does nothing. +// - category and name strings must have application lifetime (statics or +// literals). They may not include " chars. +#define TRACE_EVENT_BEGIN0(category, name) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_BEGIN, \ + category, name, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_BEGIN1(category, name, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_BEGIN, \ + category, name, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) +#define TRACE_EVENT_BEGIN2(category, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_BEGIN, \ + category, name, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val, \ + arg2_name, arg2_val) +#define TRACE_EVENT_COPY_BEGIN0(category, name) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_BEGIN, \ + category, name, TRACE_EVENT_FLAG_COPY) +#define TRACE_EVENT_COPY_BEGIN1(category, name, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_BEGIN, \ + category, name, TRACE_EVENT_FLAG_COPY, arg1_name, arg1_val) +#define TRACE_EVENT_COPY_BEGIN2(category, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_BEGIN, \ + category, name, TRACE_EVENT_FLAG_COPY, arg1_name, arg1_val, \ + arg2_name, arg2_val) + +// Records a single END event for "name" immediately. If the category +// is not enabled, then this does nothing. +// - category and name strings must have application lifetime (statics or +// literals). They may not include " chars. +#define TRACE_EVENT_END0(category, name) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_END, \ + category, name, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_END1(category, name, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_END, \ + category, name, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) +#define TRACE_EVENT_END2(category, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_END, \ + category, name, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val, \ + arg2_name, arg2_val) +#define TRACE_EVENT_COPY_END0(category, name) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_END, \ + category, name, TRACE_EVENT_FLAG_COPY) +#define TRACE_EVENT_COPY_END1(category, name, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_END, \ + category, name, TRACE_EVENT_FLAG_COPY, arg1_name, arg1_val) +#define TRACE_EVENT_COPY_END2(category, name, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_END, \ + category, name, TRACE_EVENT_FLAG_COPY, arg1_name, arg1_val, \ + arg2_name, arg2_val) + +// Records the value of a counter called "name" immediately. Value +// must be representable as a 32 bit integer. +// - category and name strings must have application lifetime (statics or +// literals). They may not include " chars. +#define TRACE_COUNTER1(category, name, value) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_COUNTER, \ + category, name, TRACE_EVENT_FLAG_NONE, \ + "value", static_cast<int>(value)) +#define TRACE_COPY_COUNTER1(category, name, value) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_COUNTER, \ + category, name, TRACE_EVENT_FLAG_COPY, \ + "value", static_cast<int>(value)) + +// Records the values of a multi-parted counter called "name" immediately. +// The UI will treat value1 and value2 as parts of a whole, displaying their +// values as a stacked-bar chart. +// - category and name strings must have application lifetime (statics or +// literals). They may not include " chars. +#define TRACE_COUNTER2(category, name, value1_name, value1_val, \ + value2_name, value2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_COUNTER, \ + category, name, TRACE_EVENT_FLAG_NONE, \ + value1_name, static_cast<int>(value1_val), \ + value2_name, static_cast<int>(value2_val)) +#define TRACE_COPY_COUNTER2(category, name, value1_name, value1_val, \ + value2_name, value2_val) \ + INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_COUNTER, \ + category, name, TRACE_EVENT_FLAG_COPY, \ + value1_name, static_cast<int>(value1_val), \ + value2_name, static_cast<int>(value2_val)) + +// Records the value of a counter called "name" immediately. Value +// must be representable as a 32 bit integer. +// - category and name strings must have application lifetime (statics or +// literals). They may not include " chars. +// - |id| is used to disambiguate counters with the same name. It 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. +#define TRACE_COUNTER_ID1(category, name, id, value) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_COUNTER, \ + category, name, id, TRACE_EVENT_FLAG_NONE, \ + "value", static_cast<int>(value)) +#define TRACE_COPY_COUNTER_ID1(category, name, id, value) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_COUNTER, \ + category, name, id, TRACE_EVENT_FLAG_COPY, \ + "value", static_cast<int>(value)) + +// Records the values of a multi-parted counter called "name" immediately. +// The UI will treat value1 and value2 as parts of a whole, displaying their +// values as a stacked-bar chart. +// - category and name strings must have application lifetime (statics or +// literals). They may not include " chars. +// - |id| is used to disambiguate counters with the same name. It 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. +#define TRACE_COUNTER_ID2(category, name, id, value1_name, value1_val, \ + value2_name, value2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_COUNTER, \ + category, name, id, TRACE_EVENT_FLAG_NONE, \ + value1_name, static_cast<int>(value1_val), \ + value2_name, static_cast<int>(value2_val)) +#define TRACE_COPY_COUNTER_ID2(category, name, id, value1_name, value1_val, \ + value2_name, value2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_COUNTER, \ + category, name, id, TRACE_EVENT_FLAG_COPY, \ + value1_name, static_cast<int>(value1_val), \ + value2_name, static_cast<int>(value2_val)) + + +// Records a single ASYNC_BEGIN event called "name" immediately, with 0, 1 or 2 +// associated arguments. If the category is not enabled, then this +// does nothing. +// - category and name strings must have application lifetime (statics or +// literals). They may not include " chars. +// - |id| is used to match the ASYNC_BEGIN event with the ASYNC_END event. ASYNC +// events are considered to match if their category, name and id values 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 +// 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 +// args. +#define TRACE_EVENT_ASYNC_BEGIN0(category, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_BEGIN, \ + category, name, id, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_ASYNC_BEGIN1(category, name, id, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_BEGIN, \ + category, name, id, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) +#define TRACE_EVENT_ASYNC_BEGIN2(category, name, id, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_BEGIN, \ + category, name, id, TRACE_EVENT_FLAG_NONE, \ + arg1_name, arg1_val, arg2_name, arg2_val) +#define TRACE_EVENT_COPY_ASYNC_BEGIN0(category, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_BEGIN, \ + category, name, id, TRACE_EVENT_FLAG_COPY) +#define TRACE_EVENT_COPY_ASYNC_BEGIN1(category, name, id, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_BEGIN, \ + category, name, id, TRACE_EVENT_FLAG_COPY, \ + arg1_name, arg1_val) +#define TRACE_EVENT_COPY_ASYNC_BEGIN2(category, name, id, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_BEGIN, \ + category, 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, name, id, step) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP, \ + category, name, id, TRACE_EVENT_FLAG_NONE, "step", step) +#define TRACE_EVENT_ASYNC_STEP1(category, name, id, step, \ + arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP, \ + category, name, id, TRACE_EVENT_FLAG_NONE, "step", step, \ + arg1_name, arg1_val) +#define TRACE_EVENT_COPY_ASYNC_STEP0(category, name, id, step) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP, \ + category, name, id, TRACE_EVENT_FLAG_COPY, "step", step) +#define TRACE_EVENT_COPY_ASYNC_STEP1(category, name, id, step, \ + arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP, \ + category, name, id, TRACE_EVENT_FLAG_COPY, "step", step, \ + arg1_name, arg1_val) + +// Records a single ASYNC_END event for "name" immediately. If the category +// is not enabled, then this does nothing. +#define TRACE_EVENT_ASYNC_END0(category, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_END, \ + category, name, id, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_ASYNC_END1(category, name, id, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_END, \ + category, name, id, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) +#define TRACE_EVENT_ASYNC_END2(category, name, id, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_END, \ + category, name, id, TRACE_EVENT_FLAG_NONE, \ + arg1_name, arg1_val, arg2_name, arg2_val) +#define TRACE_EVENT_COPY_ASYNC_END0(category, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_END, \ + category, name, id, TRACE_EVENT_FLAG_COPY) +#define TRACE_EVENT_COPY_ASYNC_END1(category, name, id, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_END, \ + category, name, id, TRACE_EVENT_FLAG_COPY, \ + arg1_name, arg1_val) +#define TRACE_EVENT_COPY_ASYNC_END2(category, name, id, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_END, \ + category, name, id, TRACE_EVENT_FLAG_COPY, \ + arg1_name, arg1_val, arg2_name, arg2_val) + + +// Records a single FLOW_BEGIN event called "name" immediately, with 0, 1 or 2 +// associated arguments. If the category is not enabled, then this +// does nothing. +// - category and name strings must have application lifetime (statics or +// literals). They may not include " chars. +// - |id| is used to match the FLOW_BEGIN event with the FLOW_END event. FLOW +// events are considered to match if their category, name and id values 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. +// FLOW events are different from ASYNC events in how they are drawn by the +// tracing UI. A FLOW defines asynchronous data flow, such as posting a task +// (FLOW_BEGIN) and later executing that task (FLOW_END). Expect FLOWs to be +// drawn as lines or arrows from FLOW_BEGIN scopes to FLOW_END scopes. Similar +// to ASYNC, a FLOW can consist of multiple phases. The first phase is defined +// by the FLOW_BEGIN calls. Additional phases can be defined using the FLOW_STEP +// macros. When the operation completes, call FLOW_END. An async operation can +// span threads and processes, but all events in that operation must use the +// same |name| and |id|. Each event can have its own args. +#define TRACE_EVENT_FLOW_BEGIN0(category, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, \ + category, name, id, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_FLOW_BEGIN1(category, name, id, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, \ + category, name, id, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) +#define TRACE_EVENT_FLOW_BEGIN2(category, name, id, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, \ + category, name, id, TRACE_EVENT_FLAG_NONE, \ + arg1_name, arg1_val, arg2_name, arg2_val) +#define TRACE_EVENT_COPY_FLOW_BEGIN0(category, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, \ + category, name, id, TRACE_EVENT_FLAG_COPY) +#define TRACE_EVENT_COPY_FLOW_BEGIN1(category, name, id, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, \ + category, name, id, TRACE_EVENT_FLAG_COPY, \ + arg1_name, arg1_val) +#define TRACE_EVENT_COPY_FLOW_BEGIN2(category, name, id, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_BEGIN, \ + category, name, id, TRACE_EVENT_FLAG_COPY, \ + arg1_name, arg1_val, arg2_name, arg2_val) + +// Records a single FLOW_STEP event for |step| immediately. If the category +// is not enabled, then this does nothing. The |name| and |id| must match the +// FLOW_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_FLOW_STEP0(category, name, id, step) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_STEP, \ + category, name, id, TRACE_EVENT_FLAG_NONE, "step", step) +#define TRACE_EVENT_FLOW_STEP1(category, name, id, step, \ + arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_STEP, \ + category, name, id, TRACE_EVENT_FLAG_NONE, "step", step, \ + arg1_name, arg1_val) +#define TRACE_EVENT_COPY_FLOW_STEP0(category, name, id, step) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_STEP, \ + category, name, id, TRACE_EVENT_FLAG_COPY, "step", step) +#define TRACE_EVENT_COPY_FLOW_STEP1(category, name, id, step, \ + arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_STEP, \ + category, name, id, TRACE_EVENT_FLAG_COPY, "step", step, \ + arg1_name, arg1_val) + +// Records a single FLOW_END event for "name" immediately. If the category +// is not enabled, then this does nothing. +#define TRACE_EVENT_FLOW_END0(category, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, \ + category, name, id, TRACE_EVENT_FLAG_NONE) +#define TRACE_EVENT_FLOW_END1(category, name, id, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, \ + category, name, id, TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val) +#define TRACE_EVENT_FLOW_END2(category, name, id, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, \ + category, name, id, TRACE_EVENT_FLAG_NONE, \ + arg1_name, arg1_val, arg2_name, arg2_val) +#define TRACE_EVENT_COPY_FLOW_END0(category, name, id) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, \ + category, name, id, TRACE_EVENT_FLAG_COPY) +#define TRACE_EVENT_COPY_FLOW_END1(category, name, id, arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, \ + category, name, id, TRACE_EVENT_FLAG_COPY, \ + arg1_name, arg1_val) +#define TRACE_EVENT_COPY_FLOW_END2(category, name, id, arg1_name, arg1_val, \ + arg2_name, arg2_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_FLOW_END, \ + category, name, id, TRACE_EVENT_FLAG_COPY, \ + arg1_name, arg1_val, arg2_name, arg2_val) + + +//////////////////////////////////////////////////////////////////////////////// +// Implementation specific tracing API definitions. + +// Get a pointer to the enabled state of the given trace category. Only +// long-lived literal strings should be given as the category name. The returned +// pointer can be held permanently in a local static for example. If the +// unsigned char is non-zero, tracing is enabled. If tracing is enabled, +// TRACE_EVENT_API_ADD_TRACE_EVENT can be called. It's OK if tracing is disabled +// between the load of the tracing state and the call to +// TRACE_EVENT_API_ADD_TRACE_EVENT, because this flag only provides an early out +// for best performance when tracing is disabled. +// const unsigned char* +// TRACE_EVENT_API_GET_CATEGORY_ENABLED(const char* category_name) +#define TRACE_EVENT_API_GET_CATEGORY_ENABLED \ + base::debug::TraceLog::GetCategoryEnabled + +#if !defined(TRACING_DISABLED) +// Add a trace event to the platform tracing system. +// void TRACE_EVENT_API_ADD_TRACE_EVENT( +// char phase, +// const unsigned char* category_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, +// unsigned char flags) +#define TRACE_EVENT_API_ADD_TRACE_EVENT \ + base::debug::TraceLog::GetInstance()->AddTraceEvent +#endif // !defined(TRACING_DISABLED) + +//////////////////////////////////////////////////////////////////////////////// + +// Implementation detail: trace event macros create temporary variables +// to keep instrumentation overhead low. These macros give each temporary +// variable a unique name based on the line number to prevent name collissions. +#define INTERNAL_TRACE_EVENT_UID3(a,b) \ + trace_event_unique_##a##b +#define INTERNAL_TRACE_EVENT_UID2(a,b) \ + INTERNAL_TRACE_EVENT_UID3(a,b) +#define INTERNAL_TRACE_EVENT_UID(name_prefix) \ + INTERNAL_TRACE_EVENT_UID2(name_prefix, __LINE__) + +// Implementation detail: internal macro to create static category. +// 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) \ + static base::subtle::AtomicWord INTERNAL_TRACE_EVENT_UID(atomic) = 0; \ + const uint8* INTERNAL_TRACE_EVENT_UID(catstatic) = \ + reinterpret_cast<const uint8*>( \ + base::subtle::NoBarrier_Load(&INTERNAL_TRACE_EVENT_UID(atomic))); \ + if (!INTERNAL_TRACE_EVENT_UID(catstatic)) { \ + INTERNAL_TRACE_EVENT_UID(catstatic) = \ + TRACE_EVENT_API_GET_CATEGORY_ENABLED(category); \ + base::subtle::NoBarrier_Store(&INTERNAL_TRACE_EVENT_UID(atomic), \ + reinterpret_cast<base::subtle::AtomicWord>( \ + INTERNAL_TRACE_EVENT_UID(catstatic))); \ + } + +#if !defined(TRACING_DISABLED) +// Implementation detail: internal macro to create static category and add +// event if the category is enabled. +#define INTERNAL_TRACE_EVENT_ADD(phase, category, name, flags, ...) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category); \ + if (*INTERNAL_TRACE_EVENT_UID(catstatic)) { \ + trace_event_internal::AddTraceEvent( \ + phase, INTERNAL_TRACE_EVENT_UID(catstatic), name, \ + trace_event_internal::kNoEventId, flags, ##__VA_ARGS__); \ + } \ + } while (0) + +// Implementation detail: internal macro to create static category and add begin +// event if the category is enabled. Also adds the end event when the scope +// ends. +#define INTERNAL_TRACE_EVENT_ADD_SCOPED(category, name, ...) \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category); \ + 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), \ + name, trace_event_internal::kNoEventId, \ + TRACE_EVENT_FLAG_NONE, ##__VA_ARGS__); \ + INTERNAL_TRACE_EVENT_UID(profileScope).Initialize( \ + INTERNAL_TRACE_EVENT_UID(catstatic), name); \ + } + +// Implementation detail: internal macro to create static category and add +// event if the category is enabled. +#define INTERNAL_TRACE_EVENT_ADD_WITH_ID(phase, category, name, id, flags, \ + ...) \ + do { \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category); \ + if (*INTERNAL_TRACE_EVENT_UID(catstatic)) { \ + 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), \ + name, trace_event_trace_id.data(), trace_event_flags, \ + ##__VA_ARGS__); \ + } \ + } while (0) +#endif // #if !defined(TRACING_DISABLED) + +// Notes regarding the following definitions: +// New values can be added and propagated to third party libraries, but existing +// definitions must never be changed, because third party libraries may use old +// definitions. + +// 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_INSTANT ('I') +#define TRACE_EVENT_PHASE_ASYNC_BEGIN ('S') +#define TRACE_EVENT_PHASE_ASYNC_STEP ('T') +#define TRACE_EVENT_PHASE_ASYNC_END ('F') +#define TRACE_EVENT_PHASE_FLOW_BEGIN ('s') +#define TRACE_EVENT_PHASE_FLOW_STEP ('t') +#define TRACE_EVENT_PHASE_FLOW_END ('f') +#define TRACE_EVENT_PHASE_METADATA ('M') +#define TRACE_EVENT_PHASE_COUNTER ('C') + +// Flags for changing the behavior of TRACE_EVENT_API_ADD_TRACE_EVENT. +#define TRACE_EVENT_FLAG_NONE (static_cast<unsigned char>(0)) +#define TRACE_EVENT_FLAG_COPY (static_cast<unsigned char>(1 << 0)) +#define TRACE_EVENT_FLAG_HAS_ID (static_cast<unsigned char>(1 << 1)) +#define TRACE_EVENT_FLAG_MANGLE_ID (static_cast<unsigned char>(1 << 2)) + +// Type values for identifying types in the TraceValue union. +#define TRACE_VALUE_TYPE_BOOL (static_cast<unsigned char>(1)) +#define TRACE_VALUE_TYPE_UINT (static_cast<unsigned char>(2)) +#define TRACE_VALUE_TYPE_INT (static_cast<unsigned char>(3)) +#define TRACE_VALUE_TYPE_DOUBLE (static_cast<unsigned char>(4)) +#define TRACE_VALUE_TYPE_POINTER (static_cast<unsigned char>(5)) +#define TRACE_VALUE_TYPE_STRING (static_cast<unsigned char>(6)) +#define TRACE_VALUE_TYPE_COPY_STRING (static_cast<unsigned char>(7)) + +namespace trace_event_internal { + +// Specify these values when the corresponding argument of AddTraceEvent is not +// used. +const int kZeroNumArgs = 0; +const unsigned long long kNoEventId = 0; + +// TraceID encapsulates an ID that can either be an integer or pointer. Pointers +// are mangled with the Process ID so that they are unlikely to collide when the +// same pointer is used on different processes. +class TraceID { + public: + class ForceMangle { + public: + explicit ForceMangle(unsigned long long id) : data_(id) {} + explicit ForceMangle(unsigned long id) : data_(id) {} + explicit ForceMangle(unsigned int id) : data_(id) {} + explicit ForceMangle(unsigned short id) : data_(id) {} + explicit ForceMangle(unsigned char id) : data_(id) {} + explicit ForceMangle(long long id) + : data_(static_cast<unsigned long long>(id)) {} + explicit ForceMangle(long id) + : data_(static_cast<unsigned long long>(id)) {} + explicit ForceMangle(int id) + : data_(static_cast<unsigned long long>(id)) {} + explicit ForceMangle(short id) + : data_(static_cast<unsigned long long>(id)) {} + explicit ForceMangle(signed char id) + : data_(static_cast<unsigned long long>(id)) {} + + unsigned long long data() const { return data_; } + + private: + unsigned long long data_; + }; + + explicit TraceID(const void* id, unsigned char* flags) + : data_(reinterpret_cast<unsigned long long>(id)) { + *flags |= TRACE_EVENT_FLAG_MANGLE_ID; + } + explicit TraceID(ForceMangle id, unsigned char* flags) : data_(id.data()) { + *flags |= TRACE_EVENT_FLAG_MANGLE_ID; + } + explicit TraceID(unsigned long long id, unsigned char* flags) + : data_(id) { (void)flags; } + explicit TraceID(unsigned long id, unsigned char* flags) + : data_(id) { (void)flags; } + explicit TraceID(unsigned int id, unsigned char* flags) + : data_(id) { (void)flags; } + explicit TraceID(unsigned short id, unsigned char* flags) + : data_(id) { (void)flags; } + explicit TraceID(unsigned char id, unsigned char* flags) + : data_(id) { (void)flags; } + explicit TraceID(long long id, unsigned char* flags) + : data_(static_cast<unsigned long long>(id)) { (void)flags; } + explicit TraceID(long id, unsigned char* flags) + : data_(static_cast<unsigned long long>(id)) { (void)flags; } + explicit TraceID(int id, unsigned char* flags) + : data_(static_cast<unsigned long long>(id)) { (void)flags; } + explicit TraceID(short id, unsigned char* flags) + : data_(static_cast<unsigned long long>(id)) { (void)flags; } + explicit TraceID(signed char id, unsigned char* flags) + : data_(static_cast<unsigned long long>(id)) { (void)flags; } + + unsigned long long data() const { return data_; } + + private: + unsigned long long data_; +}; + +// Simple union to store various types as unsigned long long. +union TraceValueUnion { + bool as_bool; + unsigned long long as_uint; + long long as_int; + double as_double; + const void* as_pointer; + const char* as_string; +}; + +// Simple container for const char* that should be copied instead of retained. +class TraceStringWithCopy { + public: + explicit TraceStringWithCopy(const char* str) : str_(str) {} + operator const char* () const { return str_; } + private: + const char* str_; +}; + +// Define SetTraceValue for each allowed type. It stores the type and +// value in the return arguments. This allows this API to avoid declaring any +// structures so that it is portable to third_party libraries. +#define INTERNAL_DECLARE_SET_TRACE_VALUE(actual_type, \ + union_member, \ + value_type_id) \ + static inline void SetTraceValue(actual_type arg, \ + unsigned char* type, \ + unsigned long long* value) { \ + TraceValueUnion type_value; \ + type_value.union_member = arg; \ + *type = value_type_id; \ + *value = type_value.as_uint; \ + } +// Simpler form for int types that can be safely casted. +#define INTERNAL_DECLARE_SET_TRACE_VALUE_INT(actual_type, \ + value_type_id) \ + static inline void SetTraceValue(actual_type arg, \ + unsigned char* type, \ + unsigned long long* value) { \ + *type = value_type_id; \ + *value = static_cast<unsigned long long>(arg); \ + } + +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned long long, TRACE_VALUE_TYPE_UINT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned long, TRACE_VALUE_TYPE_UINT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned int, TRACE_VALUE_TYPE_UINT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned short, TRACE_VALUE_TYPE_UINT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned char, TRACE_VALUE_TYPE_UINT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(long long, TRACE_VALUE_TYPE_INT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(long, TRACE_VALUE_TYPE_INT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(int, TRACE_VALUE_TYPE_INT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(short, TRACE_VALUE_TYPE_INT) +INTERNAL_DECLARE_SET_TRACE_VALUE_INT(signed char, TRACE_VALUE_TYPE_INT) +INTERNAL_DECLARE_SET_TRACE_VALUE(bool, as_bool, TRACE_VALUE_TYPE_BOOL) +INTERNAL_DECLARE_SET_TRACE_VALUE(double, as_double, TRACE_VALUE_TYPE_DOUBLE) +INTERNAL_DECLARE_SET_TRACE_VALUE(const void*, as_pointer, + TRACE_VALUE_TYPE_POINTER) +INTERNAL_DECLARE_SET_TRACE_VALUE(const char*, as_string, + TRACE_VALUE_TYPE_STRING) +INTERNAL_DECLARE_SET_TRACE_VALUE(const TraceStringWithCopy&, as_string, + TRACE_VALUE_TYPE_COPY_STRING) + +#undef INTERNAL_DECLARE_SET_TRACE_VALUE +#undef INTERNAL_DECLARE_SET_TRACE_VALUE_INT + +// std::string version of SetTraceValue so that trace arguments can be strings. +static inline void SetTraceValue(const std::string& arg, + unsigned char* type, + unsigned long long* value) { + TraceValueUnion type_value; + type_value.as_string = arg.c_str(); + *type = TRACE_VALUE_TYPE_COPY_STRING; + *value = type_value.as_uint; +} + +// These AddTraceEvent template functions are defined here instead of in the +// macro, because the arg_values could be temporary objects, such as +// std::string. In order to store pointers to the internal c_str and pass +// through to the tracing API, the arg_values must live throughout +// these procedures. + +static inline void AddTraceEvent(char phase, + const unsigned char* category_enabled, + const char* name, + unsigned long long id, + unsigned char flags) { + TRACE_EVENT_API_ADD_TRACE_EVENT( + phase, category_enabled, name, id, + kZeroNumArgs, NULL, NULL, NULL, + flags); +} + +template<class ARG1_TYPE> +static inline void AddTraceEvent(char phase, + const unsigned char* category_enabled, + const char* name, + unsigned long long id, + unsigned char flags, + const char* arg1_name, + const ARG1_TYPE& arg1_val) { + const int num_args = 1; + unsigned char arg_types[1]; + unsigned long long arg_values[1]; + trace_event_internal::SetTraceValue(arg1_val, &arg_types[0], &arg_values[0]); + TRACE_EVENT_API_ADD_TRACE_EVENT( + phase, category_enabled, name, id, + num_args, &arg1_name, arg_types, arg_values, + flags); +} + +template<class ARG1_TYPE, class ARG2_TYPE> +static inline void AddTraceEvent(char phase, + const unsigned char* category_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) { + const int num_args = 2; + const char* arg_names[2] = { arg1_name, arg2_name }; + unsigned char arg_types[2]; + unsigned long long arg_values[2]; + trace_event_internal::SetTraceValue(arg1_val, &arg_types[0], &arg_values[0]); + trace_event_internal::SetTraceValue(arg2_val, &arg_types[1], &arg_values[1]); + TRACE_EVENT_API_ADD_TRACE_EVENT( + phase, category_enabled, name, id, + num_args, arg_names, arg_types, arg_values, + flags); +} + +// Used by TRACE_EVENTx macro. Do not use directly. +class BASE_EXPORT TraceEndOnScopeClose { + public: + // Note: members of data_ intentionally left uninitialized. See Initialize. + TraceEndOnScopeClose() : p_data_(NULL) {} + ~TraceEndOnScopeClose() { + if (p_data_) + AddEventIfEnabled(); + } + + void Initialize(const unsigned char* category_enabled, + const char* name); + + private: + // Add the end event if the category is still enabled. + void AddEventIfEnabled(); + + // 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 + // members of this class instead, compiler warnings occur about potential + // uninitialized accesses. + struct Data { + const unsigned char* category_enabled; + const char* name; + }; + Data* p_data_; + Data data_; +}; + + +} // namespace trace_event_internal + +#endif // BASE_DEBUG_TRACE_EVENT_H_
diff --git a/src/base/debug/trace_event_android.cc b/src/base/debug/trace_event_android.cc new file mode 100644 index 0000000..c56c3c1 --- /dev/null +++ b/src/base/debug/trace_event_android.cc
@@ -0,0 +1,144 @@ +// 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/debug/trace_event_impl.h" + +#include <fcntl.h> + +#include "base/debug/trace_event.h" +#include "base/file_util.h" +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/stringprintf.h" + +namespace { + +int g_atrace_fd = -1; +const char* kATraceMarkerFile = "/sys/kernel/debug/tracing/trace_marker"; + +} // namespace + +namespace base { +namespace debug { + +// static +void TraceLog::InitATrace() { + DCHECK(g_atrace_fd == -1); + g_atrace_fd = open(kATraceMarkerFile, O_WRONLY | O_APPEND); + if (g_atrace_fd == -1) + LOG(WARNING) << "Couldn't open " << kATraceMarkerFile; +} + +void TraceLog::SendToATrace(char phase, + const char* category, + const char* name, + int num_args, + const char** arg_names, + const unsigned char* arg_types, + const unsigned long long* arg_values) { + if (g_atrace_fd == -1) + return; + + switch (phase) { + case TRACE_EVENT_PHASE_BEGIN: + case TRACE_EVENT_PHASE_INSTANT: { + std::string out = StringPrintf("B|%d|%s-%s", getpid(), category, name); + for (int i = 0; i < num_args; ++i) { + out += (i == 0 ? '|' : ';'); + out += arg_names[i]; + out += '='; + TraceEvent::TraceValue value; + value.as_uint = arg_values[i]; + std::string::size_type value_start = out.length(); + TraceEvent::AppendValueAsJSON(arg_types[i], value, &out); + // Remove the quotes which may confuse the atrace script. + ReplaceSubstringsAfterOffset(&out, value_start, "\\\"", "'"); + ReplaceSubstringsAfterOffset(&out, value_start, "\"", ""); + // Replace chars used for separators with similar chars in the value. + std::replace(out.begin() + value_start, out.end(), ';', ','); + std::replace(out.begin() + value_start, out.end(), '|', '!'); + } + write(g_atrace_fd, out.c_str(), out.size()); + + if (phase != TRACE_EVENT_PHASE_INSTANT) + break; + // Fall through. Simulate an instance event with a pair of begin/end. + } + case TRACE_EVENT_PHASE_END: { + // Though a single 'E' is enough, here append pid and name so that + // unpaired events can be found easily. + std::string out = StringPrintf("E|%d|%s-%s", getpid(), category, name); + write(g_atrace_fd, out.c_str(), out.size()); + 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 = StringPrintf( + "C|%d|%s-%s-%s|%d", + getpid(), category, name, + arg_names[i], static_cast<int>(arg_values[i])); + write(g_atrace_fd, out.c_str(), out.size()); + } + break; + + default: + // Do nothing. + break; + } +} + +void TraceLog::AddClockSyncMetadataEvents() { + // Since Android does not support sched_setaffinity, we cannot establish clock + // sync unless the scheduler clock is set to global. If the trace_clock file + // can't be read, we will assume the kernel doesn't support tracing and do + // nothing. + std::string clock_mode; + if (!file_util::ReadFileToString( + FilePath("/sys/kernel/debug/tracing/trace_clock"), &clock_mode)) + return; + + if (clock_mode != "local [global]\n") { + DLOG(WARNING) << + "The kernel's tracing clock must be set to global in order for " << + "trace_event to be synchronized with . Do this by\n" << + " echo global > /sys/kerel/debug/tracing/trace_clock"; + return; + } + + int atrace_fd = g_atrace_fd; + if (atrace_fd == -1) { + // This function may be called when atrace is not enabled. + atrace_fd = open(kATraceMarkerFile, O_WRONLY | O_APPEND); + if (atrace_fd == -1) { + LOG(WARNING) << "Couldn't open " << kATraceMarkerFile; + return; + } + } + + // 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()) != 0) { + DLOG(WARNING) << "Couldn't write to " << kATraceMarkerFile << ": " + << strerror(errno); + } + + if (g_atrace_fd == -1) + close(atrace_fd); +} + +// Must be called with lock_ locked. +void TraceLog::ApplyATraceEnabledFlag(unsigned char* category_enabled) { + if (g_atrace_fd != -1) + *category_enabled |= ATRACE_ENABLED; +} + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/trace_event_impl.cc b/src/base/debug/trace_event_impl.cc new file mode 100644 index 0000000..5af61e5 --- /dev/null +++ b/src/base/debug/trace_event_impl.cc
@@ -0,0 +1,830 @@ +// 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/debug/trace_event_impl.h" + +#include <algorithm> + +#include "base/bind.h" +#include "base/debug/leak_annotations.h" +#include "base/debug/trace_event.h" +#include "base/format_macros.h" +#include "base/lazy_instance.h" +#include "base/memory/singleton.h" +#include "base/process_util.h" +#include "base/stl_util.h" +#include "base/stringprintf.h" +#include "base/string_tokenizer.h" +#include "base/string_util.h" +#include "base/sys_info.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread_local.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" + +#if defined(OS_WIN) +#include "base/debug/trace_event_win.h" +#endif + +#if defined(OS_STARBOARD) +#include "starboard/types.h" +#endif + +class DeleteTraceLogForTesting { + public: + static void Delete() { + Singleton<base::debug::TraceLog, + StaticMemorySingletonTraits<base::debug::TraceLog> >::OnExit(0); + } +}; + +namespace base { +namespace debug { + +// Controls the number of trace events we will buffer in-memory +// before throwing them away. +const size_t kTraceEventBufferSize = 500000; +const size_t kTraceEventBatchSize = 1000; + +#define TRACE_EVENT_MAX_CATEGORIES 100 + +namespace { + +// Parallel arrays g_categories and g_category_enabled are separate so that +// a pointer to a member of g_category_enabled can be easily converted to an +// index into g_categories. This allows macros to deal only with char enabled +// pointers from g_category_enabled, and we can convert internally to determine +// the category name from the char enabled pointer. +const char* g_categories[TRACE_EVENT_MAX_CATEGORIES] = { + "tracing already shutdown", + "tracing categories exhausted; must increase TRACE_EVENT_MAX_CATEGORIES", + "__metadata", +}; +// The enabled flag is char instead of bool so that the API can be used from C. +unsigned char g_category_enabled[TRACE_EVENT_MAX_CATEGORIES] = { 0 }; +const int g_category_already_shutdown = 0; +const int g_category_categories_exhausted = 1; +const int g_category_metadata = 2; +int g_category_index = 3; // skip initial 3 categories + +// The most-recently captured name of the current thread +LazyInstance<ThreadLocalPointer<const char> >::Leaky + g_current_thread_name = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// +// TraceEvent +// +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +size_t GetAllocLength(const char* str) { return str ? strlen(str) + 1 : 0; } + +// Copies |*member| into |*buffer|, sets |*member| to point to this new +// location, and then advances |*buffer| by the amount written. +void CopyTraceEventParameter(char** buffer, + const char** member, + const char* end) { + if (*member) { + size_t written = strlcpy(*buffer, *member, end - *buffer) + 1; + DCHECK_LE(static_cast<int>(written), end - *buffer); + *member = *buffer; + *buffer += written; + } +} + +} // namespace + +TraceEvent::TraceEvent() + : id_(0u), + category_enabled_(NULL), + name_(NULL), + thread_id_(0), + phase_(TRACE_EVENT_PHASE_BEGIN), + flags_(0) { + arg_names_[0] = NULL; + arg_names_[1] = NULL; + memset(arg_values_, 0, sizeof(arg_values_)); +} + +TraceEvent::TraceEvent(int thread_id, + TimeTicks timestamp, + char phase, + const unsigned char* category_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, + unsigned char flags) + : timestamp_(timestamp), + id_(id), + category_enabled_(category_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; + int i = 0; + for (; i < num_args; ++i) { + arg_names_[i] = arg_names[i]; + arg_values_[i].as_uint = arg_values[i]; + arg_types_[i] = arg_types[i]; + } + for (; i < kTraceMaxNumArgs; ++i) { + arg_names_[i] = NULL; + arg_values_[i].as_uint = 0u; + arg_types_[i] = TRACE_VALUE_TYPE_UINT; + } + + bool copy = !!(flags & TRACE_EVENT_FLAG_COPY); + size_t alloc_size = 0; + if (copy) { + alloc_size += GetAllocLength(name); + for (i = 0; i < num_args; ++i) { + alloc_size += GetAllocLength(arg_names_[i]); + if (arg_types_[i] == TRACE_VALUE_TYPE_STRING) + arg_types_[i] = TRACE_VALUE_TYPE_COPY_STRING; + } + } + + bool arg_is_copy[kTraceMaxNumArgs]; + for (i = 0; i < num_args; ++i) { + // We only take a copy of arg_vals if they are of type COPY_STRING. + arg_is_copy[i] = (arg_types_[i] == TRACE_VALUE_TYPE_COPY_STRING); + if (arg_is_copy[i]) + alloc_size += GetAllocLength(arg_values_[i].as_string); + } + + if (alloc_size) { + parameter_copy_storage_ = new base::RefCountedString; + parameter_copy_storage_->data().resize(alloc_size); + char* ptr = string_as_array(¶meter_copy_storage_->data()); + const char* end = ptr + alloc_size; + if (copy) { + CopyTraceEventParameter(&ptr, &name_, end); + for (i = 0; i < num_args; ++i) + CopyTraceEventParameter(&ptr, &arg_names_[i], end); + } + for (i = 0; i < num_args; ++i) { + if (arg_is_copy[i]) + CopyTraceEventParameter(&ptr, &arg_values_[i].as_string, end); + } + DCHECK_EQ(end, ptr) << "Overrun by " << ptr - end; + } +} + +TraceEvent::~TraceEvent() { +} + +// 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"; + break; + case TRACE_VALUE_TYPE_UINT: + StringAppendF(out, "%" PRIu64, static_cast<uint64>(value.as_uint)); + break; + 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); + 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. + StringAppendF(out, "\"%" PRIx64 "\"", static_cast<uint64>( + reinterpret_cast<intptr_t>( + value.as_pointer))); + 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 += "\""; + break; + default: + NOTREACHED() << "Don't know how to print this value"; + break; + } +} + +void TraceEvent::AppendEventsAsJSON(const std::vector<TraceEvent>& events, + size_t start, + size_t count, + std::string* out) { + for (size_t i = 0; i < count && start + i < events.size(); ++i) { + if (i > 0) + *out += ","; + events[i + start].AppendAsJSON(out); + } +} + +void TraceEvent::AppendAsJSON(std::string* out) const { + int64 time_int64 = timestamp_.ToInternalValue(); + int process_id = TraceLog::GetInstance()->process_id(); + // Category name checked at category creation time. + DCHECK(!strchr(name_, '"')); + StringAppendF(out, + "{\"cat\":\"%s\",\"pid\":%i,\"tid\":%i,\"ts\":%" PRId64 "," + "\"ph\":\"%c\",\"name\":\"%s\",\"args\":{", + TraceLog::GetCategoryName(category_enabled_), + process_id, + thread_id_, + time_int64, + phase_, + name_); + + // Output argument names and values, stop at first NULL argument name. + for (int i = 0; i < kTraceMaxNumArgs && arg_names_[i]; ++i) { + if (i > 0) + *out += ","; + *out += "\""; + *out += arg_names_[i]; + *out += "\":"; + AppendValueAsJSON(arg_types_[i], arg_values_[i], out); + } + *out += "}"; + + // If id_ is set, print it out as a hex string so we don't loose any + // bits (it might be a 64-bit pointer). + if (flags_ & TRACE_EVENT_FLAG_HAS_ID) + StringAppendF(out, ",\"id\":\"%" PRIx64 "\"", static_cast<uint64>(id_)); + *out += "}"; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// TraceResultBuffer +// +//////////////////////////////////////////////////////////////////////////////// + +TraceResultBuffer::OutputCallback + TraceResultBuffer::SimpleOutput::GetCallback() { + return base::Bind(&SimpleOutput::Append, base::Unretained(this)); +} + +void TraceResultBuffer::SimpleOutput::Append( + const std::string& json_trace_output) { + json_output += json_trace_output; +} + +TraceResultBuffer::TraceResultBuffer() : append_comma_(false) { +} + +TraceResultBuffer::~TraceResultBuffer() { +} + +void TraceResultBuffer::SetOutputCallback( + const OutputCallback& json_chunk_callback) { + output_callback_ = json_chunk_callback; +} + +void TraceResultBuffer::Start() { + append_comma_ = false; + output_callback_.Run("["); +} + +void TraceResultBuffer::AddFragment(const std::string& trace_fragment) { + if (append_comma_) + output_callback_.Run(","); + append_comma_ = true; + output_callback_.Run(trace_fragment); +} + +void TraceResultBuffer::Finish() { + output_callback_.Run("]"); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// TraceLog +// +//////////////////////////////////////////////////////////////////////////////// + +TraceLog::NotificationHelper::NotificationHelper(TraceLog* trace_log) + : trace_log_(trace_log), + notification_(0) { +} + +TraceLog::NotificationHelper::~NotificationHelper() { +} + +void TraceLog::NotificationHelper::AddNotificationWhileLocked( + int notification) { + if (trace_log_->notification_callback_.is_null()) + return; + if (notification_ == 0) + callback_copy_ = trace_log_->notification_callback_; + notification_ |= notification; +} + +void TraceLog::NotificationHelper::SendNotificationIfAny() { + if (notification_) + callback_copy_.Run(notification_); +} + +// static +TraceLog* TraceLog::GetInstance() { + return Singleton<TraceLog, StaticMemorySingletonTraits<TraceLog> >::get(); +} + +TraceLog::TraceLog() + : enabled_(false), + dispatching_to_observer_list_(false), + watch_category_(NULL) { + // 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 + // macros fast. + // TODO(jbates): ANNOTATE_BENIGN_RACE_SIZED crashes windows TSAN bots: + // ANNOTATE_BENIGN_RACE_SIZED(g_category_enabled, sizeof(g_category_enabled), + // "trace_event category enabled"); + for (int i = 0; i < TRACE_EVENT_MAX_CATEGORIES; ++i) { + ANNOTATE_BENIGN_RACE(&g_category_enabled[i], + "trace_event category enabled"); + } +#if defined(OS_NACL) // NaCl shouldn't expose the process id. + SetProcessID(0); +#else + SetProcessID(static_cast<int>(base::GetCurrentProcId())); +#endif +} + +TraceLog::~TraceLog() { +} + +const unsigned char* TraceLog::GetCategoryEnabled(const char* name) { + TraceLog* tracelog = GetInstance(); + if (!tracelog) { + DCHECK(!g_category_enabled[g_category_already_shutdown]); + return &g_category_enabled[g_category_already_shutdown]; + } + return tracelog->GetCategoryEnabledInternal(name); +} + +const char* TraceLog::GetCategoryName(const unsigned char* category_enabled) { + // Calculate the index of the category by finding category_enabled in + // g_category_enabled array. + uintptr_t category_begin = reinterpret_cast<uintptr_t>(g_category_enabled); + uintptr_t category_ptr = reinterpret_cast<uintptr_t>(category_enabled); + DCHECK(category_ptr >= category_begin && + category_ptr < reinterpret_cast<uintptr_t>(g_category_enabled + + TRACE_EVENT_MAX_CATEGORIES)) << + "out of bounds category pointer"; + uintptr_t category_index = + (category_ptr - category_begin) / sizeof(g_category_enabled[0]); + return g_categories[category_index]; +} + +static void EnableMatchingCategory(int category_index, + const std::vector<std::string>& patterns, + unsigned char matched_value, + unsigned char unmatched_value) { + std::vector<std::string>::const_iterator ci = patterns.begin(); + bool is_match = false; + for (; ci != patterns.end(); ++ci) { + is_match = MatchPattern(g_categories[category_index], ci->c_str()); + if (is_match) + break; + } + g_category_enabled[category_index] = is_match ? + matched_value : unmatched_value; +} + +// Enable/disable each category based on the category filters in |patterns|. +// If the category name matches one of the patterns, its enabled status is set +// to |matched_value|. Otherwise its enabled status is set to |unmatched_value|. +static void EnableMatchingCategories(const std::vector<std::string>& patterns, + unsigned char matched_value, + unsigned char unmatched_value) { + for (int i = 0; i < g_category_index; i++) + EnableMatchingCategory(i, patterns, matched_value, unmatched_value); +} + +const unsigned char* TraceLog::GetCategoryEnabledInternal(const char* name) { + AutoLock lock(lock_); + DCHECK(!strchr(name, '"')) << "Category names may not contain double quote"; + + unsigned char* category_enabled = NULL; + // Search for pre-existing category matching this name + for (int i = 0; i < g_category_index; i++) { + if (strcmp(g_categories[i], name) == 0) { + category_enabled = &g_category_enabled[i]; + break; + } + } + + if (!category_enabled) { + // Create a new category + DCHECK(g_category_index < TRACE_EVENT_MAX_CATEGORIES) << + "must increase TRACE_EVENT_MAX_CATEGORIES"; + if (g_category_index < TRACE_EVENT_MAX_CATEGORIES) { + int new_index = g_category_index++; + // Don't hold on to the name pointer, so that we can create categories + // with strings not known at compile time (this is required by + // SetWatchEvent). + const char* new_name = base::strdup(name); + ANNOTATE_LEAKING_OBJECT_PTR(new_name); + g_categories[new_index] = new_name; + DCHECK(!g_category_enabled[new_index]); + if (enabled_) { + // Note that if both included and excluded_categories are empty, the + // else clause below excludes nothing, thereby enabling this category. + if (!included_categories_.empty()) { + EnableMatchingCategory(new_index, included_categories_, + CATEGORY_ENABLED, 0); + } else { + EnableMatchingCategory(new_index, excluded_categories_, + 0, CATEGORY_ENABLED); + } + } else { + g_category_enabled[new_index] = 0; + } + category_enabled = &g_category_enabled[new_index]; + } else { + category_enabled = &g_category_enabled[g_category_categories_exhausted]; + } + } +#if defined(OS_ANDROID) + ApplyATraceEnabledFlag(category_enabled); +#endif + return category_enabled; +} + +void TraceLog::GetKnownCategories(std::vector<std::string>* categories) { + AutoLock lock(lock_); + for (int i = 0; i < g_category_index; i++) + categories->push_back(g_categories[i]); +} + +void TraceLog::SetEnabled(const std::vector<std::string>& included_categories, + const std::vector<std::string>& excluded_categories) { +#if defined(TRACING_DISABLED) + return; +#else // defined(TRACING_DISABLED) + AutoLock lock(lock_); + if (enabled_) + return; + + if (dispatching_to_observer_list_) { + DLOG(ERROR) << + "Cannot manipulate TraceLog::Enabled state from an observer."; + return; + } + + dispatching_to_observer_list_ = true; + FOR_EACH_OBSERVER(EnabledStateChangedObserver, enabled_state_observer_list_, + OnTraceLogWillEnable()); + dispatching_to_observer_list_ = false; + + logged_events_.reserve(256 * 1024); + enabled_ = true; + included_categories_ = included_categories; + excluded_categories_ = excluded_categories; + // Note that if both included and excluded_categories are empty, the else + // clause below excludes nothing, thereby enabling all categories. + if (!included_categories_.empty()) + EnableMatchingCategories(included_categories_, CATEGORY_ENABLED, 0); + else + EnableMatchingCategories(excluded_categories_, 0, CATEGORY_ENABLED); +#endif // defined(TRACING_DISABLED) +} + +void TraceLog::SetEnabled(const std::string& categories) { + std::vector<std::string> included, excluded; + // Tokenize list of categories, delimited by ','. + StringTokenizer tokens(categories, ","); + while (tokens.GetNext()) { + bool is_included = true; + std::string category = tokens.token(); + // Excluded categories start with '-'. + if (category.at(0) == '-') { + // Remove '-' from category string. + category = category.substr(1); + is_included = false; + } + if (is_included) + included.push_back(category); + else + excluded.push_back(category); + } + SetEnabled(included, excluded); +} + +void TraceLog::GetEnabledTraceCategories( + std::vector<std::string>* included_out, + std::vector<std::string>* excluded_out) { + AutoLock lock(lock_); + if (enabled_) { + *included_out = included_categories_; + *excluded_out = excluded_categories_; + } +} + +void TraceLog::SetDisabled() { + AutoLock lock(lock_); + if (!enabled_) + return; + + if (dispatching_to_observer_list_) { + DLOG(ERROR) + << "Cannot manipulate TraceLog::Enabled state from an observer."; + return; + } + + dispatching_to_observer_list_ = true; + FOR_EACH_OBSERVER(EnabledStateChangedObserver, enabled_state_observer_list_, + OnTraceLogWillDisable()); + dispatching_to_observer_list_ = false; + + enabled_ = false; + included_categories_.clear(); + excluded_categories_.clear(); + watch_category_ = NULL; + watch_event_name_ = ""; + for (int i = 0; i < g_category_index; i++) + g_category_enabled[i] = 0; + AddThreadNameMetadataEvents(); +#if defined(OS_ANDROID) + AddClockSyncMetadataEvents(); +#endif +} + +void TraceLog::SetEnabled(bool enabled) { + if (enabled) + SetEnabled(std::vector<std::string>(), std::vector<std::string>()); + else + SetDisabled(); +} + +void TraceLog::AddEnabledStateObserver(EnabledStateChangedObserver* listener) { + enabled_state_observer_list_.AddObserver(listener); +} + +void TraceLog::RemoveEnabledStateObserver( + EnabledStateChangedObserver* listener) { + enabled_state_observer_list_.RemoveObserver(listener); +} + +float TraceLog::GetBufferPercentFull() const { + return (float)((double)logged_events_.size()/(double)kTraceEventBufferSize); +} + +void TraceLog::SetNotificationCallback( + const TraceLog::NotificationCallback& cb) { + AutoLock lock(lock_); + notification_callback_ = cb; +} + +void TraceLog::Flush(const TraceLog::OutputCallback& cb) { + std::vector<TraceEvent> previous_logged_events; + { + AutoLock lock(lock_); + previous_logged_events.swap(logged_events_); + } // release lock + + for (size_t i = 0; + i < previous_logged_events.size(); + i += kTraceEventBatchSize) { + scoped_refptr<RefCountedString> json_events_str_ptr = + new RefCountedString(); + TraceEvent::AppendEventsAsJSON(previous_logged_events, + i, + kTraceEventBatchSize, + &(json_events_str_ptr->data())); + cb.Run(json_events_str_ptr); + } +} + +void TraceLog::FlushWithRawEvents( + const TraceLog::RawEventOutputCallback& raw_event_callback, + const TraceLog::OutputCallback& json_output_callback) { + std::vector<TraceEvent> previous_logged_events; + { + AutoLock lock(lock_); + previous_logged_events.swap(logged_events_); + } // release lock + + for (size_t i = 0; + i < previous_logged_events.size(); + i += kTraceEventBatchSize) { + if (!raw_event_callback.is_null()) { + for (size_t j = i; j < i + kTraceEventBatchSize; ++j) { + if (j >= previous_logged_events.size()) { + break; + } + raw_event_callback.Run(previous_logged_events[j]); + } + } + + if (!json_output_callback.is_null()) { + scoped_refptr<RefCountedString> json_events_str_ptr = + new RefCountedString(); + TraceEvent::AppendEventsAsJSON(previous_logged_events, + i, + kTraceEventBatchSize, + &(json_events_str_ptr->data())); + json_output_callback.Run(json_events_str_ptr); + } + } +} + +void TraceLog::AddTraceEvent(char phase, + const unsigned char* category_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, + unsigned char flags) { + DCHECK(name); + +#if defined(OS_ANDROID) + SendToATrace(phase, GetCategoryName(category_enabled), name, + num_args, arg_names, arg_types, arg_values); +#endif + + TimeTicks now = TimeTicks::NowFromSystemTraceTime() - time_offset_; + NotificationHelper notifier(this); + { + AutoLock lock(lock_); + if (*category_enabled != CATEGORY_ENABLED) + return; + if (logged_events_.size() >= kTraceEventBufferSize) + return; + + int thread_id = static_cast<int>(PlatformThread::CurrentId()); + + const char* new_name = PlatformThread::GetName(); + // Check if the thread name has been set or changed since the previous + // call (if any), but don't bother if the new name is empty. Note this will + // not detect a thread name change within the same char* buffer address: we + // favor common case performance over corner case correctness. + if (new_name != g_current_thread_name.Get().Get() && + new_name && *new_name) { + g_current_thread_name.Get().Set(new_name); + base::hash_map<int, std::string>::iterator existing_name = + thread_names_.find(thread_id); + if (existing_name == thread_names_.end()) { + // This is a new thread id, and a new name. + thread_names_[thread_id] = new_name; + } else { + // This is a thread id that we've seen before, but potentially with a + // new name. + std::vector<base::StringPiece> existing_names; + Tokenize(existing_name->second, ",", &existing_names); + bool found = std::find(existing_names.begin(), + existing_names.end(), + new_name) != existing_names.end(); + if (!found) { + existing_name->second.push_back(','); + existing_name->second.append(new_name); + } + } + } + + if (flags & TRACE_EVENT_FLAG_MANGLE_ID) + id ^= process_id_hash_; + + logged_events_.push_back( + TraceEvent(thread_id, + now, phase, category_enabled, name, id, + num_args, arg_names, arg_types, arg_values, + flags)); + + if (logged_events_.size() == kTraceEventBufferSize) + notifier.AddNotificationWhileLocked(TRACE_BUFFER_FULL); + + if (watch_category_ == category_enabled && watch_event_name_ == name) + notifier.AddNotificationWhileLocked(EVENT_WATCH_NOTIFICATION); + } // release lock + + notifier.SendNotificationIfAny(); +} + +void TraceLog::AddTraceEventEtw(char phase, + const char* name, + const void* id, + const char* extra) { +#if defined(OS_WIN) + TraceEventETWProvider::Trace(name, phase, id, extra); +#endif + INTERNAL_TRACE_EVENT_ADD(phase, "ETW Trace Event", name, + TRACE_EVENT_FLAG_COPY, "id", id, "extra", extra); +} + +void TraceLog::AddTraceEventEtw(char phase, + const char* name, + const void* id, + const std::string& extra) +{ +#if defined(OS_WIN) + TraceEventETWProvider::Trace(name, phase, id, extra); +#endif + INTERNAL_TRACE_EVENT_ADD(phase, "ETW Trace Event", name, + 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 = GetCategoryEnabled(category_name.c_str()); + int notify_count = 0; + { + AutoLock lock(lock_); + watch_category_ = category; + watch_event_name_ = event_name; + + // First, search existing events for watch event because we want to catch it + // even if it has already occurred. + for (size_t i = 0u; i < logged_events_.size(); ++i) { + if (category == logged_events_[i].category_enabled() && + strcmp(event_name.c_str(), logged_events_[i].name()) == 0) { + ++notify_count; + } + } + } // release lock + + // Send notification for each event found. + for (int i = 0; i < notify_count; ++i) { + NotificationHelper notifier(this); + lock_.Acquire(); + notifier.AddNotificationWhileLocked(EVENT_WATCH_NOTIFICATION); + lock_.Release(); + notifier.SendNotificationIfAny(); + } +} + +void TraceLog::CancelWatchEvent() { + AutoLock lock(lock_); + watch_category_ = NULL; + watch_event_name_ = ""; +} + +void TraceLog::AddThreadNameMetadataEvents() { + lock_.AssertAcquired(); + for(base::hash_map<int, std::string>::iterator it = thread_names_.begin(); + it != thread_names_.end(); + it++) { + if (!it->second.empty()) { + int num_args = 1; + const char* arg_name = "name"; + unsigned char arg_type; + unsigned long long arg_value; + trace_event_internal::SetTraceValue(it->second, &arg_type, &arg_value); + logged_events_.push_back( + TraceEvent(it->first, + TimeTicks(), TRACE_EVENT_PHASE_METADATA, + &g_category_enabled[g_category_metadata], + "thread_name", trace_event_internal::kNoEventId, + num_args, &arg_name, &arg_type, &arg_value, + TRACE_EVENT_FLAG_NONE)); + } + } +} + +void TraceLog::DeleteForTesting() { + DeleteTraceLogForTesting::Delete(); +} + +void TraceLog::Resurrect() { + StaticMemorySingletonTraits<TraceLog>::Resurrect(); +} + +void TraceLog::SetProcessID(int process_id) { + process_id_ = process_id; + // Create a FNV hash from the process ID for XORing. + // See http://isthe.com/chongo/tech/comp/fnv/ for algorithm details. + unsigned long long offset_basis = 14695981039346656037ull; + unsigned long long fnv_prime = 1099511628211ull; + unsigned long long pid = static_cast<unsigned long long>(process_id_); + process_id_hash_ = (offset_basis ^ pid) * fnv_prime; +} + +void TraceLog::SetTimeOffset(TimeDelta offset) { + time_offset_ = offset; +} + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/trace_event_impl.h b/src/base/debug/trace_event_impl.h new file mode 100644 index 0000000..b8b4110 --- /dev/null +++ b/src/base/debug/trace_event_impl.h
@@ -0,0 +1,412 @@ +// 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_DEBUG_TRACE_EVENT_IMPL_H_ +#define BASE_DEBUG_TRACE_EVENT_IMPL_H_ + +#include "build/build_config.h" + +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/hash_tables.h" +#include "base/memory/ref_counted_memory.h" +#include "base/observer_list.h" +#include "base/string_util.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/timer.h" + +// Older style trace macros with explicit id and extra data +// Only these macros result in publishing data to ETW as currently implemented. +#define TRACE_EVENT_BEGIN_ETW(name, id, extra) \ + base::debug::TraceLog::AddTraceEventEtw( \ + TRACE_EVENT_PHASE_BEGIN, \ + name, reinterpret_cast<const void*>(id), extra) + +#define TRACE_EVENT_END_ETW(name, id, extra) \ + base::debug::TraceLog::AddTraceEventEtw( \ + TRACE_EVENT_PHASE_END, \ + name, reinterpret_cast<const void*>(id), extra) + +#define TRACE_EVENT_INSTANT_ETW(name, id, extra) \ + base::debug::TraceLog::AddTraceEventEtw( \ + TRACE_EVENT_PHASE_INSTANT, \ + name, reinterpret_cast<const void*>(id), extra) + +template <typename Type> +struct StaticMemorySingletonTraits; + +namespace base { + +namespace debug { + +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 programatically +// force it to happen. +class BASE_EXPORT TraceEvent { + public: + union TraceValue { + bool as_bool; + unsigned long long as_uint; + long long as_int; + double as_double; + const void* as_pointer; + const char* as_string; + }; + + TraceEvent(); + TraceEvent(int thread_id, + TimeTicks timestamp, + char phase, + const unsigned char* category_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, + unsigned char flags); + ~TraceEvent(); + + // Serialize event data to JSON + static void AppendEventsAsJSON(const std::vector<TraceEvent>& events, + size_t start, + size_t count, + std::string* out); + void AppendAsJSON(std::string* out) const; + + static void AppendValueAsJSON(unsigned char type, + TraceValue value, + std::string* out); + + TimeTicks timestamp() const { return timestamp_; } + + // Exposed for unittesting: + + const base::RefCountedString* parameter_copy_storage() const { + return parameter_copy_storage_.get(); + } + + const unsigned char* category_enabled() const { return category_enabled_; } + const char* name() const { return name_; } + +#if defined(COBALT) + unsigned long long id() const { return id_; } + const TraceValue* arg_values() const { return arg_values_; } + int thread_id() const { return thread_id_; } + char phase() const { return phase_; } +#endif + + private: + // Note: these are ordered by size (largest first) for optimal packing. + TimeTicks timestamp_; + // id_ can be used to store phase-specific data. + unsigned long long id_; + TraceValue arg_values_[kTraceMaxNumArgs]; + const char* arg_names_[kTraceMaxNumArgs]; + const unsigned char* category_enabled_; + const char* name_; + scoped_refptr<base::RefCountedString> parameter_copy_storage_; + int thread_id_; + char phase_; + unsigned char flags_; + unsigned char arg_types_[kTraceMaxNumArgs]; +}; + + +// TraceResultBuffer collects and converts trace fragments returned by TraceLog +// to JSON output. +class BASE_EXPORT TraceResultBuffer { + public: + typedef base::Callback<void(const std::string&)> OutputCallback; + + // If you don't need to stream JSON chunks out efficiently, and just want to + // get a complete JSON string after calling Finish, use this struct to collect + // JSON trace output. + struct BASE_EXPORT SimpleOutput { + OutputCallback GetCallback(); + void Append(const std::string& json_string); + + // Do what you want with the json_output_ string after calling + // TraceResultBuffer::Finish. + std::string json_output; + }; + + TraceResultBuffer(); + ~TraceResultBuffer(); + + // Set callback. The callback will be called during Start with the initial + // JSON output and during AddFragment and Finish with following JSON output + // chunks. The callback target must live past the last calls to + // TraceResultBuffer::Start/AddFragment/Finish. + void SetOutputCallback(const OutputCallback& json_chunk_callback); + + // Start JSON output. This resets all internal state, so you can reuse + // the TraceResultBuffer by calling Start. + void Start(); + + // Call AddFragment 0 or more times to add trace fragments from TraceLog. + void AddFragment(const std::string& trace_fragment); + + // When all fragments have been added, call Finish to complete the JSON + // formatted output. + void Finish(); + + private: + OutputCallback output_callback_; + bool append_comma_; +}; + + +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 + }; + + static TraceLog* GetInstance(); + + // Get set of known categories. This can change as new code paths are reached. + // The known categories are inserted into |categories|. + void GetKnownCategories(std::vector<std::string>* categories); + + // Enable tracing for provided list of categories. If tracing is already + // enabled, this method does nothing -- changing categories during trace is + // not supported. + // If both included_categories and excluded_categories are empty, + // all categories are traced. + // Else if included_categories is non-empty, only those are traced. + // Else if excluded_categories is non-empty, everything but those are traced. + // Wildcards * and ? are supported (see MatchPattern in string_util.h). + void SetEnabled(const std::vector<std::string>& included_categories, + const std::vector<std::string>& excluded_categories); + + // |categories| is a comma-delimited list of category wildcards. + // A category can have an optional '-' prefix to make it an excluded category. + // All the same rules apply above, so for example, having both included and + // excluded categories in the same list would not be supported. + // + // Example: SetEnabled("test_MyTest*"); + // Example: SetEnabled("test_MyTest*,test_OtherStuff"); + // Example: SetEnabled("-excluded_category1,-excluded_category2"); + void SetEnabled(const std::string& categories); + + // Retieves the categories set via a prior call to SetEnabled(). Only + // meaningful if |IsEnabled()| is true. + void GetEnabledTraceCategories(std::vector<std::string>* included_out, + std::vector<std::string>* excluded_out); + + // Disable tracing for all categories. + void SetDisabled(); + // Helper method to enable/disable tracing for all categories. + void SetEnabled(bool enabled); + bool IsEnabled() { return enabled_; } + +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) + static void InitATrace(); +#endif + + // Enabled state listeners give a callback when tracing is enabled or + // disabled. This can be used to tie into other library's tracing systems + // on-demand. + class EnabledStateChangedObserver { + public: + // Called just before the tracing system becomes + // enabled. TraceLog::IsEnabled will return false at this point and trace + // macros and methods called within the observer will deadlock. + virtual void OnTraceLogWillEnable() { } + + // Called just before the tracing system disables. TraceLog::IsEnabled is + // still false at this point TRACE macros will still be capturing + // data. However, trace macros and methods called within the observer will + // deadlock. + virtual void OnTraceLogWillDisable() { } + }; + void AddEnabledStateObserver(EnabledStateChangedObserver* listener); + void RemoveEnabledStateObserver(EnabledStateChangedObserver* listener); + + 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); + + // Flush all collected events to the given output callback. The callback will + // be called one or more times with IPC-bite-size chunks. The string format is + // undefined. Use TraceResultBuffer to convert one or more trace strings to + // JSON. + typedef base::Callback<void(const scoped_refptr<base::RefCountedString>&)> + OutputCallback; + void Flush(const OutputCallback& cb); + +#if defined(COBALT) + // Flush out events as raw TraceEvent structures. Optinally also flush out + // JSON output as well. + typedef base::Callback<void(const TraceEvent&)> RawEventOutputCallback; + void FlushWithRawEvents(const RawEventOutputCallback& raw_event_callback, + const OutputCallback& json_output_callback); +#endif + + // Called by TRACE_EVENT* macros, don't call this directly. + static const unsigned char* GetCategoryEnabled(const char* name); + static const char* GetCategoryName(const unsigned char* category_enabled); + + // 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_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, + unsigned char flags); + static void AddTraceEventEtw(char phase, + const char* name, + const void* id, + const char* extra); + static void AddTraceEventEtw(char phase, + const char* name, + 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 SetWatchEvent(const std::string& category_name, + const std::string& event_name); + // Cancel the watch event. If tracing is enabled, this may race with the + // watch event notification firing. + void CancelWatchEvent(); + + int process_id() const { return process_id_; } + + // Exposed for unittesting: + + // Allows deleting our singleton instance. + static void DeleteForTesting(); + + // Allows resurrecting our singleton instance post-AtExit processing. + static void Resurrect(); + + // Allow tests to inspect TraceEvents. + size_t GetEventsSize() const { return logged_events_.size(); } + const TraceEvent& GetEventAt(size_t index) const { + DCHECK(index < logged_events_.size()); + return logged_events_[index]; + } + + void SetProcessID(int process_id); + + // Allow setting an offset between the current TimeTicks time and the time + // that should be reported. + void SetTimeOffset(TimeDelta offset); + + private: + // This allows constructor and destructor to be private and usable only + // by the Singleton class. + friend struct StaticMemorySingletonTraits<TraceLog>; + + // The pointer returned from GetCategoryEnabledInternal() 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 CategoryEnabledFlags { + // Normal enabled flag for categories enabled with Enable(). + CATEGORY_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 notifcations. + 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_; + }; + + TraceLog(); + ~TraceLog(); + const unsigned char* GetCategoryEnabledInternal(const char* name); + void AddThreadNameMetadataEvents(); + +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) + void SendToATrace(char phase, + const char* category, + const char* name, + int num_args, + const char** arg_names, + const unsigned char* arg_types, + const unsigned long long* arg_values); + void AddClockSyncMetadataEvents(); + static void ApplyATraceEnabledFlag(unsigned char* category_enabled); +#endif + + // TODO(nduca): switch to per-thread trace buffers to reduce thread + // synchronization. + // This lock protects TraceLog member accesses from arbitrary threads. + Lock lock_; + bool enabled_; + NotificationCallback notification_callback_; + std::vector<TraceEvent> logged_events_; + std::vector<std::string> included_categories_; + std::vector<std::string> excluded_categories_; + bool dispatching_to_observer_list_; + ObserverList<EnabledStateChangedObserver> enabled_state_observer_list_; + + base::hash_map<int, std::string> thread_names_; + + // XORed with TraceID to make it unlikely to collide with other processes. + unsigned long long process_id_hash_; + + int process_id_; + + TimeDelta time_offset_; + + // Allow tests to wake up when certain events occur. + const unsigned char* watch_category_; + std::string watch_event_name_; + + DISALLOW_COPY_AND_ASSIGN(TraceLog); +}; + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_TRACE_EVENT_IMPL_H_
diff --git a/src/base/debug/trace_event_unittest.cc b/src/base/debug/trace_event_unittest.cc new file mode 100644 index 0000000..445118b --- /dev/null +++ b/src/base/debug/trace_event_unittest.cc
@@ -0,0 +1,1341 @@ +// 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/debug/trace_event_unittest.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/debug/trace_event.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/process_util.h" +#include "base/stringprintf.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread.h" +#include "base/values.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::debug::HighResSleepForTraceTest; + +namespace base { +namespace debug { + +namespace { + +enum CompareOp { + IS_EQUAL, + IS_NOT_EQUAL, +}; + +struct JsonKeyValue { + const char* key; + const char* value; + CompareOp op; +}; + +class TraceEventTestFixture : public testing::Test { + public: + // This fixture does not use SetUp() because the fixture must be manually set + // up multiple times when testing AtExit. Use ManualTestSetUp for this. + void ManualTestSetUp(); + void OnTraceDataCollected( + const scoped_refptr<base::RefCountedString>& events_str); + void OnTraceNotification(int notification) { + if (notification & TraceLog::EVENT_WATCH_NOTIFICATION) + ++event_watch_notification_; + } + DictionaryValue* FindMatchingTraceEntry(const JsonKeyValue* key_values); + DictionaryValue* FindNamePhase(const char* name, const char* phase); + DictionaryValue* FindNamePhaseKeyValue(const char* name, + const char* phase, + const char* key, + const char* value); + bool FindMatchingValue(const char* key, + const char* value); + bool FindNonMatchingValue(const char* key, + const char* value); + void Clear() { + trace_parsed_.Clear(); + json_output_.json_output.clear(); + } + + void BeginTrace() { + event_watch_notification_ = 0; + TraceLog::GetInstance()->SetEnabled("*"); + } + + void EndTraceAndFlush() { + TraceLog::GetInstance()->SetDisabled(); + TraceLog::GetInstance()->Flush( + base::Bind(&TraceEventTestFixture::OnTraceDataCollected, + base::Unretained(this))); + } + + virtual void SetUp() OVERRIDE { + old_thread_name_ = PlatformThread::GetName(); + } + virtual void TearDown() OVERRIDE { + if (TraceLog::GetInstance()) + EXPECT_FALSE(TraceLog::GetInstance()->IsEnabled()); + PlatformThread::SetName(old_thread_name_ ? old_thread_name_ : ""); + } + + const char* old_thread_name_; + ListValue trace_parsed_; + base::debug::TraceResultBuffer trace_buffer_; + base::debug::TraceResultBuffer::SimpleOutput json_output_; + int event_watch_notification_; + + private: + // We want our singleton torn down after each test. + ShadowingAtExitManager at_exit_manager_; + Lock lock_; +}; + +void TraceEventTestFixture::ManualTestSetUp() { + TraceLog::DeleteForTesting(); + TraceLog::Resurrect(); + 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()); +} + +void TraceEventTestFixture::OnTraceDataCollected( + const scoped_refptr<base::RefCountedString>& events_str) { + AutoLock lock(lock_); + json_output_.json_output.clear(); + trace_buffer_.Start(); + trace_buffer_.AddFragment(events_str->data()); + trace_buffer_.Finish(); + + scoped_ptr<Value> root; + root.reset(base::JSONReader::Read(json_output_.json_output, + JSON_PARSE_RFC | JSON_DETACHABLE_CHILDREN)); + + if (!root.get()) { + LOG(ERROR) << json_output_.json_output; + } + + ListValue* root_list = NULL; + ASSERT_TRUE(root.get()); + ASSERT_TRUE(root->GetAsList(&root_list)); + + // Move items into our aggregate collection + while (root_list->GetSize()) { + Value* item = NULL; + root_list->Remove(0, &item); + trace_parsed_.Append(item); + } +} + +static bool CompareJsonValues(const std::string& lhs, + const std::string& rhs, + CompareOp op) { + switch (op) { + case IS_EQUAL: + return lhs == rhs; + case IS_NOT_EQUAL: + return lhs != rhs; + default: + CHECK(0); + } + return false; +} + +static bool IsKeyValueInDict(const JsonKeyValue* key_value, + DictionaryValue* dict) { + Value* value = NULL; + std::string value_str; + if (dict->Get(key_value->key, &value) && + value->GetAsString(&value_str) && + CompareJsonValues(value_str, key_value->value, key_value->op)) + return true; + + // Recurse to test arguments + DictionaryValue* args_dict = NULL; + dict->GetDictionary("args", &args_dict); + if (args_dict) + return IsKeyValueInDict(key_value, args_dict); + + return false; +} + +static bool IsAllKeyValueInDict(const JsonKeyValue* key_values, + DictionaryValue* dict) { + // Scan all key_values, they must all be present and equal. + while (key_values && key_values->key) { + if (!IsKeyValueInDict(key_values, dict)) + return false; + ++key_values; + } + return true; +} + +DictionaryValue* TraceEventTestFixture::FindMatchingTraceEntry( + const JsonKeyValue* key_values) { + // Scan all items + size_t trace_parsed_count = trace_parsed_.GetSize(); + for (size_t i = 0; i < trace_parsed_count; i++) { + Value* value = NULL; + trace_parsed_.Get(i, &value); + if (!value || value->GetType() != Value::TYPE_DICTIONARY) + continue; + DictionaryValue* dict = static_cast<DictionaryValue*>(value); + + if (IsAllKeyValueInDict(key_values, dict)) + return dict; + } + return NULL; +} + +DictionaryValue* TraceEventTestFixture::FindNamePhase(const char* name, + const char* phase) { + JsonKeyValue key_values[] = { + {"name", name, IS_EQUAL}, + {"ph", phase, IS_EQUAL}, + {0, 0, IS_EQUAL} + }; + return FindMatchingTraceEntry(key_values); +} + +DictionaryValue* TraceEventTestFixture::FindNamePhaseKeyValue( + const char* name, + const char* phase, + const char* key, + const char* value) { + JsonKeyValue key_values[] = { + {"name", name, IS_EQUAL}, + {"ph", phase, IS_EQUAL}, + {key, value, IS_EQUAL}, + {0, 0, IS_EQUAL} + }; + return FindMatchingTraceEntry(key_values); +} + +bool TraceEventTestFixture::FindMatchingValue(const char* key, + const char* value) { + JsonKeyValue key_values[] = { + {key, value, IS_EQUAL}, + {0, 0, IS_EQUAL} + }; + return FindMatchingTraceEntry(key_values); +} + +bool TraceEventTestFixture::FindNonMatchingValue(const char* key, + const char* value) { + JsonKeyValue key_values[] = { + {key, value, IS_NOT_EQUAL}, + {0, 0, IS_EQUAL} + }; + return FindMatchingTraceEntry(key_values); +} + +bool IsStringInDict(const char* string_to_match, const DictionaryValue* dict) { + for (DictionaryValue::key_iterator ikey = dict->begin_keys(); + ikey != dict->end_keys(); ++ikey) { + const Value* child = NULL; + if (!dict->GetWithoutPathExpansion(*ikey, &child)) + continue; + + if ((*ikey).find(string_to_match) != std::string::npos) + return true; + + std::string value_str; + child->GetAsString(&value_str); + if (value_str.find(string_to_match) != std::string::npos) + return true; + } + + // Recurse to test arguments + const DictionaryValue* args_dict = NULL; + dict->GetDictionary("args", &args_dict); + if (args_dict) + return IsStringInDict(string_to_match, args_dict); + + return false; +} + +const DictionaryValue* FindTraceEntry( + const ListValue& trace_parsed, + const char* string_to_match, + const DictionaryValue* match_after_this_item = NULL) { + // Scan all items + size_t trace_parsed_count = trace_parsed.GetSize(); + for (size_t i = 0; i < trace_parsed_count; i++) { + const Value* value = NULL; + trace_parsed.Get(i, &value); + if (match_after_this_item) { + if (value == match_after_this_item) + match_after_this_item = NULL; + continue; + } + if (!value || value->GetType() != Value::TYPE_DICTIONARY) + continue; + const DictionaryValue* dict = static_cast<const DictionaryValue*>(value); + + if (IsStringInDict(string_to_match, dict)) + return dict; + } + return NULL; +} + +std::vector<const DictionaryValue*> FindTraceEntries( + const ListValue& trace_parsed, + const char* string_to_match) { + std::vector<const DictionaryValue*> hits; + size_t trace_parsed_count = trace_parsed.GetSize(); + for (size_t i = 0; i < trace_parsed_count; i++) { + const Value* value = NULL; + trace_parsed.Get(i, &value); + if (!value || value->GetType() != Value::TYPE_DICTIONARY) + continue; + const DictionaryValue* dict = static_cast<const DictionaryValue*>(value); + + if (IsStringInDict(string_to_match, dict)) + hits.push_back(dict); + } + return hits; +} + +void TraceWithAllMacroVariants(WaitableEvent* task_complete_event) { + { + TRACE_EVENT_BEGIN_ETW("TRACE_EVENT_BEGIN_ETW call", 0x1122, "extrastring1"); + TRACE_EVENT_END_ETW("TRACE_EVENT_END_ETW call", 0x3344, "extrastring2"); + TRACE_EVENT_INSTANT_ETW("TRACE_EVENT_INSTANT_ETW call", + 0x5566, "extrastring3"); + + TRACE_EVENT0("all", "TRACE_EVENT0 call"); + TRACE_EVENT1("all", "TRACE_EVENT1 call", "name1", "value1"); + TRACE_EVENT2("all", "TRACE_EVENT2 call", + "name1", "\"value1\"", + "name2", "value\\2"); + + TRACE_EVENT_INSTANT0("all", "TRACE_EVENT_INSTANT0 call"); + TRACE_EVENT_INSTANT1("all", "TRACE_EVENT_INSTANT1 call", "name1", "value1"); + TRACE_EVENT_INSTANT2("all", "TRACE_EVENT_INSTANT2 call", + "name1", "value1", + "name2", "value2"); + + TRACE_EVENT_BEGIN0("all", "TRACE_EVENT_BEGIN0 call"); + TRACE_EVENT_BEGIN1("all", "TRACE_EVENT_BEGIN1 call", "name1", "value1"); + TRACE_EVENT_BEGIN2("all", "TRACE_EVENT_BEGIN2 call", + "name1", "value1", + "name2", "value2"); + + TRACE_EVENT_END0("all", "TRACE_EVENT_END0 call"); + TRACE_EVENT_END1("all", "TRACE_EVENT_END1 call", "name1", "value1"); + TRACE_EVENT_END2("all", "TRACE_EVENT_END2 call", + "name1", "value1", + "name2", "value2"); + + TRACE_EVENT_ASYNC_BEGIN0("all", "TRACE_EVENT_ASYNC_BEGIN0 call", 5); + TRACE_EVENT_ASYNC_BEGIN1("all", "TRACE_EVENT_ASYNC_BEGIN1 call", 5, + "name1", "value1"); + TRACE_EVENT_ASYNC_BEGIN2("all", "TRACE_EVENT_ASYNC_BEGIN2 call", 5, + "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_END0("all", "TRACE_EVENT_ASYNC_END0 call", 5); + TRACE_EVENT_ASYNC_END1("all", "TRACE_EVENT_ASYNC_END1 call", 5, + "name1", "value1"); + TRACE_EVENT_ASYNC_END2("all", "TRACE_EVENT_ASYNC_END2 call", 5, + "name1", "value1", + "name2", "value2"); + + TRACE_EVENT_BEGIN_ETW("TRACE_EVENT_BEGIN_ETW0 call", 5, NULL); + TRACE_EVENT_BEGIN_ETW("TRACE_EVENT_BEGIN_ETW1 call", 5, "value"); + TRACE_EVENT_END_ETW("TRACE_EVENT_END_ETW0 call", 5, NULL); + TRACE_EVENT_END_ETW("TRACE_EVENT_END_ETW1 call", 5, "value"); + TRACE_EVENT_INSTANT_ETW("TRACE_EVENT_INSTANT_ETW0 call", 5, NULL); + TRACE_EVENT_INSTANT_ETW("TRACE_EVENT_INSTANT_ETW1 call", 5, "value"); + + TRACE_COUNTER1("all", "TRACE_COUNTER1 call", 31415); + TRACE_COUNTER2("all", "TRACE_COUNTER2 call", + "a", 30000, + "b", 1415); + + TRACE_COUNTER_ID1("all", "TRACE_COUNTER_ID1 call", 0x319009, 31415); + TRACE_COUNTER_ID2("all", "TRACE_COUNTER_ID2 call", 0x319009, + "a", 30000, "b", 1415); + } // Scope close causes TRACE_EVENT0 etc to send their END events. + + if (task_complete_event) + task_complete_event->Signal(); +} + +void ValidateAllTraceMacrosCreatedData(const ListValue& trace_parsed) { + const DictionaryValue* item = NULL; + +#define EXPECT_FIND_(string) \ + EXPECT_TRUE((item = FindTraceEntry(trace_parsed, string))); +#define EXPECT_NOT_FIND_(string) \ + EXPECT_FALSE((item = FindTraceEntry(trace_parsed, string))); +#define EXPECT_SUB_FIND_(string) \ + if (item) EXPECT_TRUE((IsStringInDict(string, item))); + + EXPECT_FIND_("ETW Trace Event"); + EXPECT_FIND_("all"); + EXPECT_FIND_("TRACE_EVENT_BEGIN_ETW call"); + { + std::string str_val; + EXPECT_TRUE(item && item->GetString("args.id", &str_val)); + EXPECT_STREQ("1122", str_val.c_str()); + } + EXPECT_SUB_FIND_("extrastring1"); + EXPECT_FIND_("TRACE_EVENT_END_ETW call"); + EXPECT_FIND_("TRACE_EVENT_INSTANT_ETW call"); + EXPECT_FIND_("TRACE_EVENT0 call"); + { + std::string ph_begin; + 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", + 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"); + EXPECT_SUB_FIND_("value1"); + EXPECT_FIND_("TRACE_EVENT2 call"); + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("\"value1\""); + EXPECT_SUB_FIND_("name2"); + EXPECT_SUB_FIND_("value\\2"); + + EXPECT_FIND_("TRACE_EVENT_INSTANT0 call"); + EXPECT_FIND_("TRACE_EVENT_INSTANT1 call"); + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("value1"); + EXPECT_FIND_("TRACE_EVENT_INSTANT2 call"); + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("value1"); + EXPECT_SUB_FIND_("name2"); + EXPECT_SUB_FIND_("value2"); + + EXPECT_FIND_("TRACE_EVENT_BEGIN0 call"); + EXPECT_FIND_("TRACE_EVENT_BEGIN1 call"); + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("value1"); + EXPECT_FIND_("TRACE_EVENT_BEGIN2 call"); + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("value1"); + EXPECT_SUB_FIND_("name2"); + EXPECT_SUB_FIND_("value2"); + + EXPECT_FIND_("TRACE_EVENT_END0 call"); + EXPECT_FIND_("TRACE_EVENT_END1 call"); + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("value1"); + EXPECT_FIND_("TRACE_EVENT_END2 call"); + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("value1"); + EXPECT_SUB_FIND_("name2"); + EXPECT_SUB_FIND_("value2"); + + EXPECT_FIND_("TRACE_EVENT_ASYNC_BEGIN0 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_("5"); + EXPECT_FIND_("TRACE_EVENT_ASYNC_BEGIN1 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_("5"); + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("value1"); + EXPECT_FIND_("TRACE_EVENT_ASYNC_BEGIN2 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_("5"); + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("value1"); + EXPECT_SUB_FIND_("name2"); + EXPECT_SUB_FIND_("value2"); + + EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP0 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_("5"); + EXPECT_SUB_FIND_("step1"); + EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP1 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_("5"); + EXPECT_SUB_FIND_("step2"); + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("value1"); + + EXPECT_FIND_("TRACE_EVENT_ASYNC_END0 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_("5"); + EXPECT_FIND_("TRACE_EVENT_ASYNC_END1 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_("5"); + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("value1"); + EXPECT_FIND_("TRACE_EVENT_ASYNC_END2 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_("5"); + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("value1"); + EXPECT_SUB_FIND_("name2"); + EXPECT_SUB_FIND_("value2"); + + EXPECT_FIND_("TRACE_EVENT_BEGIN_ETW0 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_("5"); + EXPECT_SUB_FIND_("extra"); + EXPECT_SUB_FIND_("NULL"); + EXPECT_FIND_("TRACE_EVENT_BEGIN_ETW1 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_("5"); + EXPECT_SUB_FIND_("extra"); + EXPECT_SUB_FIND_("value"); + EXPECT_FIND_("TRACE_EVENT_END_ETW0 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_("5"); + EXPECT_SUB_FIND_("extra"); + EXPECT_SUB_FIND_("NULL"); + EXPECT_FIND_("TRACE_EVENT_END_ETW1 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_("5"); + EXPECT_SUB_FIND_("extra"); + EXPECT_SUB_FIND_("value"); + EXPECT_FIND_("TRACE_EVENT_INSTANT_ETW0 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_("5"); + EXPECT_SUB_FIND_("extra"); + EXPECT_SUB_FIND_("NULL"); + EXPECT_FIND_("TRACE_EVENT_INSTANT_ETW1 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_("5"); + EXPECT_SUB_FIND_("extra"); + EXPECT_SUB_FIND_("value"); + + EXPECT_FIND_("TRACE_COUNTER1 call"); + { + std::string ph; + EXPECT_TRUE((item && item->GetString("ph", &ph))); + EXPECT_EQ("C", ph); + + int value; + EXPECT_TRUE((item && item->GetInteger("args.value", &value))); + EXPECT_EQ(31415, value); + } + + EXPECT_FIND_("TRACE_COUNTER2 call"); + { + std::string ph; + EXPECT_TRUE((item && item->GetString("ph", &ph))); + EXPECT_EQ("C", ph); + + int value; + EXPECT_TRUE((item && item->GetInteger("args.a", &value))); + EXPECT_EQ(30000, value); + + EXPECT_TRUE((item && item->GetInteger("args.b", &value))); + EXPECT_EQ(1415, value); + } + + EXPECT_FIND_("TRACE_COUNTER_ID1 call"); + { + std::string id; + EXPECT_TRUE((item && item->GetString("id", &id))); + EXPECT_EQ("319009", id); + + std::string ph; + EXPECT_TRUE((item && item->GetString("ph", &ph))); + EXPECT_EQ("C", ph); + + int value; + EXPECT_TRUE((item && item->GetInteger("args.value", &value))); + EXPECT_EQ(31415, value); + } + + EXPECT_FIND_("TRACE_COUNTER_ID2 call"); + { + std::string id; + EXPECT_TRUE((item && item->GetString("id", &id))); + EXPECT_EQ("319009", id); + + std::string ph; + EXPECT_TRUE((item && item->GetString("ph", &ph))); + EXPECT_EQ("C", ph); + + int value; + EXPECT_TRUE((item && item->GetInteger("args.a", &value))); + EXPECT_EQ(30000, value); + + EXPECT_TRUE((item && item->GetInteger("args.b", &value))); + EXPECT_EQ(1415, value); + } +} + +void TraceManyInstantEvents(int thread_id, int num_events, + WaitableEvent* task_complete_event) { + for (int i = 0; i < num_events; i++) { + TRACE_EVENT_INSTANT2("all", "multi thread event", + "thread", thread_id, + "event", i); + } + + if (task_complete_event) + task_complete_event->Signal(); +} + +void ValidateInstantEventPresentOnEveryThread(const ListValue& trace_parsed, + int num_threads, + int num_events) { + std::map<int, std::map<int, bool> > results; + + size_t trace_parsed_count = trace_parsed.GetSize(); + for (size_t i = 0; i < trace_parsed_count; i++) { + const Value* value = NULL; + trace_parsed.Get(i, &value); + if (!value || value->GetType() != Value::TYPE_DICTIONARY) + continue; + const DictionaryValue* dict = static_cast<const DictionaryValue*>(value); + std::string name; + dict->GetString("name", &name); + if (name != "multi thread event") + continue; + + int thread = 0; + int event = 0; + EXPECT_TRUE(dict->GetInteger("args.thread", &thread)); + EXPECT_TRUE(dict->GetInteger("args.event", &event)); + results[thread][event] = true; + } + + EXPECT_FALSE(results[-1][-1]); + for (int thread = 0; thread < num_threads; thread++) { + for (int event = 0; event < num_events; event++) { + EXPECT_TRUE(results[thread][event]); + } + } +} + +void TraceCallsWithCachedCategoryPointersPointers(const char* name_str) { + TRACE_EVENT0("category name1", name_str); + TRACE_EVENT_INSTANT0("category name2", name_str); + TRACE_EVENT_BEGIN0("category name3", name_str); + TRACE_EVENT_END0("category name4", name_str); +} + +} // namespace + +void HighResSleepForTraceTest(base::TimeDelta elapsed) { + base::TimeTicks end_time = base::TimeTicks::HighResNow() + elapsed; + do { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1)); + } while (base::TimeTicks::HighResNow() < end_time); +} + +// Simple Test for emitting data and validating it was received. +TEST_F(TraceEventTestFixture, DataCaptured) { + ManualTestSetUp(); + TraceLog::GetInstance()->SetEnabled(true); + + TraceWithAllMacroVariants(NULL); + + EndTraceAndFlush(); + + ValidateAllTraceMacrosCreatedData(trace_parsed_); +} + +class MockEnabledStateChangedObserver : + public base::debug::TraceLog::EnabledStateChangedObserver { + public: + MOCK_METHOD0(OnTraceLogWillEnable, void()); + MOCK_METHOD0(OnTraceLogWillDisable, void()); +}; + +TEST_F(TraceEventTestFixture, EnabledObserverFiresOnEnable) { + ManualTestSetUp(); + + MockEnabledStateChangedObserver observer; + TraceLog::GetInstance()->AddEnabledStateObserver(&observer); + + EXPECT_CALL(observer, OnTraceLogWillEnable()) + .Times(1); + TraceLog::GetInstance()->SetEnabled(true); + testing::Mock::VerifyAndClear(&observer); + + // Cleanup. + TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer); + TraceLog::GetInstance()->SetEnabled(false); +} + +TEST_F(TraceEventTestFixture, EnabledObserverDoesntFireOnSecondEnable) { + ManualTestSetUp(); + + TraceLog::GetInstance()->SetEnabled(true); + + testing::StrictMock<MockEnabledStateChangedObserver> observer; + TraceLog::GetInstance()->AddEnabledStateObserver(&observer); + + EXPECT_CALL(observer, OnTraceLogWillEnable()) + .Times(0); + EXPECT_CALL(observer, OnTraceLogWillDisable()) + .Times(0); + TraceLog::GetInstance()->SetEnabled(true); + testing::Mock::VerifyAndClear(&observer); + + // Cleanup. + TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer); + TraceLog::GetInstance()->SetEnabled(false); +} + +TEST_F(TraceEventTestFixture, EnabledObserverDoesntFireOnUselessDisable) { + ManualTestSetUp(); + + + testing::StrictMock<MockEnabledStateChangedObserver> observer; + TraceLog::GetInstance()->AddEnabledStateObserver(&observer); + + EXPECT_CALL(observer, OnTraceLogWillEnable()) + .Times(0); + EXPECT_CALL(observer, OnTraceLogWillDisable()) + .Times(0); + TraceLog::GetInstance()->SetEnabled(false); + testing::Mock::VerifyAndClear(&observer); + + // Cleanup. + TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer); +} + +TEST_F(TraceEventTestFixture, EnabledObserverFiresOnDisable) { + ManualTestSetUp(); + + TraceLog::GetInstance()->SetEnabled(true); + + MockEnabledStateChangedObserver observer; + TraceLog::GetInstance()->AddEnabledStateObserver(&observer); + + EXPECT_CALL(observer, OnTraceLogWillDisable()) + .Times(1); + TraceLog::GetInstance()->SetEnabled(false); + testing::Mock::VerifyAndClear(&observer); + + // Cleanup. + TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer); +} + +// Test that categories work. +TEST_F(TraceEventTestFixture, Categories) { + ManualTestSetUp(); + + // Test that categories that are used can be retrieved whether trace was + // enabled or disabled when the trace event was encountered. + TRACE_EVENT_INSTANT0("c1", "name"); + TRACE_EVENT_INSTANT0("c2", "name"); + BeginTrace(); + TRACE_EVENT_INSTANT0("c3", "name"); + TRACE_EVENT_INSTANT0("c4", "name"); + EndTraceAndFlush(); + std::vector<std::string> cats; + TraceLog::GetInstance()->GetKnownCategories(&cats); + EXPECT_TRUE(std::find(cats.begin(), cats.end(), "c1") != cats.end()); + EXPECT_TRUE(std::find(cats.begin(), cats.end(), "c2") != cats.end()); + EXPECT_TRUE(std::find(cats.begin(), cats.end(), "c3") != cats.end()); + EXPECT_TRUE(std::find(cats.begin(), cats.end(), "c4") != cats.end()); + + const std::vector<std::string> empty_categories; + std::vector<std::string> included_categories; + std::vector<std::string> excluded_categories; + + // Test that category filtering works. + + // Include nonexistent category -> no events + Clear(); + included_categories.clear(); + included_categories.push_back("not_found823564786"); + TraceLog::GetInstance()->SetEnabled(included_categories, empty_categories); + TRACE_EVENT_INSTANT0("cat1", "name"); + TRACE_EVENT_INSTANT0("cat2", "name"); + EndTraceAndFlush(); + EXPECT_TRUE(trace_parsed_.empty()); + + // Include existent category -> only events of that category + Clear(); + included_categories.clear(); + included_categories.push_back("inc"); + TraceLog::GetInstance()->SetEnabled(included_categories, empty_categories); + TRACE_EVENT_INSTANT0("inc", "name"); + TRACE_EVENT_INSTANT0("inc2", "name"); + EndTraceAndFlush(); + EXPECT_TRUE(FindMatchingValue("cat", "inc")); + EXPECT_FALSE(FindNonMatchingValue("cat", "inc")); + + // Include existent wildcard -> all categories matching wildcard + Clear(); + included_categories.clear(); + included_categories.push_back("inc_wildcard_*"); + included_categories.push_back("inc_wildchar_?_end"); + TraceLog::GetInstance()->SetEnabled(included_categories, empty_categories); + TRACE_EVENT_INSTANT0("inc_wildcard_abc", "included"); + TRACE_EVENT_INSTANT0("inc_wildcard_", "included"); + TRACE_EVENT_INSTANT0("inc_wildchar_x_end", "included"); + TRACE_EVENT_INSTANT0("inc_wildchar_bla_end", "not_inc"); + TRACE_EVENT_INSTANT0("cat1", "not_inc"); + TRACE_EVENT_INSTANT0("cat2", "not_inc"); + EndTraceAndFlush(); + EXPECT_TRUE(FindMatchingValue("cat", "inc_wildcard_abc")); + EXPECT_TRUE(FindMatchingValue("cat", "inc_wildcard_")); + EXPECT_TRUE(FindMatchingValue("cat", "inc_wildchar_x_end")); + EXPECT_FALSE(FindMatchingValue("name", "not_inc")); + + included_categories.clear(); + + // Exclude nonexistent category -> all events + Clear(); + excluded_categories.clear(); + excluded_categories.push_back("not_found823564786"); + TraceLog::GetInstance()->SetEnabled(empty_categories, excluded_categories); + TRACE_EVENT_INSTANT0("cat1", "name"); + TRACE_EVENT_INSTANT0("cat2", "name"); + EndTraceAndFlush(); + EXPECT_TRUE(FindMatchingValue("cat", "cat1")); + EXPECT_TRUE(FindMatchingValue("cat", "cat2")); + + // Exclude existent category -> only events of other categories + Clear(); + excluded_categories.clear(); + excluded_categories.push_back("inc"); + TraceLog::GetInstance()->SetEnabled(empty_categories, excluded_categories); + TRACE_EVENT_INSTANT0("inc", "name"); + TRACE_EVENT_INSTANT0("inc2", "name"); + EndTraceAndFlush(); + EXPECT_TRUE(FindMatchingValue("cat", "inc2")); + EXPECT_FALSE(FindMatchingValue("cat", "inc")); + + // Exclude existent wildcard -> all categories not matching wildcard + Clear(); + excluded_categories.clear(); + excluded_categories.push_back("inc_wildcard_*"); + excluded_categories.push_back("inc_wildchar_?_end"); + TraceLog::GetInstance()->SetEnabled(empty_categories, excluded_categories); + TRACE_EVENT_INSTANT0("inc_wildcard_abc", "not_inc"); + TRACE_EVENT_INSTANT0("inc_wildcard_", "not_inc"); + TRACE_EVENT_INSTANT0("inc_wildchar_x_end", "not_inc"); + TRACE_EVENT_INSTANT0("inc_wildchar_bla_end", "included"); + TRACE_EVENT_INSTANT0("cat1", "included"); + TRACE_EVENT_INSTANT0("cat2", "included"); + EndTraceAndFlush(); + EXPECT_TRUE(FindMatchingValue("cat", "inc_wildchar_bla_end")); + EXPECT_TRUE(FindMatchingValue("cat", "cat1")); + EXPECT_TRUE(FindMatchingValue("cat", "cat2")); + EXPECT_FALSE(FindMatchingValue("name", "not_inc")); +} + + +// Test EVENT_WATCH_NOTIFICATION +TEST_F(TraceEventTestFixture, EventWatchNotification) { + ManualTestSetUp(); + + // Basic one occurrence. + BeginTrace(); + TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + TRACE_EVENT_INSTANT0("cat", "event"); + EndTraceAndFlush(); + EXPECT_EQ(event_watch_notification_, 1); + + // Basic one occurrence before Set. + BeginTrace(); + TRACE_EVENT_INSTANT0("cat", "event"); + TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + EndTraceAndFlush(); + EXPECT_EQ(event_watch_notification_, 1); + + // Auto-reset after end trace. + BeginTrace(); + TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + EndTraceAndFlush(); + BeginTrace(); + TRACE_EVENT_INSTANT0("cat", "event"); + EndTraceAndFlush(); + EXPECT_EQ(event_watch_notification_, 0); + + // Multiple occurrence. + BeginTrace(); + int num_occurrences = 5; + TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + for (int i = 0; i < num_occurrences; ++i) + TRACE_EVENT_INSTANT0("cat", "event"); + EndTraceAndFlush(); + EXPECT_EQ(event_watch_notification_, num_occurrences); + + // Wrong category. + BeginTrace(); + TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + TRACE_EVENT_INSTANT0("wrong_cat", "event"); + EndTraceAndFlush(); + EXPECT_EQ(event_watch_notification_, 0); + + // Wrong name. + BeginTrace(); + TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + TRACE_EVENT_INSTANT0("cat", "wrong_event"); + EndTraceAndFlush(); + EXPECT_EQ(event_watch_notification_, 0); + + // Canceled. + BeginTrace(); + TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + TraceLog::GetInstance()->CancelWatchEvent(); + TRACE_EVENT_INSTANT0("cat", "event"); + EndTraceAndFlush(); + EXPECT_EQ(event_watch_notification_, 0); +} + +// Test ASYNC_BEGIN/END events +TEST_F(TraceEventTestFixture, AsyncBeginEndEvents) { + ManualTestSetUp(); + BeginTrace(); + + unsigned long long id = 0xfeedbeeffeedbeefull; + TRACE_EVENT_ASYNC_BEGIN0( "cat", "name1", id); + TRACE_EVENT_ASYNC_STEP0( "cat", "name1", id, "step1"); + TRACE_EVENT_ASYNC_END0("cat", "name1", id); + TRACE_EVENT_BEGIN0( "cat", "name2"); + TRACE_EVENT_ASYNC_BEGIN0( "cat", "name3", 0); + + EndTraceAndFlush(); + + EXPECT_TRUE(FindNamePhase("name1", "S")); + EXPECT_TRUE(FindNamePhase("name1", "T")); + EXPECT_TRUE(FindNamePhase("name1", "F")); + + std::string id_str; + StringAppendF(&id_str, "%llx", id); + + EXPECT_TRUE(FindNamePhaseKeyValue("name1", "S", "id", id_str.c_str())); + 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", "0")); + + // BEGIN events should not have id + EXPECT_FALSE(FindNamePhaseKeyValue("name2", "B", "id", "0")); +} + +// Test ASYNC_BEGIN/END events +TEST_F(TraceEventTestFixture, AsyncBeginEndPointerMangling) { + ManualTestSetUp(); + + void* ptr = this; + + TraceLog::GetInstance()->SetProcessID(100); + BeginTrace(); + TRACE_EVENT_ASYNC_BEGIN0( "cat", "name1", ptr); + TRACE_EVENT_ASYNC_BEGIN0( "cat", "name2", ptr); + EndTraceAndFlush(); + + TraceLog::GetInstance()->SetProcessID(200); + BeginTrace(); + TRACE_EVENT_ASYNC_END0( "cat", "name1", ptr); + EndTraceAndFlush(); + + DictionaryValue* async_begin = FindNamePhase("name1", "S"); + DictionaryValue* async_begin2 = FindNamePhase("name2", "S"); + DictionaryValue* async_end = FindNamePhase("name1", "F"); + EXPECT_TRUE(async_begin); + EXPECT_TRUE(async_begin2); + EXPECT_TRUE(async_end); + + Value* value = NULL; + std::string async_begin_id_str; + std::string async_begin2_id_str; + std::string async_end_id_str; + ASSERT_TRUE(async_begin->Get("id", &value)); + ASSERT_TRUE(value->GetAsString(&async_begin_id_str)); + ASSERT_TRUE(async_begin2->Get("id", &value)); + ASSERT_TRUE(value->GetAsString(&async_begin2_id_str)); + ASSERT_TRUE(async_end->Get("id", &value)); + ASSERT_TRUE(value->GetAsString(&async_end_id_str)); + + EXPECT_STREQ(async_begin_id_str.c_str(), async_begin2_id_str.c_str()); + EXPECT_STRNE(async_begin_id_str.c_str(), async_end_id_str.c_str()); +} + +// Test that static strings are not copied. +TEST_F(TraceEventTestFixture, StaticStringVsString) { + ManualTestSetUp(); + TraceLog* tracer = TraceLog::GetInstance(); + // Make sure old events are flushed: + EndTraceAndFlush(); + EXPECT_EQ(0u, tracer->GetEventsSize()); + + { + BeginTrace(); + // Test that string arguments are copied. + TRACE_EVENT2("cat", "name1", + "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")); + 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); + EndTraceAndFlush(); + } + + { + BeginTrace(); + // Test that static literal string arguments are not copied. + TRACE_EVENT2("cat", "name1", + "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)); + 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); + EndTraceAndFlush(); + } +} + +// Test that data sent from other threads is gathered +TEST_F(TraceEventTestFixture, DataCapturedOnThread) { + ManualTestSetUp(); + BeginTrace(); + + Thread thread("1"); + WaitableEvent task_complete_event(false, false); + thread.Start(); + + thread.message_loop()->PostTask( + FROM_HERE, base::Bind(&TraceWithAllMacroVariants, &task_complete_event)); + task_complete_event.Wait(); + thread.Stop(); + + EndTraceAndFlush(); + ValidateAllTraceMacrosCreatedData(trace_parsed_); +} + +// Test that data sent from multiple threads is gathered +TEST_F(TraceEventTestFixture, DataCapturedManyThreads) { + ManualTestSetUp(); + BeginTrace(); + + const int num_threads = 4; + const int num_events = 4000; + Thread* threads[num_threads]; + WaitableEvent* task_complete_events[num_threads]; + for (int i = 0; i < num_threads; i++) { + threads[i] = new Thread(StringPrintf("Thread %d", i).c_str()); + task_complete_events[i] = new WaitableEvent(false, false); + threads[i]->Start(); + threads[i]->message_loop()->PostTask( + FROM_HERE, base::Bind(&TraceManyInstantEvents, + i, num_events, task_complete_events[i])); + } + + for (int i = 0; i < num_threads; i++) { + task_complete_events[i]->Wait(); + } + + for (int i = 0; i < num_threads; i++) { + threads[i]->Stop(); + delete threads[i]; + delete task_complete_events[i]; + } + + EndTraceAndFlush(); + + ValidateInstantEventPresentOnEveryThread(trace_parsed_, + num_threads, num_events); +} + +// Test that thread and process names show up in the trace +TEST_F(TraceEventTestFixture, ThreadNames) { + ManualTestSetUp(); + + // Create threads before we enable tracing to make sure + // that tracelog still captures them. + const int num_threads = 4; + const int num_events = 10; + Thread* threads[num_threads]; + PlatformThreadId thread_ids[num_threads]; + for (int i = 0; i < num_threads; i++) + threads[i] = new Thread(StringPrintf("Thread %d", i).c_str()); + + // Enable tracing. + BeginTrace(); + + // Now run some trace code on these threads. + WaitableEvent* task_complete_events[num_threads]; + for (int i = 0; i < num_threads; i++) { + task_complete_events[i] = new WaitableEvent(false, false); + threads[i]->Start(); + thread_ids[i] = threads[i]->thread_id(); + threads[i]->message_loop()->PostTask( + FROM_HERE, base::Bind(&TraceManyInstantEvents, + i, num_events, task_complete_events[i])); + } + for (int i = 0; i < num_threads; i++) { + task_complete_events[i]->Wait(); + } + + // Shut things down. + for (int i = 0; i < num_threads; i++) { + threads[i]->Stop(); + delete threads[i]; + delete task_complete_events[i]; + } + + EndTraceAndFlush(); + + std::string tmp; + int tmp_int; + const DictionaryValue* item; + + // Make sure we get thread name metadata. + // Note, the test suite may have created a ton of threads. + // So, we'll have thread names for threads we didn't create. + std::vector<const DictionaryValue*> items = + FindTraceEntries(trace_parsed_, "thread_name"); + for (int i = 0; i < static_cast<int>(items.size()); i++) { + item = items[i]; + ASSERT_TRUE(item); + EXPECT_TRUE(item->GetInteger("tid", &tmp_int)); + + // See if this thread name is one of the threads we just created + for (int j = 0; j < num_threads; j++) { + if(static_cast<int>(thread_ids[j]) != tmp_int) + continue; + + std::string expected_name = StringPrintf("Thread %d", j); + EXPECT_TRUE(item->GetString("ph", &tmp) && tmp == "M"); + EXPECT_TRUE(item->GetInteger("pid", &tmp_int) && + tmp_int == static_cast<int>(base::GetCurrentProcId())); + // If the thread name changes or the tid gets reused, the name will be + // a comma-separated list of thread names, so look for a substring. + EXPECT_TRUE(item->GetString("args.name", &tmp) && + tmp.find(expected_name) != std::string::npos); + } + } +} + +TEST_F(TraceEventTestFixture, ThreadNameChanges) { + ManualTestSetUp(); + + BeginTrace(); + + PlatformThread::SetName(""); + TRACE_EVENT_INSTANT0("drink", "water"); + + PlatformThread::SetName("cafe"); + TRACE_EVENT_INSTANT0("drink", "coffee"); + + PlatformThread::SetName("shop"); + // No event here, so won't appear in combined name. + + PlatformThread::SetName("pub"); + TRACE_EVENT_INSTANT0("drink", "beer"); + TRACE_EVENT_INSTANT0("drink", "wine"); + + PlatformThread::SetName(" bar"); + TRACE_EVENT_INSTANT0("drink", "whisky"); + + EndTraceAndFlush(); + + std::vector<const DictionaryValue*> items = + FindTraceEntries(trace_parsed_, "thread_name"); + EXPECT_EQ(1u, items.size()); + ASSERT_GT(items.size(), 0u); + const DictionaryValue* item = items[0]; + ASSERT_TRUE(item); + int tid; + EXPECT_TRUE(item->GetInteger("tid", &tid)); + EXPECT_EQ(PlatformThread::CurrentId(), static_cast<PlatformThreadId>(tid)); + + std::string expected_name = "cafe,pub, bar"; + std::string tmp; + EXPECT_TRUE(item->GetString("args.name", &tmp)); + EXPECT_EQ(expected_name, tmp); +} + +// Test trace calls made after tracing singleton shut down. +// +// The singleton is destroyed by our base::AtExitManager, but there can be +// code still executing as the C++ static objects are destroyed. This test +// forces the singleton to destroy early, and intentinally makes trace calls +// afterwards. +TEST_F(TraceEventTestFixture, AtExit) { + // Repeat this test a few times. Besides just showing robustness, it also + // allows us to test that events at shutdown do not appear with valid events + // recorded after the system is started again. + for (int i = 0; i < 4; i++) { + // Scope to contain the then destroy the TraceLog singleton. + { + base::ShadowingAtExitManager exit_manager_will_destroy_singletons; + + // Setup TraceLog singleton inside this test's exit manager scope + // so that it will be destroyed when this scope closes. + ManualTestSetUp(); + + TRACE_EVENT_INSTANT0("all", "not recorded; system not enabled"); + + BeginTrace(); + + TRACE_EVENT_INSTANT0("all", "is recorded 1; system has been enabled"); + // Trace calls that will cache pointers to categories; they're valid here + TraceCallsWithCachedCategoryPointersPointers( + "is recorded 2; system has been enabled"); + + EndTraceAndFlush(); + } // scope to destroy singleton + ASSERT_FALSE(TraceLog::GetInstance()); + + // Now that singleton is destroyed, check what trace events were recorded + const DictionaryValue* item = NULL; + ListValue& trace_parsed = trace_parsed_; + EXPECT_FIND_("is recorded 1"); + EXPECT_FIND_("is recorded 2"); + EXPECT_NOT_FIND_("not recorded"); + + // Make additional trace event calls on the shutdown system. They should + // all pass cleanly, but the data not be recorded. We'll verify that next + // time around the loop (the only way to flush the trace buffers). + TRACE_EVENT_BEGIN_ETW("not recorded; system shutdown", 0, NULL); + TRACE_EVENT_END_ETW("not recorded; system shutdown", 0, NULL); + TRACE_EVENT_INSTANT_ETW("not recorded; system shutdown", 0, NULL); + TRACE_EVENT0("all", "not recorded; system shutdown"); + TRACE_EVENT_INSTANT0("all", "not recorded; system shutdown"); + TRACE_EVENT_BEGIN0("all", "not recorded; system shutdown"); + TRACE_EVENT_END0("all", "not recorded; system shutdown"); + + TRACE_EVENT0("new category 0!", "not recorded; system shutdown"); + TRACE_EVENT_INSTANT0("new category 1!", "not recorded; system shutdown"); + TRACE_EVENT_BEGIN0("new category 2!", "not recorded; system shutdown"); + TRACE_EVENT_END0("new category 3!", "not recorded; system shutdown"); + + // Cached categories should be safe to check, and still disable traces + TraceCallsWithCachedCategoryPointersPointers( + "not recorded; system shutdown"); + } +} + +TEST_F(TraceEventTestFixture, NormallyNoDeepCopy) { + // Test that the TRACE_EVENT macros do not deep-copy their string. If they + // do so it may indicate a performance regression, but more-over it would + // make the DEEP_COPY overloads redundant. + ManualTestSetUp(); + + std::string name_string("event name"); + + BeginTrace(); + TRACE_EVENT_INSTANT0("category", name_string.c_str()); + + // Modify the string in place (a wholesale reassignment may leave the old + // string intact on the heap). + name_string[0] = '@'; + + EndTraceAndFlush(); + + EXPECT_FALSE(FindTraceEntry(trace_parsed_, "event name")); + EXPECT_TRUE(FindTraceEntry(trace_parsed_, name_string.c_str())); +} + +TEST_F(TraceEventTestFixture, DeepCopy) { + ManualTestSetUp(); + + static const char kOriginalName1[] = "name1"; + static const char kOriginalName2[] = "name2"; + static const char kOriginalName3[] = "name3"; + std::string name1(kOriginalName1); + std::string name2(kOriginalName2); + std::string name3(kOriginalName3); + std::string arg1("arg1"); + std::string arg2("arg2"); + std::string val1("val1"); + std::string val2("val2"); + + BeginTrace(); + TRACE_EVENT_COPY_INSTANT0("category", name1.c_str()); + TRACE_EVENT_COPY_BEGIN1("category", name2.c_str(), + arg1.c_str(), 5); + TRACE_EVENT_COPY_END2("category", name3.c_str(), + arg1.c_str(), val1, + arg2.c_str(), val2); + + // As per NormallyNoDeepCopy, modify the strings in place. + name1[0] = name2[0] = name3[0] = arg1[0] = arg2[0] = val1[0] = val2[0] = '@'; + + EndTraceAndFlush(); + + EXPECT_FALSE(FindTraceEntry(trace_parsed_, name1.c_str())); + EXPECT_FALSE(FindTraceEntry(trace_parsed_, name2.c_str())); + EXPECT_FALSE(FindTraceEntry(trace_parsed_, name3.c_str())); + + const DictionaryValue* entry1 = FindTraceEntry(trace_parsed_, kOriginalName1); + const DictionaryValue* entry2 = FindTraceEntry(trace_parsed_, kOriginalName2); + const DictionaryValue* entry3 = FindTraceEntry(trace_parsed_, kOriginalName3); + ASSERT_TRUE(entry1); + ASSERT_TRUE(entry2); + ASSERT_TRUE(entry3); + + int i; + EXPECT_FALSE(entry2->GetInteger("args.@rg1", &i)); + EXPECT_TRUE(entry2->GetInteger("args.arg1", &i)); + EXPECT_EQ(5, i); + + std::string s; + EXPECT_TRUE(entry3->GetString("args.arg1", &s)); + EXPECT_EQ("val1", s); + EXPECT_TRUE(entry3->GetString("args.arg2", &s)); + EXPECT_EQ("val2", s); +} + +// Test that TraceResultBuffer outputs the correct result whether it is added +// in chunks or added all at once. +TEST_F(TraceEventTestFixture, TraceResultBuffer) { + ManualTestSetUp(); + + Clear(); + + trace_buffer_.Start(); + trace_buffer_.AddFragment("bla1"); + trace_buffer_.AddFragment("bla2"); + trace_buffer_.AddFragment("bla3,bla4"); + trace_buffer_.Finish(); + EXPECT_STREQ(json_output_.json_output.c_str(), "[bla1,bla2,bla3,bla4]"); + + Clear(); + + trace_buffer_.Start(); + trace_buffer_.AddFragment("bla1,bla2,bla3,bla4"); + trace_buffer_.Finish(); + EXPECT_STREQ(json_output_.json_output.c_str(), "[bla1,bla2,bla3,bla4]"); +} + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/trace_event_unittest.h b/src/base/debug/trace_event_unittest.h new file mode 100644 index 0000000..3b570e1 --- /dev/null +++ b/src/base/debug/trace_event_unittest.h
@@ -0,0 +1,14 @@ +// 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/time.h" + +namespace base { +namespace debug { + +// Sleep until HighResNow has advanced by at least |elapsed|. +void HighResSleepForTraceTest(base::TimeDelta elapsed); + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/trace_event_win.cc b/src/base/debug/trace_event_win.cc new file mode 100644 index 0000000..d5a21f4 --- /dev/null +++ b/src/base/debug/trace_event_win.cc
@@ -0,0 +1,120 @@ +// 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/debug/trace_event_win.h" + +#include "base/logging.h" +#include "base/memory/singleton.h" +#include <initguid.h> // NOLINT + +namespace base { +namespace debug { + +using base::win::EtwEventType; +using base::win::EtwMofEvent; + +// {3DADA31D-19EF-4dc1-B345-037927193422} +const GUID kChromeTraceProviderName = { + 0x3dada31d, 0x19ef, 0x4dc1, 0xb3, 0x45, 0x3, 0x79, 0x27, 0x19, 0x34, 0x22 }; + +// {B967AE67-BB22-49d7-9406-55D91EE1D560} +const GUID kTraceEventClass32 = { + 0xb967ae67, 0xbb22, 0x49d7, 0x94, 0x6, 0x55, 0xd9, 0x1e, 0xe1, 0xd5, 0x60 }; + +// {97BE602D-2930-4ac3-8046-B6763B631DFE} +const GUID kTraceEventClass64 = { + 0x97be602d, 0x2930, 0x4ac3, 0x80, 0x46, 0xb6, 0x76, 0x3b, 0x63, 0x1d, 0xfe}; + + +TraceEventETWProvider::TraceEventETWProvider() : + EtwTraceProvider(kChromeTraceProviderName) { + Register(); +} + +TraceEventETWProvider* TraceEventETWProvider::GetInstance() { + return Singleton<TraceEventETWProvider, + StaticMemorySingletonTraits<TraceEventETWProvider> >::get(); +} + +bool TraceEventETWProvider::StartTracing() { + return true; +} + +void TraceEventETWProvider::TraceEvent(const char* name, + size_t name_len, + char type, + const void* id, + const char* extra, + size_t extra_len) { + // Make sure we don't touch NULL. + if (name == NULL) + name = ""; + if (extra == NULL) + extra = ""; + + EtwEventType etw_type = 0; + switch (type) { + case TRACE_EVENT_PHASE_BEGIN: + etw_type = kTraceEventTypeBegin; + break; + case TRACE_EVENT_PHASE_END: + etw_type = kTraceEventTypeEnd; + break; + + case TRACE_EVENT_PHASE_INSTANT: + etw_type = kTraceEventTypeInstant; + break; + + default: + NOTREACHED() << "Unknown event type"; + etw_type = kTraceEventTypeInstant; + break; + } + + EtwMofEvent<5> event(kTraceEventClass32, + etw_type, + TRACE_LEVEL_INFORMATION); + event.SetField(0, name_len + 1, name); + event.SetField(1, sizeof(id), &id); + event.SetField(2, extra_len + 1, extra); + + // See whether we're to capture a backtrace. + void* backtrace[32]; + if (enable_flags() & CAPTURE_STACK_TRACE) { + DWORD hash = 0; + DWORD depth = CaptureStackBackTrace(0, + arraysize(backtrace), + backtrace, + &hash); + event.SetField(3, sizeof(depth), &depth); + event.SetField(4, sizeof(backtrace[0]) * depth, backtrace); + } + + // Trace the event. + Log(event.get()); +} + +void TraceEventETWProvider::Trace(const char* name, + size_t name_len, + char type, + const void* id, + const char* extra, + size_t extra_len) { + TraceEventETWProvider* provider = TraceEventETWProvider::GetInstance(); + if (provider && provider->IsTracing()) { + // Compute the name & extra lengths if not supplied already. + if (name_len == -1) + name_len = (name == NULL) ? 0 : strlen(name); + if (extra_len == -1) + extra_len = (extra == NULL) ? 0 : strlen(extra); + + provider->TraceEvent(name, name_len, type, id, extra, extra_len); + } +} + +void TraceEventETWProvider::Resurrect() { + StaticMemorySingletonTraits<TraceEventETWProvider>::Resurrect(); +} + +} // namespace debug +} // namespace base
diff --git a/src/base/debug/trace_event_win.h b/src/base/debug/trace_event_win.h new file mode 100644 index 0000000..2a900bb --- /dev/null +++ b/src/base/debug/trace_event_win.h
@@ -0,0 +1,123 @@ +// 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. + +// This file contains the Windows-specific declarations for trace_event.h. +#ifndef BASE_DEBUG_TRACE_EVENT_WIN_H_ +#define BASE_DEBUG_TRACE_EVENT_WIN_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/debug/trace_event.h" +#include "base/win/event_trace_provider.h" + +// Fwd. +template <typename Type> +struct StaticMemorySingletonTraits; + +namespace base { +namespace debug { + +// This EtwTraceProvider subclass implements ETW logging +// for the macros above on Windows. +class BASE_EXPORT TraceEventETWProvider : public base::win::EtwTraceProvider { + public: + // Start logging trace events. + // This is a noop in this implementation. + static bool StartTracing(); + + // Trace begin/end/instant events, this is the bottleneck implementation + // all the others defer to. + // Allowing the use of std::string for name or extra is a convenience, + // whereas passing name or extra as a const char* avoids the construction + // of temporary std::string instances. + // If -1 is passed for name_len or extra_len, the strlen of the string will + // be used for length. + static void Trace(const char* name, + size_t name_len, + char type, + const void* id, + const char* extra, + size_t extra_len); + + // Allows passing extra as a std::string for convenience. + static void Trace(const char* name, + char type, + const void* id, + const std::string& extra) { + return Trace(name, -1, type, id, extra.c_str(), extra.length()); + } + + // Allows passing extra as a const char* to avoid constructing temporary + // std::string instances where not needed. + static void Trace(const char* name, + char type, + const void* id, + const char* extra) { + return Trace(name, -1, type, id, extra, -1); + } + + // Retrieves the singleton. + // Note that this may return NULL post-AtExit processing. + static TraceEventETWProvider* GetInstance(); + + // Returns true iff tracing is turned on. + bool IsTracing() { + return enable_level() >= TRACE_LEVEL_INFORMATION; + } + + // Emit a trace of type |type| containing |name|, |id|, and |extra|. + // Note: |name| and |extra| must be NULL, or a zero-terminated string of + // length |name_len| or |extra_len| respectively. + // Note: if name_len or extra_len are -1, the length of the corresponding + // string will be used. + void TraceEvent(const char* name, + size_t name_len, + char type, + const void* id, + const char* extra, + size_t extra_len); + + // Exposed for unittesting only, allows resurrecting our + // singleton instance post-AtExit processing. + static void Resurrect(); + + private: + // Ensure only the provider can construct us. + friend struct StaticMemorySingletonTraits<TraceEventETWProvider>; + TraceEventETWProvider(); + + DISALLOW_COPY_AND_ASSIGN(TraceEventETWProvider); +}; + +// The ETW trace provider GUID. +BASE_EXPORT extern const GUID kChromeTraceProviderName; + +// The ETW event class GUID for 32 bit events. +BASE_EXPORT extern const GUID kTraceEventClass32; + +// The ETW event class GUID for 64 bit events. +BASE_EXPORT extern const GUID kTraceEventClass64; + +// The ETW event types, IDs 0x00-0x09 are reserved, so start at 0x10. +const base::win::EtwEventType kTraceEventTypeBegin = 0x10; +const base::win::EtwEventType kTraceEventTypeEnd = 0x11; +const base::win::EtwEventType kTraceEventTypeInstant = 0x12; + +// If this flag is set in enable flags +enum TraceEventETWFlags { + CAPTURE_STACK_TRACE = 0x0001, +}; + +// The event format consists of: +// The "name" string as a zero-terminated ASCII string. +// The id pointer in the machine bitness. +// The "extra" string as a zero-terminated ASCII string. +// Optionally the stack trace, consisting of a DWORD "depth", followed +// by an array of void* (machine bitness) of length "depth". + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_TRACE_EVENT_WIN_H_
diff --git a/src/base/debug/trace_event_win_unittest.cc b/src/base/debug/trace_event_win_unittest.cc new file mode 100644 index 0000000..786b9d5 --- /dev/null +++ b/src/base/debug/trace_event_win_unittest.cc
@@ -0,0 +1,316 @@ +// 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/debug/trace_event.h" + +#include <strstream> + +#include "base/at_exit.h" +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/debug/trace_event.h" +#include "base/debug/trace_event_win.h" +#include "base/win/event_trace_consumer.h" +#include "base/win/event_trace_controller.h" +#include "base/win/event_trace_provider.h" +#include "base/win/windows_version.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include <initguid.h> // NOLINT - must be last include. + +namespace base { +namespace debug { + +namespace { + +using testing::_; +using testing::AnyNumber; +using testing::InSequence; +using testing::Ge; +using testing::Le; +using testing::NotNull; + +using base::win::EtwEventType; +using base::win::EtwTraceConsumerBase; +using base::win::EtwTraceController; +using base::win::EtwTraceProperties; + +// Data for unittests traces. +const char kEmpty[] = ""; +const char kName[] = "unittest.trace_name"; +const char kExtra[] = "UnittestDummyExtraString"; +const void* kId = kName; + +const wchar_t kTestSessionName[] = L"TraceEvent unittest session"; + +MATCHER_P(BufferStartsWith, str, "Buffer starts with") { + return memcmp(arg, str.c_str(), str.length()) == 0; +} + +// Duplicated from <evntrace.h> to fix link problems. +DEFINE_GUID( /* 68fdd900-4a3e-11d1-84f4-0000f80464e3 */ + kEventTraceGuid, + 0x68fdd900, + 0x4a3e, + 0x11d1, + 0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3); + +class TestEventConsumer: public EtwTraceConsumerBase<TestEventConsumer> { + public: + TestEventConsumer() { + EXPECT_TRUE(current_ == NULL); + current_ = this; + } + + ~TestEventConsumer() { + EXPECT_TRUE(current_ == this); + current_ = NULL; + } + + MOCK_METHOD4(Event, void(REFGUID event_class, + EtwEventType event_type, + size_t buf_len, + const void* buf)); + + static void ProcessEvent(EVENT_TRACE* event) { + ASSERT_TRUE(current_ != NULL); + current_->Event(event->Header.Guid, + event->Header.Class.Type, + event->MofLength, + event->MofData); + } + + private: + static TestEventConsumer* current_; +}; + +TestEventConsumer* TestEventConsumer::current_ = NULL; + +class TraceEventWinTest: public testing::Test { + public: + TraceEventWinTest() { + } + + void SetUp() { + bool is_xp = win::GetVersion() < base::win::VERSION_VISTA; + + if (is_xp) { + // Tear down any dangling session from an earlier failing test. + EtwTraceProperties ignore; + EtwTraceController::Stop(kTestSessionName, &ignore); + } + + // Resurrect and initialize the TraceLog singleton instance. + // On Vista and better, we need the provider registered before we + // start the private, in-proc session, but on XP we need the global + // session created and the provider enabled before we register our + // provider. + TraceEventETWProvider* tracelog = NULL; + if (!is_xp) { + TraceEventETWProvider::Resurrect(); + tracelog = TraceEventETWProvider::GetInstance(); + ASSERT_TRUE(tracelog != NULL); + ASSERT_FALSE(tracelog->IsTracing()); + } + + // Create the log file. + ASSERT_TRUE(file_util::CreateTemporaryFile(&log_file_)); + + // Create a private log session on the file. + EtwTraceProperties prop; + ASSERT_HRESULT_SUCCEEDED(prop.SetLoggerFileName(log_file_.value().c_str())); + EVENT_TRACE_PROPERTIES& p = *prop.get(); + p.Wnode.ClientContext = 1; // QPC timer accuracy. + p.LogFileMode = EVENT_TRACE_FILE_MODE_SEQUENTIAL; // Sequential log. + + // On Vista and later, we create a private in-process log session, because + // otherwise we'd need administrator privileges. Unfortunately we can't + // do the same on XP and better, because the semantics of a private + // logger session are different, and the IN_PROC flag is not supported. + if (!is_xp) { + p.LogFileMode |= EVENT_TRACE_PRIVATE_IN_PROC | // In-proc for non-admin. + EVENT_TRACE_PRIVATE_LOGGER_MODE; // Process-private log. + } + + p.MaximumFileSize = 100; // 100M file size. + p.FlushTimer = 1; // 1 second flush lag. + ASSERT_HRESULT_SUCCEEDED(controller_.Start(kTestSessionName, &prop)); + + // Enable the TraceLog provider GUID. + ASSERT_HRESULT_SUCCEEDED( + controller_.EnableProvider(kChromeTraceProviderName, + TRACE_LEVEL_INFORMATION, + 0)); + + if (is_xp) { + TraceEventETWProvider::Resurrect(); + tracelog = TraceEventETWProvider::GetInstance(); + } + ASSERT_TRUE(tracelog != NULL); + EXPECT_TRUE(tracelog->IsTracing()); + } + + void TearDown() { + EtwTraceProperties prop; + if (controller_.session() != 0) + EXPECT_HRESULT_SUCCEEDED(controller_.Stop(&prop)); + + if (!log_file_.value().empty()) + file_util::Delete(log_file_, false); + } + + void ExpectEvent(REFGUID guid, + EtwEventType type, + const char* name, + size_t name_len, + const void* id, + const char* extra, + size_t extra_len) { + // Build the trace event buffer we expect will result from this. + std::stringbuf str; + str.sputn(name, name_len + 1); + str.sputn(reinterpret_cast<const char*>(&id), sizeof(id)); + str.sputn(extra, extra_len + 1); + + // And set up the expectation for the event callback. + EXPECT_CALL(consumer_, Event(guid, + type, + testing::Ge(str.str().length()), + BufferStartsWith(str.str()))); + } + + void ExpectPlayLog() { + // Ignore EventTraceGuid events. + EXPECT_CALL(consumer_, Event(kEventTraceGuid, _, _, _)) + .Times(AnyNumber()); + } + + void PlayLog() { + EtwTraceProperties prop; + EXPECT_HRESULT_SUCCEEDED(controller_.Flush(&prop)); + EXPECT_HRESULT_SUCCEEDED(controller_.Stop(&prop)); + ASSERT_HRESULT_SUCCEEDED( + consumer_.OpenFileSession(log_file_.value().c_str())); + + ASSERT_HRESULT_SUCCEEDED(consumer_.Consume()); + } + + private: + // We want our singleton torn down after each test. + ShadowingAtExitManager at_exit_manager_; + EtwTraceController controller_; + FilePath log_file_; + TestEventConsumer consumer_; +}; + +} // namespace + + +TEST_F(TraceEventWinTest, TraceLog) { + ExpectPlayLog(); + + // The events should arrive in the same sequence as the expects. + InSequence in_sequence; + + // Full argument version, passing lengths explicitly. + TraceEventETWProvider::Trace(kName, + strlen(kName), + TRACE_EVENT_PHASE_BEGIN, + kId, + kExtra, + strlen(kExtra)); + + ExpectEvent(kTraceEventClass32, + kTraceEventTypeBegin, + kName, strlen(kName), + kId, + kExtra, strlen(kExtra)); + + // Const char* version. + TraceEventETWProvider::Trace(static_cast<const char*>(kName), + TRACE_EVENT_PHASE_END, + kId, + static_cast<const char*>(kExtra)); + + ExpectEvent(kTraceEventClass32, + kTraceEventTypeEnd, + kName, strlen(kName), + kId, + kExtra, strlen(kExtra)); + + // std::string extra version. + TraceEventETWProvider::Trace(static_cast<const char*>(kName), + TRACE_EVENT_PHASE_INSTANT, + kId, + std::string(kExtra)); + + ExpectEvent(kTraceEventClass32, + kTraceEventTypeInstant, + kName, strlen(kName), + kId, + kExtra, strlen(kExtra)); + + + // Test for sanity on NULL inputs. + TraceEventETWProvider::Trace(NULL, + 0, + TRACE_EVENT_PHASE_BEGIN, + kId, + NULL, + 0); + + ExpectEvent(kTraceEventClass32, + kTraceEventTypeBegin, + kEmpty, 0, + kId, + kEmpty, 0); + + TraceEventETWProvider::Trace(NULL, + -1, + TRACE_EVENT_PHASE_END, + kId, + NULL, + -1); + + ExpectEvent(kTraceEventClass32, + kTraceEventTypeEnd, + kEmpty, 0, + kId, + kEmpty, 0); + + PlayLog(); +} + +TEST_F(TraceEventWinTest, Macros) { + ExpectPlayLog(); + + // The events should arrive in the same sequence as the expects. + InSequence in_sequence; + + TRACE_EVENT_BEGIN_ETW(kName, kId, kExtra); + ExpectEvent(kTraceEventClass32, + kTraceEventTypeBegin, + kName, strlen(kName), + kId, + kExtra, strlen(kExtra)); + + TRACE_EVENT_END_ETW(kName, kId, kExtra); + ExpectEvent(kTraceEventClass32, + kTraceEventTypeEnd, + kName, strlen(kName), + kId, + kExtra, strlen(kExtra)); + + TRACE_EVENT_INSTANT_ETW(kName, kId, kExtra); + ExpectEvent(kTraceEventClass32, + kTraceEventTypeInstant, + kName, strlen(kName), + kId, + kExtra, strlen(kExtra)); + + PlayLog(); +} + +} // namespace debug +} // namespace base
diff --git a/src/base/debug_message.cc b/src/base/debug_message.cc new file mode 100644 index 0000000..10f441d --- /dev/null +++ b/src/base/debug_message.cc
@@ -0,0 +1,17 @@ +// Copyright (c) 2006-2008 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 <windows.h> + +// Display the command line. This program is designed to be called from +// another process to display assertions. Since the other process has +// complete control of our command line, we assume that it did *not* +// add the program name as the first parameter. This allows us to just +// show the command line directly as the message. +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int nCmdShow) { + LPWSTR cmdline = GetCommandLineW(); + MessageBox(NULL, cmdline, L"Kr\x00d8m", MB_TOPMOST); + return 0; +}
diff --git a/src/base/environment.cc b/src/base/environment.cc new file mode 100644 index 0000000..8c61591 --- /dev/null +++ b/src/base/environment.cc
@@ -0,0 +1,127 @@ +// 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/environment.h" + +#if defined(OS_POSIX) +#include <stdlib.h> +#elif defined(OS_WIN) +#include <windows.h> +#endif + +#include "base/string_util.h" + +#if defined(OS_WIN) +#include "base/memory/scoped_ptr.h" +#include "base/utf_string_conversions.h" +#endif + +namespace { + +class EnvironmentImpl : public base::Environment { + public: + virtual bool GetVar(const char* variable_name, + std::string* result) OVERRIDE { + if (GetVarImpl(variable_name, result)) + return true; + + // Some commonly used variable names are uppercase while others + // are lowercase, which is inconsistent. Let's try to be helpful + // and look for a variable name with the reverse case. + // I.e. HTTP_PROXY may be http_proxy for some users/systems. + char first_char = variable_name[0]; + std::string alternate_case_var; + if (first_char >= 'a' && first_char <= 'z') + alternate_case_var = StringToUpperASCII(std::string(variable_name)); + else if (first_char >= 'A' && first_char <= 'Z') + alternate_case_var = StringToLowerASCII(std::string(variable_name)); + else + return false; + return GetVarImpl(alternate_case_var.c_str(), result); + } + + virtual bool SetVar(const char* variable_name, + const std::string& new_value) OVERRIDE { + return SetVarImpl(variable_name, new_value); + } + + virtual bool UnSetVar(const char* variable_name) OVERRIDE { + return UnSetVarImpl(variable_name); + } + + private: + bool GetVarImpl(const char* variable_name, std::string* result) { +#if defined(OS_POSIX) + const char* env_value = getenv(variable_name); + if (!env_value) + return false; + // Note that the variable may be defined but empty. + if (result) + *result = env_value; + return true; +#elif defined(OS_WIN) + DWORD value_length = ::GetEnvironmentVariable( + UTF8ToWide(variable_name).c_str(), NULL, 0); + if (value_length == 0) + return false; + if (result) { + scoped_array<wchar_t> value(new wchar_t[value_length]); + ::GetEnvironmentVariable(UTF8ToWide(variable_name).c_str(), value.get(), + value_length); + *result = WideToUTF8(value.get()); + } + return true; +#else +#error need to port +#endif + } + + bool SetVarImpl(const char* variable_name, const std::string& new_value) { +#if defined(OS_POSIX) + // On success, zero is returned. + return !setenv(variable_name, new_value.c_str(), 1); +#elif defined(OS_WIN) + // On success, a nonzero value is returned. + return !!SetEnvironmentVariable(UTF8ToWide(variable_name).c_str(), + UTF8ToWide(new_value).c_str()); +#endif + } + + bool UnSetVarImpl(const char* variable_name) { +#if defined(OS_POSIX) + // On success, zero is returned. + return !unsetenv(variable_name); +#elif defined(OS_WIN) + // On success, a nonzero value is returned. + return !!SetEnvironmentVariable(UTF8ToWide(variable_name).c_str(), NULL); +#endif + } +}; + +} // namespace + +namespace base { + +namespace env_vars { + +#if defined(OS_POSIX) +// On Posix systems, this variable contains the location of the user's home +// directory. (e.g, /home/username/). +const char kHome[] = "HOME"; +#endif + +} // namespace env_vars + +Environment::~Environment() {} + +// static +Environment* Environment::Create() { + return new EnvironmentImpl(); +} + +bool Environment::HasVar(const char* variable_name) { + return GetVar(variable_name, NULL); +} + +} // namespace base
diff --git a/src/base/environment.h b/src/base/environment.h new file mode 100644 index 0000000..5160ff2 --- /dev/null +++ b/src/base/environment.h
@@ -0,0 +1,48 @@ +// 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. + +#ifndef BASE_ENVIRONMENT_H_ +#define BASE_ENVIRONMENT_H_ + +#include <string> + +#include "base/base_export.h" +#include "build/build_config.h" + +namespace base { + +namespace env_vars { + +#if defined(OS_POSIX) +BASE_EXPORT extern const char kHome[]; +#endif + +} // namespace env_vars + +class BASE_EXPORT Environment { + public: + virtual ~Environment(); + + // Static factory method that returns the implementation that provide the + // appropriate platform-specific instance. + static Environment* Create(); + + // Gets an environment variable's value and stores it in |result|. + // Returns false if the key is unset. + virtual bool GetVar(const char* variable_name, std::string* result) = 0; + + // Syntactic sugar for GetVar(variable_name, NULL); + virtual bool HasVar(const char* variable_name); + + // Returns true on success, otherwise returns false. + virtual bool SetVar(const char* variable_name, + const std::string& new_value) = 0; + + // Returns true on success, otherwise returns false. + virtual bool UnSetVar(const char* variable_name) = 0; +}; + +} // namespace base + +#endif // BASE_ENVIRONMENT_H_
diff --git a/src/base/environment_unittest.cc b/src/base/environment_unittest.cc new file mode 100644 index 0000000..b6654c9 --- /dev/null +++ b/src/base/environment_unittest.cc
@@ -0,0 +1,85 @@ +// 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/environment.h" +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +typedef PlatformTest EnvironmentTest; + +TEST_F(EnvironmentTest, GetVar) { + // Every setup should have non-empty PATH... + scoped_ptr<base::Environment> env(base::Environment::Create()); + std::string env_value; + EXPECT_TRUE(env->GetVar("PATH", &env_value)); + EXPECT_NE(env_value, ""); +} + +TEST_F(EnvironmentTest, GetVarReverse) { + scoped_ptr<base::Environment> env(base::Environment::Create()); + const char* kFooUpper = "FOO"; + const char* kFooLower = "foo"; + + // Set a variable in UPPER case. + EXPECT_TRUE(env->SetVar(kFooUpper, kFooLower)); + + // And then try to get this variable passing the lower case. + std::string env_value; + EXPECT_TRUE(env->GetVar(kFooLower, &env_value)); + + EXPECT_STREQ(env_value.c_str(), kFooLower); + + EXPECT_TRUE(env->UnSetVar(kFooUpper)); + + const char* kBar = "bar"; + // Now do the opposite, set the variable in the lower case. + EXPECT_TRUE(env->SetVar(kFooLower, kBar)); + + // And then try to get this variable passing the UPPER case. + EXPECT_TRUE(env->GetVar(kFooUpper, &env_value)); + + EXPECT_STREQ(env_value.c_str(), kBar); + + EXPECT_TRUE(env->UnSetVar(kFooLower)); +} + +TEST_F(EnvironmentTest, HasVar) { + // Every setup should have PATH... + scoped_ptr<base::Environment> env(base::Environment::Create()); + EXPECT_TRUE(env->HasVar("PATH")); +} + +TEST_F(EnvironmentTest, SetVar) { + scoped_ptr<base::Environment> env(base::Environment::Create()); + + const char* kFooUpper = "FOO"; + const char* kFooLower = "foo"; + EXPECT_TRUE(env->SetVar(kFooUpper, kFooLower)); + + // Now verify that the environment has the new variable. + EXPECT_TRUE(env->HasVar(kFooUpper)); + + std::string var_value; + EXPECT_TRUE(env->GetVar(kFooUpper, &var_value)); + EXPECT_EQ(var_value, kFooLower); +} + +TEST_F(EnvironmentTest, UnSetVar) { + scoped_ptr<base::Environment> env(base::Environment::Create()); + + const char* kFooUpper = "FOO"; + const char* kFooLower = "foo"; + // First set some environment variable. + EXPECT_TRUE(env->SetVar(kFooUpper, kFooLower)); + + // Now verify that the environment has the new variable. + EXPECT_TRUE(env->HasVar(kFooUpper)); + + // Finally verify that the environment variable was erased. + EXPECT_TRUE(env->UnSetVar(kFooUpper)); + + // And check that the variable has been unset. + EXPECT_FALSE(env->HasVar(kFooUpper)); +}
diff --git a/src/base/event_recorder.h b/src/base/event_recorder.h new file mode 100644 index 0000000..32e1124 --- /dev/null +++ b/src/base/event_recorder.h
@@ -0,0 +1,109 @@ +// 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. + +#ifndef BASE_EVENT_RECORDER_H_ +#define BASE_EVENT_RECORDER_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <stdio.h> +#include <string.h> +#include <windows.h> +#endif + +class FilePath; + +namespace base { + +// A class for recording and playing back keyboard and mouse input events. +// +// Note - if you record events, and the playback with the windows in +// different sizes or positions, the playback will fail. When +// recording and playing, you should move the relevant windows +// to constant sizes and locations. +// TODO(mbelshe) For now this is a singleton. I believe that this class +// could be easily modified to: +// support two simultaneous recorders +// be playing back events while already recording events. +// Why? Imagine if the product had a "record a macro" feature. +// You might be recording globally, while recording or playing back +// a macro. I don't think two playbacks make sense. +class BASE_EXPORT EventRecorder { + public: + // Get the singleton EventRecorder. + // We can only handle one recorder/player at a time. + static EventRecorder* current() { + if (!current_) + current_ = new EventRecorder(); + return current_; + } + + // Starts recording events. + // Will clobber the file if it already exists. + // Returns true on success, or false if an error occurred. + bool StartRecording(const FilePath& filename); + + // Stops recording. + void StopRecording(); + + // Is the EventRecorder currently recording. + bool is_recording() const { return is_recording_; } + + // Plays events previously recorded. + // Returns true on success, or false if an error occurred. + bool StartPlayback(const FilePath& filename); + + // Stops playback. + void StopPlayback(); + + // Is the EventRecorder currently playing. + bool is_playing() const { return is_playing_; } + +#if defined(OS_WIN) + // C-style callbacks for the EventRecorder. + // Used for internal purposes only. + LRESULT RecordWndProc(int nCode, WPARAM wParam, LPARAM lParam); + LRESULT PlaybackWndProc(int nCode, WPARAM wParam, LPARAM lParam); +#endif + + private: + // Create a new EventRecorder. Events are saved to the file filename. + // If the file already exists, it will be deleted before recording + // starts. + explicit EventRecorder() + : is_recording_(false), + is_playing_(false), +#if defined(OS_WIN) + journal_hook_(NULL), + file_(NULL), +#endif + playback_first_msg_time_(0), + playback_start_time_(0) { +#if defined(OS_WIN) + memset(&playback_msg_, 0, sizeof(playback_msg_)); +#endif + } + ~EventRecorder(); + + static EventRecorder* current_; // Our singleton. + + bool is_recording_; + bool is_playing_; +#if defined(OS_WIN) + HHOOK journal_hook_; + FILE* file_; + EVENTMSG playback_msg_; +#endif + int playback_first_msg_time_; + int playback_start_time_; + + DISALLOW_COPY_AND_ASSIGN(EventRecorder); +}; + +} // namespace base + +#endif // BASE_EVENT_RECORDER_H_
diff --git a/src/base/event_recorder_stubs.cc b/src/base/event_recorder_stubs.cc new file mode 100644 index 0000000..91f2e07 --- /dev/null +++ b/src/base/event_recorder_stubs.cc
@@ -0,0 +1,28 @@ +// Copyright (c) 2009 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/event_recorder.h" + +// This file implements a link stub for EventRecorder that can be used on +// platforms that don't have a working EventRecorder implementation. + +namespace base { + +EventRecorder* EventRecorder::current_; // Our singleton. + +bool EventRecorder::StartRecording(const FilePath& filename) { + return true; +} + +void EventRecorder::StopRecording() { +} + +bool EventRecorder::StartPlayback(const FilePath& filename) { + return false; +} + +void EventRecorder::StopPlayback() { +} + +} // namespace
diff --git a/src/base/event_recorder_win.cc b/src/base/event_recorder_win.cc new file mode 100644 index 0000000..11bf0f0 --- /dev/null +++ b/src/base/event_recorder_win.cc
@@ -0,0 +1,258 @@ +// 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 <stddef.h> +#include <windows.h> +#include <mmsystem.h> + +#include "base/event_recorder.h" +#include "base/file_util.h" +#include "base/logging.h" + +// A note about time. +// For perfect playback of events, you'd like a very accurate timer +// so that events are played back at exactly the same time that +// they were recorded. However, windows has a clock which is only +// granular to ~15ms. We see more consistent event playback when +// using a higher resolution timer. To do this, we use the +// timeGetTime API instead of the default GetTickCount() API. + +namespace base { + +EventRecorder* EventRecorder::current_ = NULL; + +LRESULT CALLBACK StaticRecordWndProc(int nCode, WPARAM wParam, + LPARAM lParam) { + DCHECK(EventRecorder::current()); + return EventRecorder::current()->RecordWndProc(nCode, wParam, lParam); +} + +LRESULT CALLBACK StaticPlaybackWndProc(int nCode, WPARAM wParam, + LPARAM lParam) { + DCHECK(EventRecorder::current()); + return EventRecorder::current()->PlaybackWndProc(nCode, wParam, lParam); +} + +EventRecorder::~EventRecorder() { + // Try to assert early if the caller deletes the recorder + // while it is still in use. + DCHECK(!journal_hook_); + DCHECK(!is_recording_ && !is_playing_); +} + +bool EventRecorder::StartRecording(const FilePath& filename) { + if (journal_hook_ != NULL) + return false; + if (is_recording_ || is_playing_) + return false; + + // Open the recording file. + DCHECK(!file_); + file_ = file_util::OpenFile(filename, "wb+"); + if (!file_) { + DLOG(ERROR) << "EventRecorder could not open log file"; + return false; + } + + // Set the faster clock, if possible. + ::timeBeginPeriod(1); + + // Set the recording hook. JOURNALRECORD can only be used as a global hook. + journal_hook_ = ::SetWindowsHookEx(WH_JOURNALRECORD, StaticRecordWndProc, + GetModuleHandle(NULL), 0); + if (!journal_hook_) { + DLOG(ERROR) << "EventRecorder Record Hook failed"; + file_util::CloseFile(file_); + return false; + } + + is_recording_ = true; + return true; +} + +void EventRecorder::StopRecording() { + if (is_recording_) { + DCHECK(journal_hook_ != NULL); + + if (!::UnhookWindowsHookEx(journal_hook_)) { + DLOG(ERROR) << "EventRecorder Unhook failed"; + // Nothing else we can really do here. + return; + } + + ::timeEndPeriod(1); + + DCHECK(file_ != NULL); + file_util::CloseFile(file_); + file_ = NULL; + + journal_hook_ = NULL; + is_recording_ = false; + } +} + +bool EventRecorder::StartPlayback(const FilePath& filename) { + if (journal_hook_ != NULL) + return false; + if (is_recording_ || is_playing_) + return false; + + // Open the recording file. + DCHECK(!file_); + file_ = file_util::OpenFile(filename, "rb"); + if (!file_) { + DLOG(ERROR) << "EventRecorder Playback could not open log file"; + return false; + } + // 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_); + return false; + } + + // Set the faster clock, if possible. + ::timeBeginPeriod(1); + + // Playback time is tricky. When playing back, we read a series of events, + // each with timeouts. Simply subtracting the delta between two timers will + // lead to fast playback (about 2x speed). The API has two events, one + // which advances to the next event (HC_SKIP), and another that requests the + // event (HC_GETNEXT). The same event will be requested multiple times. + // Each time the event is requested, we must calculate the new delay. + // To do this, we track the start time of the playback, and constantly + // re-compute the delay. I mention this only because I saw two examples + // of how to use this code on the net, and both were broken :-) + playback_start_time_ = timeGetTime(); + playback_first_msg_time_ = playback_msg_.time; + + // Set the hook. JOURNALPLAYBACK can only be used as a global hook. + journal_hook_ = ::SetWindowsHookEx(WH_JOURNALPLAYBACK, StaticPlaybackWndProc, + GetModuleHandle(NULL), 0); + if (!journal_hook_) { + DLOG(ERROR) << "EventRecorder Playback Hook failed"; + return false; + } + + is_playing_ = true; + + return true; +} + +void EventRecorder::StopPlayback() { + if (is_playing_) { + DCHECK(journal_hook_ != NULL); + + if (!::UnhookWindowsHookEx(journal_hook_)) { + DLOG(ERROR) << "EventRecorder Unhook failed"; + // Nothing else we can really do here. + } + + DCHECK(file_ != NULL); + file_util::CloseFile(file_); + file_ = NULL; + + ::timeEndPeriod(1); + + journal_hook_ = NULL; + is_playing_ = false; + } +} + +// Windows callback hook for the recorder. +LRESULT EventRecorder::RecordWndProc(int nCode, WPARAM wParam, LPARAM lParam) { + static bool recording_enabled = true; + EVENTMSG* msg_ptr = NULL; + + // The API says we have to do this. + // See http://msdn2.microsoft.com/en-us/library/ms644983(VS.85).aspx + if (nCode < 0) + return ::CallNextHookEx(journal_hook_, nCode, wParam, lParam); + + // Check for the break key being pressed and stop recording. + if (::GetKeyState(VK_CANCEL) & 0x8000) { + StopRecording(); + return ::CallNextHookEx(journal_hook_, nCode, wParam, lParam); + } + + // The Journal Recorder must stop recording events when system modal + // dialogs are present. (see msdn link above) + switch (nCode) { + case HC_SYSMODALON: + recording_enabled = false; + break; + case HC_SYSMODALOFF: + recording_enabled = true; + break; + } + + if (nCode == HC_ACTION && recording_enabled) { + // Aha - we have an event to record. + msg_ptr = reinterpret_cast<EVENTMSG*>(lParam); + msg_ptr->time = timeGetTime(); + fwrite(msg_ptr, sizeof(EVENTMSG), 1, file_); + fflush(file_); + } + + return CallNextHookEx(journal_hook_, nCode, wParam, lParam); +} + +// Windows callback for the playback mode. +LRESULT EventRecorder::PlaybackWndProc(int nCode, WPARAM wParam, + LPARAM lParam) { + static bool playback_enabled = true; + int delay = 0; + + switch (nCode) { + // A system modal dialog box is being displayed. Stop playing back + // messages. + case HC_SYSMODALON: + playback_enabled = false; + break; + + // A system modal dialog box is destroyed. We can start playing back + // messages again. + case HC_SYSMODALOFF: + playback_enabled = true; + break; + + // Prepare to copy the next mouse or keyboard event to playback. + case HC_SKIP: + if (!playback_enabled) + break; + + // Read the next event from the record. + if (fread(&playback_msg_, sizeof(EVENTMSG), 1, file_) != 1) + this->StopPlayback(); + break; + + // Copy the mouse or keyboard event to the EVENTMSG structure in lParam. + case HC_GETNEXT: + if (!playback_enabled) + break; + + memcpy(reinterpret_cast<void*>(lParam), &playback_msg_, + sizeof(playback_msg_)); + + // The return value is the amount of time (in milliseconds) to wait + // before playing back the next message in the playback queue. Each + // time this is called, we recalculate the delay relative to our current + // wall clock. + delay = (playback_msg_.time - playback_first_msg_time_) - + (timeGetTime() - playback_start_time_); + if (delay < 0) + delay = 0; + return delay; + + // An application has called PeekMessage with wRemoveMsg set to PM_NOREMOVE + // indicating that the message is not removed from the message queue after + // PeekMessage processing. + case HC_NOREMOVE: + break; + } + + return CallNextHookEx(journal_hook_, nCode, wParam, lParam); +} + +} // namespace base
diff --git a/src/base/event_types.h b/src/base/event_types.h new file mode 100644 index 0000000..af586e4 --- /dev/null +++ b/src/base/event_types.h
@@ -0,0 +1,37 @@ +// 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_EVENT_TYPES_H +#define BASE_EVENT_TYPES_H + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#elif defined(USE_X11) +typedef union _XEvent XEvent; +#elif defined(OS_MACOSX) +#if defined(__OBJC__) +@class NSEvent; +#else // __OBJC__ +class NSEvent; +#endif // __OBJC__ +#endif + +namespace base { + +// Cross platform typedefs for native event types. +#if defined(OS_WIN) +typedef MSG NativeEvent; +#elif defined(USE_X11) +typedef XEvent* NativeEvent; +#elif defined(OS_MACOSX) +typedef NSEvent* NativeEvent; +#else +typedef void* NativeEvent; +#endif + +} // namespace base + +#endif // BASE_EVENT_TYPES_H
diff --git a/src/base/file_descriptor_posix.h b/src/base/file_descriptor_posix.h new file mode 100644 index 0000000..abc0789 --- /dev/null +++ b/src/base/file_descriptor_posix.h
@@ -0,0 +1,45 @@ +// Copyright (c) 2006-2009 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_FILE_DESCRIPTOR_POSIX_H_ +#define BASE_FILE_DESCRIPTOR_POSIX_H_ + +namespace base { + +// ----------------------------------------------------------------------------- +// We introduct a special structure for file descriptors in order that we are +// able to use template specialisation to special-case their handling. +// +// WARNING: (Chromium only) There are subtleties to consider if serialising +// these objects over IPC. See comments in ipc/ipc_message_utils.h +// above the template specialisation for this structure. +// ----------------------------------------------------------------------------- +struct FileDescriptor { + FileDescriptor() + : fd(-1), + auto_close(false) { } + + FileDescriptor(int ifd, bool iauto_close) + : fd(ifd), + auto_close(iauto_close) { } + + bool operator==(const FileDescriptor& other) const { + return (fd == other.fd && auto_close == other.auto_close); + } + + // A comparison operator so that we can use these as keys in a std::map. + bool operator<(const FileDescriptor& other) const { + return other.fd < fd; + } + + int fd; + // If true, this file descriptor should be closed after it has been used. For + // example an IPC system might interpret this flag as indicating that the + // file descriptor it has been given should be closed after use. + bool auto_close; +}; + +} // namespace base + +#endif // BASE_FILE_DESCRIPTOR_POSIX_H_
diff --git a/src/base/file_path.cc b/src/base/file_path.cc new file mode 100644 index 0000000..acada54 --- /dev/null +++ b/src/base/file_path.cc
@@ -0,0 +1,1264 @@ +// 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_path.h" + +#include <string.h> +#include <algorithm> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/pickle.h" + +// These includes are just for the *Hack functions, and should be removed +// when those functions are removed. +#include "base/string_piece.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" + +#if defined(OS_MACOSX) +#include "base/mac/scoped_cftyperef.h" +#include "base/third_party/icu/icu_utf.h" +#endif + +#if defined(OS_WIN) +#include <windows.h> +#elif defined(OS_MACOSX) +#include <CoreFoundation/CoreFoundation.h> +#endif + +#if defined(FILE_PATH_USES_WIN_SEPARATORS) +const FilePath::CharType FilePath::kSeparators[] = FILE_PATH_LITERAL("\\/"); +#else // FILE_PATH_USES_WIN_SEPARATORS +const FilePath::CharType FilePath::kSeparators[] = FILE_PATH_LITERAL("/"); +#endif // FILE_PATH_USES_WIN_SEPARATORS + +const FilePath::CharType FilePath::kCurrentDirectory[] = FILE_PATH_LITERAL("."); +const FilePath::CharType FilePath::kParentDirectory[] = FILE_PATH_LITERAL(".."); + +const FilePath::CharType FilePath::kExtensionSeparator = FILE_PATH_LITERAL('.'); + +typedef FilePath::StringType StringType; + +namespace { + +#if defined(FILE_PATH_USES_WIN_SEPARATORS) && defined(OS_POSIX) +inline bool StartsWith(const std::string& str, + const std::string& search, + bool case_sensitive) { + return StartsWithASCII(str, search, case_sensitive); +} +#endif + +const char* kCommonDoubleExtensionSuffixes[] = { "gz", "z", "bz2" }; +const char* kCommonDoubleExtensions[] = { "user.js" }; + +const FilePath::CharType kStringTerminator = FILE_PATH_LITERAL('\0'); + +// If this FilePath contains a drive letter specification, returns the +// position of the last character of the drive letter specification, +// otherwise returns npos. This can only be true on Windows, when a pathname +// begins with a letter followed by a colon. On other platforms, this always +// returns npos. +StringType::size_type FindDriveLetter(const StringType& path) { +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + // This is dependent on an ASCII-based character set, but that's a + // reasonable assumption. iswalpha can be too inclusive here. + if (path.length() >= 2 && path[1] == L':' && + ((path[0] >= L'A' && path[0] <= L'Z') || + (path[0] >= L'a' && path[0] <= L'z'))) { + return 1; + } +#endif // FILE_PATH_USES_DRIVE_LETTERS + return StringType::npos; +} + +#if defined(FILE_PATH_USES_DRIVE_LETTERS) +bool EqualDriveLetterCaseInsensitive(const StringType& a, + const StringType& b) { + size_t a_letter_pos = FindDriveLetter(a); + size_t b_letter_pos = FindDriveLetter(b); + + if (a_letter_pos == StringType::npos || b_letter_pos == StringType::npos) + return a == b; + + StringType a_letter(a.substr(0, a_letter_pos + 1)); + StringType b_letter(b.substr(0, b_letter_pos + 1)); + if (!StartsWith(a_letter, b_letter, false)) + return false; + + StringType a_rest(a.substr(a_letter_pos + 1)); + StringType b_rest(b.substr(b_letter_pos + 1)); + return a_rest == b_rest; +} +#endif // defined(FILE_PATH_USES_DRIVE_LETTERS) + +bool IsPathAbsolute(const StringType& path) { +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + StringType::size_type letter = FindDriveLetter(path); + if (letter != StringType::npos) { + // Look for a separator right after the drive specification. + return path.length() > letter + 1 && + FilePath::IsSeparator(path[letter + 1]); + } + // Look for a pair of leading separators. + return path.length() > 1 && + FilePath::IsSeparator(path[0]) && FilePath::IsSeparator(path[1]); +#else // FILE_PATH_USES_DRIVE_LETTERS + // Look for a separator in the first position. + return path.length() > 0 && FilePath::IsSeparator(path[0]); +#endif // FILE_PATH_USES_DRIVE_LETTERS +} + +bool AreAllSeparators(const StringType& input) { + for (StringType::const_iterator it = input.begin(); + it != input.end(); ++it) { + if (!FilePath::IsSeparator(*it)) + return false; + } + + return true; +} + +// 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) { + // Special case "." and ".." + if (path == FilePath::kCurrentDirectory || path == FilePath::kParentDirectory) + return StringType::npos; + + const StringType::size_type last_dot = + path.rfind(FilePath::kExtensionSeparator); + + // No extension, or the extension is the whole filename. + if (last_dot == StringType::npos || last_dot == 0U) + return last_dot; + + const StringType::size_type penultimate_dot = + path.rfind(FilePath::kExtensionSeparator, last_dot - 1); + const StringType::size_type last_separator = + path.find_last_of(FilePath::kSeparators, last_dot - 1, + arraysize(FilePath::kSeparators) - 1); + + if (penultimate_dot == StringType::npos || + (last_separator != StringType::npos && + penultimate_dot < last_separator)) { + return last_dot; + } + + for (size_t i = 0; i < arraysize(kCommonDoubleExtensions); ++i) { + StringType extension(path, penultimate_dot + 1); + if (LowerCaseEqualsASCII(extension, kCommonDoubleExtensions[i])) + return penultimate_dot; + } + + StringType extension(path, last_dot + 1); + for (size_t i = 0; i < arraysize(kCommonDoubleExtensionSuffixes); ++i) { + if (LowerCaseEqualsASCII(extension, kCommonDoubleExtensionSuffixes[i])) { + if ((last_dot - penultimate_dot) <= 5U && + (last_dot - penultimate_dot) > 1U) { + return penultimate_dot; + } + } + } + + return last_dot; +} + +// Returns true if path is "", ".", or "..". +bool IsEmptyOrSpecialCase(const StringType& path) { + // Special cases "", ".", and ".." + if (path.empty() || path == FilePath::kCurrentDirectory || + path == FilePath::kParentDirectory) { + return true; + } + + return false; +} + +} // namespace + +FilePath::FilePath() { +} + +FilePath::FilePath(const FilePath& that) : path_(that.path_) { +} + +FilePath::FilePath(const StringType& path) : path_(path) { + StringType::size_type nul_pos = path_.find(kStringTerminator); + if (nul_pos != StringType::npos) + path_.erase(nul_pos, StringType::npos); +} + +FilePath::~FilePath() { +} + +FilePath& FilePath::operator=(const FilePath& that) { + path_ = that.path_; + return *this; +} + +bool FilePath::operator==(const FilePath& that) const { +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + return EqualDriveLetterCaseInsensitive(this->path_, that.path_); +#else // defined(FILE_PATH_USES_DRIVE_LETTERS) + return path_ == that.path_; +#endif // defined(FILE_PATH_USES_DRIVE_LETTERS) +} + +bool FilePath::operator!=(const FilePath& that) const { +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + return !EqualDriveLetterCaseInsensitive(this->path_, that.path_); +#else // defined(FILE_PATH_USES_DRIVE_LETTERS) + return path_ != that.path_; +#endif // defined(FILE_PATH_USES_DRIVE_LETTERS) +} + +// static +bool FilePath::IsSeparator(CharType character) { + for (size_t i = 0; i < arraysize(kSeparators) - 1; ++i) { + if (character == kSeparators[i]) { + return true; + } + } + + return false; +} + +void FilePath::GetComponents(std::vector<StringType>* components) const { + DCHECK(components); + if (!components) + return; + components->clear(); + if (value().empty()) + return; + + std::vector<StringType> ret_val; + FilePath current = *this; + FilePath base; + + // Capture path components. + while (current != current.DirName()) { + base = current.BaseName(); + if (!AreAllSeparators(base.value())) + ret_val.push_back(base.value()); + current = current.DirName(); + } + + // Capture root, if any. + base = current.BaseName(); + if (!base.value().empty() && base.value() != kCurrentDirectory) + ret_val.push_back(current.BaseName().value()); + + // Capture drive letter, if any. + FilePath dir = current.DirName(); + StringType::size_type letter = FindDriveLetter(dir.value()); + if (letter != StringType::npos) { + ret_val.push_back(StringType(dir.value(), 0, letter + 1)); + } + + *components = std::vector<StringType>(ret_val.rbegin(), ret_val.rend()); +} + +bool FilePath::IsParent(const FilePath& child) const { + return AppendRelativePath(child, NULL); +} + +bool FilePath::AppendRelativePath(const FilePath& child, + FilePath* path) const { + std::vector<StringType> parent_components; + std::vector<StringType> child_components; + GetComponents(&parent_components); + child.GetComponents(&child_components); + + if (parent_components.empty() || + parent_components.size() >= child_components.size()) + return false; + + std::vector<StringType>::const_iterator parent_comp = + parent_components.begin(); + std::vector<StringType>::const_iterator child_comp = + child_components.begin(); + +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + // Windows can access case sensitive filesystems, so component + // comparisions must be case sensitive, but drive letters are + // never case sensitive. + if ((FindDriveLetter(*parent_comp) != StringType::npos) && + (FindDriveLetter(*child_comp) != StringType::npos)) { + if (!StartsWith(*parent_comp, *child_comp, false)) + return false; + ++parent_comp; + ++child_comp; + } +#endif // defined(FILE_PATH_USES_DRIVE_LETTERS) + + while (parent_comp != parent_components.end()) { + if (*parent_comp != *child_comp) + return false; + ++parent_comp; + ++child_comp; + } + + if (path != NULL) { + for (; child_comp != child_components.end(); ++child_comp) { + *path = path->Append(*child_comp); + } + } + return true; +} + +// libgen's dirname and basename aren't guaranteed to be thread-safe and aren't +// guaranteed to not modify their input strings, and in fact are implemented +// differently in this regard on different platforms. Don't use them, but +// adhere to their behavior. +FilePath FilePath::DirName() const { + FilePath new_path(path_); + new_path.StripTrailingSeparatorsInternal(); + + // The drive letter, if any, always needs to remain in the output. If there + // is no drive letter, as will always be the case on platforms which do not + // support drive letters, letter will be npos, or -1, so the comparisons and + // resizes below using letter will still be valid. + StringType::size_type letter = FindDriveLetter(new_path.path_); + + StringType::size_type last_separator = + new_path.path_.find_last_of(kSeparators, StringType::npos, + arraysize(kSeparators) - 1); + if (last_separator == StringType::npos) { + // path_ is in the current directory. + new_path.path_.resize(letter + 1); + } else if (last_separator == letter + 1) { + // path_ is in the root directory. + new_path.path_.resize(letter + 2); + } else if (last_separator == letter + 2 && + IsSeparator(new_path.path_[letter + 1])) { + // path_ is in "//" (possibly with a drive letter); leave the double + // separator intact indicating alternate root. + new_path.path_.resize(letter + 3); + } else if (last_separator != 0) { + // path_ is somewhere else, trim the basename. + new_path.path_.resize(last_separator); + } + + new_path.StripTrailingSeparatorsInternal(); + if (!new_path.path_.length()) + new_path.path_ = kCurrentDirectory; + + return new_path; +} + +FilePath FilePath::BaseName() const { + FilePath new_path(path_); + new_path.StripTrailingSeparatorsInternal(); + + // The drive letter, if any, is always stripped. + StringType::size_type letter = FindDriveLetter(new_path.path_); + if (letter != StringType::npos) { + new_path.path_.erase(0, letter + 1); + } + + // Keep everything after the final separator, but if the pathname is only + // one character and it's a separator, leave it alone. + StringType::size_type last_separator = + new_path.path_.find_last_of(kSeparators, StringType::npos, + arraysize(kSeparators) - 1); + if (last_separator != StringType::npos && + last_separator < new_path.path_.length() - 1) { + new_path.path_.erase(0, last_separator + 1); + } + + return new_path; +} + +StringType FilePath::Extension() const { + FilePath base(BaseName()); + const StringType::size_type dot = ExtensionSeparatorPosition(base.path_); + if (dot == StringType::npos) + return StringType(); + + return base.path_.substr(dot, StringType::npos); +} + +FilePath FilePath::RemoveExtension() const { + if (Extension().empty()) + return *this; + + const StringType::size_type dot = ExtensionSeparatorPosition(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_); + + if (IsEmptyOrSpecialCase(BaseName().value())) + return FilePath(); + + StringType ext = Extension(); + StringType ret = RemoveExtension().value(); + ret.append(suffix); + ret.append(ext); + return FilePath(ret); +} + +FilePath FilePath::InsertBeforeExtensionASCII(const base::StringPiece& suffix) + const { + DCHECK(IsStringASCII(suffix)); +#if defined(OS_WIN) + return InsertBeforeExtension(ASCIIToUTF16(suffix.as_string())); +#elif defined(OS_POSIX) || defined(OS_STARBOARD) + return InsertBeforeExtension(suffix.as_string()); +#endif +} + +FilePath FilePath::AddExtension(const StringType& extension) const { + if (IsEmptyOrSpecialCase(BaseName().value())) + return FilePath(); + + // If the new extension is "" or ".", then just return the current FilePath. + if (extension.empty() || extension == StringType(1, kExtensionSeparator)) + return *this; + + StringType str = path_; + if (extension[0] != kExtensionSeparator && + *(str.end() - 1) != kExtensionSeparator) { + str.append(1, kExtensionSeparator); + } + str.append(extension); + return FilePath(str); +} + +FilePath FilePath::ReplaceExtension(const StringType& extension) const { + if (IsEmptyOrSpecialCase(BaseName().value())) + return FilePath(); + + FilePath no_ext = RemoveExtension(); + // If the new extension is "" or ".", then just remove the current extension. + if (extension.empty() || extension == StringType(1, kExtensionSeparator)) + return no_ext; + + StringType str = no_ext.value(); + if (extension[0] != kExtensionSeparator) + str.append(1, kExtensionSeparator); + str.append(extension); + return FilePath(str); +} + +bool FilePath::MatchesExtension(const StringType& extension) const { + DCHECK(extension.empty() || extension[0] == kExtensionSeparator); + + StringType current_extension = Extension(); + + if (current_extension.length() != extension.length()) + return false; + + return FilePath::CompareEqualIgnoreCase(extension, current_extension); +} + +FilePath FilePath::Append(const StringType& component) const { + const StringType* appended = &component; + StringType without_nuls; + + StringType::size_type nul_pos = component.find(kStringTerminator); + if (nul_pos != StringType::npos) { + without_nuls = component.substr(0, nul_pos); + appended = &without_nuls; + } + + DCHECK(!IsPathAbsolute(*appended)); + + if (path_.compare(kCurrentDirectory) == 0) { + // Append normally doesn't do any normalization, but as a special case, + // when appending to kCurrentDirectory, just return a new path for the + // component argument. Appending component to kCurrentDirectory would + // serve no purpose other than needlessly lengthening the path, and + // it's likely in practice to wind up with FilePath objects containing + // only kCurrentDirectory when calling DirName on a single relative path + // component. + return FilePath(*appended); + } + + FilePath new_path(path_); + new_path.StripTrailingSeparatorsInternal(); + + // Don't append a separator if the path is empty (indicating the current + // directory) or if the path component is empty (indicating nothing to + // append). + if (appended->length() > 0 && new_path.path_.length() > 0) { + // Don't append a separator if the path still ends with a trailing + // separator after stripping (indicating the root directory). + if (!IsSeparator(new_path.path_[new_path.path_.length() - 1])) { + // Don't append a separator if the path is just a drive letter. + if (FindDriveLetter(new_path.path_) + 1 != new_path.path_.length()) { + new_path.path_.append(1, kSeparators[0]); + } + } + } + + new_path.path_.append(*appended); + return new_path; +} + +FilePath FilePath::Append(const FilePath& component) const { + return Append(component.value()); +} + +FilePath FilePath::AppendASCII(const base::StringPiece& component) const { + DCHECK(IsStringASCII(component)); +#if defined(OS_WIN) + return Append(ASCIIToUTF16(component.as_string())); +#elif defined(OS_POSIX) || defined(OS_STARBOARD) + return Append(component.as_string()); +#endif +} + +bool FilePath::IsAbsolute() const { + return IsPathAbsolute(path_); +} + +FilePath FilePath::StripTrailingSeparators() const { + FilePath new_path(path_); + new_path.StripTrailingSeparatorsInternal(); + + return new_path; +} + +bool FilePath::ReferencesParent() const { + std::vector<StringType> components; + GetComponents(&components); + + std::vector<StringType>::const_iterator it = components.begin(); + for (; it != components.end(); ++it) { + const StringType& component = *it; + if (component == kParentDirectory) + return true; + } + return false; +} + +#if defined(OS_POSIX) || defined(OS_STARBOARD) +// See file_path.h for a discussion of the encoding of paths on POSIX +// platforms. These encoding conversion functions are not quite correct. + +string16 FilePath::LossyDisplayName() const { + return WideToUTF16(base::SysNativeMBToWide(path_)); +} + +std::string FilePath::MaybeAsASCII() const { + if (IsStringASCII(path_)) + return path_; + return ""; +} + +std::string FilePath::AsUTF8Unsafe() const { +#if defined(OS_MACOSX) || defined(OS_CHROMEOS) + return value(); +#else + return WideToUTF8(base::SysNativeMBToWide(value())); +#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(base::SysWideToNativeMB(wstring)); +} + +// static +FilePath FilePath::FromUTF8Unsafe(const std::string& utf8) { +#if defined(OS_MACOSX) || defined(OS_CHROMEOS) + return FilePath(utf8); +#else + return FilePath(base::SysWideToNativeMB(UTF8ToWide(utf8))); +#endif +} + +#elif defined(OS_WIN) +string16 FilePath::LossyDisplayName() const { + return path_; +} + +std::string FilePath::MaybeAsASCII() const { + if (IsStringASCII(path_)) + return WideToASCII(path_); + return ""; +} + +std::string FilePath::AsUTF8Unsafe() const { + return WideToUTF8(value()); +} + +// static +FilePath FilePath::FromWStringHack(const std::wstring& wstring) { + return FilePath(wstring); +} + +// static +FilePath FilePath::FromUTF8Unsafe(const std::string& utf8) { + return FilePath(UTF8ToWide(utf8)); +} +#endif + +void FilePath::WriteToPickle(Pickle* pickle) const { +#if defined(OS_WIN) + pickle->WriteString16(path_); +#else + pickle->WriteString(path_); +#endif +} + +bool FilePath::ReadFromPickle(PickleIterator* iter) { +#if defined(OS_WIN) + if (!iter->ReadString16(&path_)) + return false; +#else + if (!iter->ReadString(&path_)) + return false; +#endif + + if (path_.find(kStringTerminator) != StringType::npos) + return false; + + return true; +} + +#if defined(OS_WIN) +// Windows specific implementation of file string comparisons + +int FilePath::CompareIgnoreCase(const StringType& string1, + const StringType& string2) { + // Perform character-wise upper case comparison rather than using the + // fully Unicode-aware CompareString(). For details see: + // http://blogs.msdn.com/michkap/archive/2005/10/17/481600.aspx + StringType::const_iterator i1 = string1.begin(); + StringType::const_iterator i2 = string2.begin(); + StringType::const_iterator string1end = string1.end(); + StringType::const_iterator string2end = string2.end(); + for ( ; i1 != string1end && i2 != string2end; ++i1, ++i2) { + wchar_t c1 = (wchar_t)LOWORD(::CharUpperW((LPWSTR)MAKELONG(*i1, 0))); + wchar_t c2 = (wchar_t)LOWORD(::CharUpperW((LPWSTR)MAKELONG(*i2, 0))); + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + } + if (i1 != string1end) + return 1; + if (i2 != string2end) + return -1; + return 0; +} + +#elif defined(OS_MACOSX) +// Mac OS X specific implementation of file string comparisons + +// cf. http://developer.apple.com/mac/library/technotes/tn/tn1150.html#UnicodeSubtleties +// +// "When using CreateTextEncoding to create a text encoding, you should set +// the TextEncodingBase to kTextEncodingUnicodeV2_0, set the +// TextEncodingVariant to kUnicodeCanonicalDecompVariant, and set the +// TextEncodingFormat to kUnicode16BitFormat. Using these values ensures that +// the Unicode will be in the same form as on an HFS Plus volume, even as the +// Unicode standard evolves." +// +// Another technical article for X 10.4 updates this: one should use +// the new (unambiguous) kUnicodeHFSPlusDecompVariant. +// cf. http://developer.apple.com/mac/library/releasenotes/TextFonts/RN-TEC/index.html +// +// This implementation uses CFStringGetFileSystemRepresentation() to get the +// decomposed form, and an adapted version of the FastUnicodeCompare as +// described in the tech note to compare the strings. + +// Character conversion table for FastUnicodeCompare() +// +// The lower case table consists of a 256-entry high-byte table followed by +// some number of 256-entry subtables. The high-byte table contains either an +// offset to the subtable for characters with that high byte or zero, which +// means that there are no case mappings or ignored characters in that block. +// Ignored characters are mapped to zero. +// +// cf. downloadable file linked in +// http://developer.apple.com/mac/library/technotes/tn/tn1150.html#StringComparisonAlgorithm + +namespace { + +const UInt16 lower_case_table[] = { + // High-byte indices ( == 0 iff no case mapping and no ignorables ) + + /* 0 */ 0x0100, 0x0200, 0x0000, 0x0300, 0x0400, 0x0500, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 1 */ 0x0600, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 2 */ 0x0700, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 3 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 4 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 5 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 6 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 7 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 8 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 9 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* A */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* B */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* C */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* D */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* E */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* F */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0900, 0x0A00, + + // Table 1 (for high byte 0x00) + + /* 0 */ 0xFFFF, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, + /* 1 */ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, + /* 2 */ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, + /* 3 */ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, + /* 4 */ 0x0040, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, + /* 5 */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, + /* 6 */ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, + /* 7 */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F, + /* 8 */ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, + /* 9 */ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + /* A */ 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + /* B */ 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, + /* C */ 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00E6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + /* D */ 0x00F0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, + 0x00F8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00FE, 0x00DF, + /* E */ 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + /* F */ 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, + 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, + + // Table 2 (for high byte 0x01) + + /* 0 */ 0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, + 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D, 0x010E, 0x010F, + /* 1 */ 0x0111, 0x0111, 0x0112, 0x0113, 0x0114, 0x0115, 0x0116, 0x0117, + 0x0118, 0x0119, 0x011A, 0x011B, 0x011C, 0x011D, 0x011E, 0x011F, + /* 2 */ 0x0120, 0x0121, 0x0122, 0x0123, 0x0124, 0x0125, 0x0127, 0x0127, + 0x0128, 0x0129, 0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F, + /* 3 */ 0x0130, 0x0131, 0x0133, 0x0133, 0x0134, 0x0135, 0x0136, 0x0137, + 0x0138, 0x0139, 0x013A, 0x013B, 0x013C, 0x013D, 0x013E, 0x0140, + /* 4 */ 0x0140, 0x0142, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0147, + 0x0148, 0x0149, 0x014B, 0x014B, 0x014C, 0x014D, 0x014E, 0x014F, + /* 5 */ 0x0150, 0x0151, 0x0153, 0x0153, 0x0154, 0x0155, 0x0156, 0x0157, + 0x0158, 0x0159, 0x015A, 0x015B, 0x015C, 0x015D, 0x015E, 0x015F, + /* 6 */ 0x0160, 0x0161, 0x0162, 0x0163, 0x0164, 0x0165, 0x0167, 0x0167, + 0x0168, 0x0169, 0x016A, 0x016B, 0x016C, 0x016D, 0x016E, 0x016F, + /* 7 */ 0x0170, 0x0171, 0x0172, 0x0173, 0x0174, 0x0175, 0x0176, 0x0177, + 0x0178, 0x0179, 0x017A, 0x017B, 0x017C, 0x017D, 0x017E, 0x017F, + /* 8 */ 0x0180, 0x0253, 0x0183, 0x0183, 0x0185, 0x0185, 0x0254, 0x0188, + 0x0188, 0x0256, 0x0257, 0x018C, 0x018C, 0x018D, 0x01DD, 0x0259, + /* 9 */ 0x025B, 0x0192, 0x0192, 0x0260, 0x0263, 0x0195, 0x0269, 0x0268, + 0x0199, 0x0199, 0x019A, 0x019B, 0x026F, 0x0272, 0x019E, 0x0275, + /* A */ 0x01A0, 0x01A1, 0x01A3, 0x01A3, 0x01A5, 0x01A5, 0x01A6, 0x01A8, + 0x01A8, 0x0283, 0x01AA, 0x01AB, 0x01AD, 0x01AD, 0x0288, 0x01AF, + /* B */ 0x01B0, 0x028A, 0x028B, 0x01B4, 0x01B4, 0x01B6, 0x01B6, 0x0292, + 0x01B9, 0x01B9, 0x01BA, 0x01BB, 0x01BD, 0x01BD, 0x01BE, 0x01BF, + /* C */ 0x01C0, 0x01C1, 0x01C2, 0x01C3, 0x01C6, 0x01C6, 0x01C6, 0x01C9, + 0x01C9, 0x01C9, 0x01CC, 0x01CC, 0x01CC, 0x01CD, 0x01CE, 0x01CF, + /* D */ 0x01D0, 0x01D1, 0x01D2, 0x01D3, 0x01D4, 0x01D5, 0x01D6, 0x01D7, + 0x01D8, 0x01D9, 0x01DA, 0x01DB, 0x01DC, 0x01DD, 0x01DE, 0x01DF, + /* E */ 0x01E0, 0x01E1, 0x01E2, 0x01E3, 0x01E5, 0x01E5, 0x01E6, 0x01E7, + 0x01E8, 0x01E9, 0x01EA, 0x01EB, 0x01EC, 0x01ED, 0x01EE, 0x01EF, + /* F */ 0x01F0, 0x01F3, 0x01F3, 0x01F3, 0x01F4, 0x01F5, 0x01F6, 0x01F7, + 0x01F8, 0x01F9, 0x01FA, 0x01FB, 0x01FC, 0x01FD, 0x01FE, 0x01FF, + + // Table 3 (for high byte 0x03) + + /* 0 */ 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307, + 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x030F, + /* 1 */ 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317, + 0x0318, 0x0319, 0x031A, 0x031B, 0x031C, 0x031D, 0x031E, 0x031F, + /* 2 */ 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327, + 0x0328, 0x0329, 0x032A, 0x032B, 0x032C, 0x032D, 0x032E, 0x032F, + /* 3 */ 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337, + 0x0338, 0x0339, 0x033A, 0x033B, 0x033C, 0x033D, 0x033E, 0x033F, + /* 4 */ 0x0340, 0x0341, 0x0342, 0x0343, 0x0344, 0x0345, 0x0346, 0x0347, + 0x0348, 0x0349, 0x034A, 0x034B, 0x034C, 0x034D, 0x034E, 0x034F, + /* 5 */ 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357, + 0x0358, 0x0359, 0x035A, 0x035B, 0x035C, 0x035D, 0x035E, 0x035F, + /* 6 */ 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367, + 0x0368, 0x0369, 0x036A, 0x036B, 0x036C, 0x036D, 0x036E, 0x036F, + /* 7 */ 0x0370, 0x0371, 0x0372, 0x0373, 0x0374, 0x0375, 0x0376, 0x0377, + 0x0378, 0x0379, 0x037A, 0x037B, 0x037C, 0x037D, 0x037E, 0x037F, + /* 8 */ 0x0380, 0x0381, 0x0382, 0x0383, 0x0384, 0x0385, 0x0386, 0x0387, + 0x0388, 0x0389, 0x038A, 0x038B, 0x038C, 0x038D, 0x038E, 0x038F, + /* 9 */ 0x0390, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, + 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, + /* A */ 0x03C0, 0x03C1, 0x03A2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7, + 0x03C8, 0x03C9, 0x03AA, 0x03AB, 0x03AC, 0x03AD, 0x03AE, 0x03AF, + /* B */ 0x03B0, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, + 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, + /* C */ 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7, + 0x03C8, 0x03C9, 0x03CA, 0x03CB, 0x03CC, 0x03CD, 0x03CE, 0x03CF, + /* D */ 0x03D0, 0x03D1, 0x03D2, 0x03D3, 0x03D4, 0x03D5, 0x03D6, 0x03D7, + 0x03D8, 0x03D9, 0x03DA, 0x03DB, 0x03DC, 0x03DD, 0x03DE, 0x03DF, + /* E */ 0x03E0, 0x03E1, 0x03E3, 0x03E3, 0x03E5, 0x03E5, 0x03E7, 0x03E7, + 0x03E9, 0x03E9, 0x03EB, 0x03EB, 0x03ED, 0x03ED, 0x03EF, 0x03EF, + /* F */ 0x03F0, 0x03F1, 0x03F2, 0x03F3, 0x03F4, 0x03F5, 0x03F6, 0x03F7, + 0x03F8, 0x03F9, 0x03FA, 0x03FB, 0x03FC, 0x03FD, 0x03FE, 0x03FF, + + // Table 4 (for high byte 0x04) + + /* 0 */ 0x0400, 0x0401, 0x0452, 0x0403, 0x0454, 0x0455, 0x0456, 0x0407, + 0x0458, 0x0459, 0x045A, 0x045B, 0x040C, 0x040D, 0x040E, 0x045F, + /* 1 */ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0419, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + /* 2 */ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, + /* 3 */ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + /* 4 */ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, + /* 5 */ 0x0450, 0x0451, 0x0452, 0x0453, 0x0454, 0x0455, 0x0456, 0x0457, + 0x0458, 0x0459, 0x045A, 0x045B, 0x045C, 0x045D, 0x045E, 0x045F, + /* 6 */ 0x0461, 0x0461, 0x0463, 0x0463, 0x0465, 0x0465, 0x0467, 0x0467, + 0x0469, 0x0469, 0x046B, 0x046B, 0x046D, 0x046D, 0x046F, 0x046F, + /* 7 */ 0x0471, 0x0471, 0x0473, 0x0473, 0x0475, 0x0475, 0x0476, 0x0477, + 0x0479, 0x0479, 0x047B, 0x047B, 0x047D, 0x047D, 0x047F, 0x047F, + /* 8 */ 0x0481, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, + 0x0488, 0x0489, 0x048A, 0x048B, 0x048C, 0x048D, 0x048E, 0x048F, + /* 9 */ 0x0491, 0x0491, 0x0493, 0x0493, 0x0495, 0x0495, 0x0497, 0x0497, + 0x0499, 0x0499, 0x049B, 0x049B, 0x049D, 0x049D, 0x049F, 0x049F, + /* A */ 0x04A1, 0x04A1, 0x04A3, 0x04A3, 0x04A5, 0x04A5, 0x04A7, 0x04A7, + 0x04A9, 0x04A9, 0x04AB, 0x04AB, 0x04AD, 0x04AD, 0x04AF, 0x04AF, + /* B */ 0x04B1, 0x04B1, 0x04B3, 0x04B3, 0x04B5, 0x04B5, 0x04B7, 0x04B7, + 0x04B9, 0x04B9, 0x04BB, 0x04BB, 0x04BD, 0x04BD, 0x04BF, 0x04BF, + /* C */ 0x04C0, 0x04C1, 0x04C2, 0x04C4, 0x04C4, 0x04C5, 0x04C6, 0x04C8, + 0x04C8, 0x04C9, 0x04CA, 0x04CC, 0x04CC, 0x04CD, 0x04CE, 0x04CF, + /* D */ 0x04D0, 0x04D1, 0x04D2, 0x04D3, 0x04D4, 0x04D5, 0x04D6, 0x04D7, + 0x04D8, 0x04D9, 0x04DA, 0x04DB, 0x04DC, 0x04DD, 0x04DE, 0x04DF, + /* E */ 0x04E0, 0x04E1, 0x04E2, 0x04E3, 0x04E4, 0x04E5, 0x04E6, 0x04E7, + 0x04E8, 0x04E9, 0x04EA, 0x04EB, 0x04EC, 0x04ED, 0x04EE, 0x04EF, + /* F */ 0x04F0, 0x04F1, 0x04F2, 0x04F3, 0x04F4, 0x04F5, 0x04F6, 0x04F7, + 0x04F8, 0x04F9, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF, + + // Table 5 (for high byte 0x05) + + /* 0 */ 0x0500, 0x0501, 0x0502, 0x0503, 0x0504, 0x0505, 0x0506, 0x0507, + 0x0508, 0x0509, 0x050A, 0x050B, 0x050C, 0x050D, 0x050E, 0x050F, + /* 1 */ 0x0510, 0x0511, 0x0512, 0x0513, 0x0514, 0x0515, 0x0516, 0x0517, + 0x0518, 0x0519, 0x051A, 0x051B, 0x051C, 0x051D, 0x051E, 0x051F, + /* 2 */ 0x0520, 0x0521, 0x0522, 0x0523, 0x0524, 0x0525, 0x0526, 0x0527, + 0x0528, 0x0529, 0x052A, 0x052B, 0x052C, 0x052D, 0x052E, 0x052F, + /* 3 */ 0x0530, 0x0561, 0x0562, 0x0563, 0x0564, 0x0565, 0x0566, 0x0567, + 0x0568, 0x0569, 0x056A, 0x056B, 0x056C, 0x056D, 0x056E, 0x056F, + /* 4 */ 0x0570, 0x0571, 0x0572, 0x0573, 0x0574, 0x0575, 0x0576, 0x0577, + 0x0578, 0x0579, 0x057A, 0x057B, 0x057C, 0x057D, 0x057E, 0x057F, + /* 5 */ 0x0580, 0x0581, 0x0582, 0x0583, 0x0584, 0x0585, 0x0586, 0x0557, + 0x0558, 0x0559, 0x055A, 0x055B, 0x055C, 0x055D, 0x055E, 0x055F, + /* 6 */ 0x0560, 0x0561, 0x0562, 0x0563, 0x0564, 0x0565, 0x0566, 0x0567, + 0x0568, 0x0569, 0x056A, 0x056B, 0x056C, 0x056D, 0x056E, 0x056F, + /* 7 */ 0x0570, 0x0571, 0x0572, 0x0573, 0x0574, 0x0575, 0x0576, 0x0577, + 0x0578, 0x0579, 0x057A, 0x057B, 0x057C, 0x057D, 0x057E, 0x057F, + /* 8 */ 0x0580, 0x0581, 0x0582, 0x0583, 0x0584, 0x0585, 0x0586, 0x0587, + 0x0588, 0x0589, 0x058A, 0x058B, 0x058C, 0x058D, 0x058E, 0x058F, + /* 9 */ 0x0590, 0x0591, 0x0592, 0x0593, 0x0594, 0x0595, 0x0596, 0x0597, + 0x0598, 0x0599, 0x059A, 0x059B, 0x059C, 0x059D, 0x059E, 0x059F, + /* A */ 0x05A0, 0x05A1, 0x05A2, 0x05A3, 0x05A4, 0x05A5, 0x05A6, 0x05A7, + 0x05A8, 0x05A9, 0x05AA, 0x05AB, 0x05AC, 0x05AD, 0x05AE, 0x05AF, + /* B */ 0x05B0, 0x05B1, 0x05B2, 0x05B3, 0x05B4, 0x05B5, 0x05B6, 0x05B7, + 0x05B8, 0x05B9, 0x05BA, 0x05BB, 0x05BC, 0x05BD, 0x05BE, 0x05BF, + /* C */ 0x05C0, 0x05C1, 0x05C2, 0x05C3, 0x05C4, 0x05C5, 0x05C6, 0x05C7, + 0x05C8, 0x05C9, 0x05CA, 0x05CB, 0x05CC, 0x05CD, 0x05CE, 0x05CF, + /* D */ 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, + 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF, + /* E */ 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7, + 0x05E8, 0x05E9, 0x05EA, 0x05EB, 0x05EC, 0x05ED, 0x05EE, 0x05EF, + /* F */ 0x05F0, 0x05F1, 0x05F2, 0x05F3, 0x05F4, 0x05F5, 0x05F6, 0x05F7, + 0x05F8, 0x05F9, 0x05FA, 0x05FB, 0x05FC, 0x05FD, 0x05FE, 0x05FF, + + // Table 6 (for high byte 0x10) + + /* 0 */ 0x1000, 0x1001, 0x1002, 0x1003, 0x1004, 0x1005, 0x1006, 0x1007, + 0x1008, 0x1009, 0x100A, 0x100B, 0x100C, 0x100D, 0x100E, 0x100F, + /* 1 */ 0x1010, 0x1011, 0x1012, 0x1013, 0x1014, 0x1015, 0x1016, 0x1017, + 0x1018, 0x1019, 0x101A, 0x101B, 0x101C, 0x101D, 0x101E, 0x101F, + /* 2 */ 0x1020, 0x1021, 0x1022, 0x1023, 0x1024, 0x1025, 0x1026, 0x1027, + 0x1028, 0x1029, 0x102A, 0x102B, 0x102C, 0x102D, 0x102E, 0x102F, + /* 3 */ 0x1030, 0x1031, 0x1032, 0x1033, 0x1034, 0x1035, 0x1036, 0x1037, + 0x1038, 0x1039, 0x103A, 0x103B, 0x103C, 0x103D, 0x103E, 0x103F, + /* 4 */ 0x1040, 0x1041, 0x1042, 0x1043, 0x1044, 0x1045, 0x1046, 0x1047, + 0x1048, 0x1049, 0x104A, 0x104B, 0x104C, 0x104D, 0x104E, 0x104F, + /* 5 */ 0x1050, 0x1051, 0x1052, 0x1053, 0x1054, 0x1055, 0x1056, 0x1057, + 0x1058, 0x1059, 0x105A, 0x105B, 0x105C, 0x105D, 0x105E, 0x105F, + /* 6 */ 0x1060, 0x1061, 0x1062, 0x1063, 0x1064, 0x1065, 0x1066, 0x1067, + 0x1068, 0x1069, 0x106A, 0x106B, 0x106C, 0x106D, 0x106E, 0x106F, + /* 7 */ 0x1070, 0x1071, 0x1072, 0x1073, 0x1074, 0x1075, 0x1076, 0x1077, + 0x1078, 0x1079, 0x107A, 0x107B, 0x107C, 0x107D, 0x107E, 0x107F, + /* 8 */ 0x1080, 0x1081, 0x1082, 0x1083, 0x1084, 0x1085, 0x1086, 0x1087, + 0x1088, 0x1089, 0x108A, 0x108B, 0x108C, 0x108D, 0x108E, 0x108F, + /* 9 */ 0x1090, 0x1091, 0x1092, 0x1093, 0x1094, 0x1095, 0x1096, 0x1097, + 0x1098, 0x1099, 0x109A, 0x109B, 0x109C, 0x109D, 0x109E, 0x109F, + /* A */ 0x10D0, 0x10D1, 0x10D2, 0x10D3, 0x10D4, 0x10D5, 0x10D6, 0x10D7, + 0x10D8, 0x10D9, 0x10DA, 0x10DB, 0x10DC, 0x10DD, 0x10DE, 0x10DF, + /* B */ 0x10E0, 0x10E1, 0x10E2, 0x10E3, 0x10E4, 0x10E5, 0x10E6, 0x10E7, + 0x10E8, 0x10E9, 0x10EA, 0x10EB, 0x10EC, 0x10ED, 0x10EE, 0x10EF, + /* C */ 0x10F0, 0x10F1, 0x10F2, 0x10F3, 0x10F4, 0x10F5, 0x10C6, 0x10C7, + 0x10C8, 0x10C9, 0x10CA, 0x10CB, 0x10CC, 0x10CD, 0x10CE, 0x10CF, + /* D */ 0x10D0, 0x10D1, 0x10D2, 0x10D3, 0x10D4, 0x10D5, 0x10D6, 0x10D7, + 0x10D8, 0x10D9, 0x10DA, 0x10DB, 0x10DC, 0x10DD, 0x10DE, 0x10DF, + /* E */ 0x10E0, 0x10E1, 0x10E2, 0x10E3, 0x10E4, 0x10E5, 0x10E6, 0x10E7, + 0x10E8, 0x10E9, 0x10EA, 0x10EB, 0x10EC, 0x10ED, 0x10EE, 0x10EF, + /* F */ 0x10F0, 0x10F1, 0x10F2, 0x10F3, 0x10F4, 0x10F5, 0x10F6, 0x10F7, + 0x10F8, 0x10F9, 0x10FA, 0x10FB, 0x10FC, 0x10FD, 0x10FE, 0x10FF, + + // Table 7 (for high byte 0x20) + + /* 0 */ 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, + 0x2008, 0x2009, 0x200A, 0x200B, 0x0000, 0x0000, 0x0000, 0x0000, + /* 1 */ 0x2010, 0x2011, 0x2012, 0x2013, 0x2014, 0x2015, 0x2016, 0x2017, + 0x2018, 0x2019, 0x201A, 0x201B, 0x201C, 0x201D, 0x201E, 0x201F, + /* 2 */ 0x2020, 0x2021, 0x2022, 0x2023, 0x2024, 0x2025, 0x2026, 0x2027, + 0x2028, 0x2029, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x202F, + /* 3 */ 0x2030, 0x2031, 0x2032, 0x2033, 0x2034, 0x2035, 0x2036, 0x2037, + 0x2038, 0x2039, 0x203A, 0x203B, 0x203C, 0x203D, 0x203E, 0x203F, + /* 4 */ 0x2040, 0x2041, 0x2042, 0x2043, 0x2044, 0x2045, 0x2046, 0x2047, + 0x2048, 0x2049, 0x204A, 0x204B, 0x204C, 0x204D, 0x204E, 0x204F, + /* 5 */ 0x2050, 0x2051, 0x2052, 0x2053, 0x2054, 0x2055, 0x2056, 0x2057, + 0x2058, 0x2059, 0x205A, 0x205B, 0x205C, 0x205D, 0x205E, 0x205F, + /* 6 */ 0x2060, 0x2061, 0x2062, 0x2063, 0x2064, 0x2065, 0x2066, 0x2067, + 0x2068, 0x2069, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* 7 */ 0x2070, 0x2071, 0x2072, 0x2073, 0x2074, 0x2075, 0x2076, 0x2077, + 0x2078, 0x2079, 0x207A, 0x207B, 0x207C, 0x207D, 0x207E, 0x207F, + /* 8 */ 0x2080, 0x2081, 0x2082, 0x2083, 0x2084, 0x2085, 0x2086, 0x2087, + 0x2088, 0x2089, 0x208A, 0x208B, 0x208C, 0x208D, 0x208E, 0x208F, + /* 9 */ 0x2090, 0x2091, 0x2092, 0x2093, 0x2094, 0x2095, 0x2096, 0x2097, + 0x2098, 0x2099, 0x209A, 0x209B, 0x209C, 0x209D, 0x209E, 0x209F, + /* A */ 0x20A0, 0x20A1, 0x20A2, 0x20A3, 0x20A4, 0x20A5, 0x20A6, 0x20A7, + 0x20A8, 0x20A9, 0x20AA, 0x20AB, 0x20AC, 0x20AD, 0x20AE, 0x20AF, + /* B */ 0x20B0, 0x20B1, 0x20B2, 0x20B3, 0x20B4, 0x20B5, 0x20B6, 0x20B7, + 0x20B8, 0x20B9, 0x20BA, 0x20BB, 0x20BC, 0x20BD, 0x20BE, 0x20BF, + /* C */ 0x20C0, 0x20C1, 0x20C2, 0x20C3, 0x20C4, 0x20C5, 0x20C6, 0x20C7, + 0x20C8, 0x20C9, 0x20CA, 0x20CB, 0x20CC, 0x20CD, 0x20CE, 0x20CF, + /* D */ 0x20D0, 0x20D1, 0x20D2, 0x20D3, 0x20D4, 0x20D5, 0x20D6, 0x20D7, + 0x20D8, 0x20D9, 0x20DA, 0x20DB, 0x20DC, 0x20DD, 0x20DE, 0x20DF, + /* E */ 0x20E0, 0x20E1, 0x20E2, 0x20E3, 0x20E4, 0x20E5, 0x20E6, 0x20E7, + 0x20E8, 0x20E9, 0x20EA, 0x20EB, 0x20EC, 0x20ED, 0x20EE, 0x20EF, + /* F */ 0x20F0, 0x20F1, 0x20F2, 0x20F3, 0x20F4, 0x20F5, 0x20F6, 0x20F7, + 0x20F8, 0x20F9, 0x20FA, 0x20FB, 0x20FC, 0x20FD, 0x20FE, 0x20FF, + + // Table 8 (for high byte 0x21) + + /* 0 */ 0x2100, 0x2101, 0x2102, 0x2103, 0x2104, 0x2105, 0x2106, 0x2107, + 0x2108, 0x2109, 0x210A, 0x210B, 0x210C, 0x210D, 0x210E, 0x210F, + /* 1 */ 0x2110, 0x2111, 0x2112, 0x2113, 0x2114, 0x2115, 0x2116, 0x2117, + 0x2118, 0x2119, 0x211A, 0x211B, 0x211C, 0x211D, 0x211E, 0x211F, + /* 2 */ 0x2120, 0x2121, 0x2122, 0x2123, 0x2124, 0x2125, 0x2126, 0x2127, + 0x2128, 0x2129, 0x212A, 0x212B, 0x212C, 0x212D, 0x212E, 0x212F, + /* 3 */ 0x2130, 0x2131, 0x2132, 0x2133, 0x2134, 0x2135, 0x2136, 0x2137, + 0x2138, 0x2139, 0x213A, 0x213B, 0x213C, 0x213D, 0x213E, 0x213F, + /* 4 */ 0x2140, 0x2141, 0x2142, 0x2143, 0x2144, 0x2145, 0x2146, 0x2147, + 0x2148, 0x2149, 0x214A, 0x214B, 0x214C, 0x214D, 0x214E, 0x214F, + /* 5 */ 0x2150, 0x2151, 0x2152, 0x2153, 0x2154, 0x2155, 0x2156, 0x2157, + 0x2158, 0x2159, 0x215A, 0x215B, 0x215C, 0x215D, 0x215E, 0x215F, + /* 6 */ 0x2170, 0x2171, 0x2172, 0x2173, 0x2174, 0x2175, 0x2176, 0x2177, + 0x2178, 0x2179, 0x217A, 0x217B, 0x217C, 0x217D, 0x217E, 0x217F, + /* 7 */ 0x2170, 0x2171, 0x2172, 0x2173, 0x2174, 0x2175, 0x2176, 0x2177, + 0x2178, 0x2179, 0x217A, 0x217B, 0x217C, 0x217D, 0x217E, 0x217F, + /* 8 */ 0x2180, 0x2181, 0x2182, 0x2183, 0x2184, 0x2185, 0x2186, 0x2187, + 0x2188, 0x2189, 0x218A, 0x218B, 0x218C, 0x218D, 0x218E, 0x218F, + /* 9 */ 0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x2196, 0x2197, + 0x2198, 0x2199, 0x219A, 0x219B, 0x219C, 0x219D, 0x219E, 0x219F, + /* A */ 0x21A0, 0x21A1, 0x21A2, 0x21A3, 0x21A4, 0x21A5, 0x21A6, 0x21A7, + 0x21A8, 0x21A9, 0x21AA, 0x21AB, 0x21AC, 0x21AD, 0x21AE, 0x21AF, + /* B */ 0x21B0, 0x21B1, 0x21B2, 0x21B3, 0x21B4, 0x21B5, 0x21B6, 0x21B7, + 0x21B8, 0x21B9, 0x21BA, 0x21BB, 0x21BC, 0x21BD, 0x21BE, 0x21BF, + /* C */ 0x21C0, 0x21C1, 0x21C2, 0x21C3, 0x21C4, 0x21C5, 0x21C6, 0x21C7, + 0x21C8, 0x21C9, 0x21CA, 0x21CB, 0x21CC, 0x21CD, 0x21CE, 0x21CF, + /* D */ 0x21D0, 0x21D1, 0x21D2, 0x21D3, 0x21D4, 0x21D5, 0x21D6, 0x21D7, + 0x21D8, 0x21D9, 0x21DA, 0x21DB, 0x21DC, 0x21DD, 0x21DE, 0x21DF, + /* E */ 0x21E0, 0x21E1, 0x21E2, 0x21E3, 0x21E4, 0x21E5, 0x21E6, 0x21E7, + 0x21E8, 0x21E9, 0x21EA, 0x21EB, 0x21EC, 0x21ED, 0x21EE, 0x21EF, + /* F */ 0x21F0, 0x21F1, 0x21F2, 0x21F3, 0x21F4, 0x21F5, 0x21F6, 0x21F7, + 0x21F8, 0x21F9, 0x21FA, 0x21FB, 0x21FC, 0x21FD, 0x21FE, 0x21FF, + + // Table 9 (for high byte 0xFE) + + /* 0 */ 0xFE00, 0xFE01, 0xFE02, 0xFE03, 0xFE04, 0xFE05, 0xFE06, 0xFE07, + 0xFE08, 0xFE09, 0xFE0A, 0xFE0B, 0xFE0C, 0xFE0D, 0xFE0E, 0xFE0F, + /* 1 */ 0xFE10, 0xFE11, 0xFE12, 0xFE13, 0xFE14, 0xFE15, 0xFE16, 0xFE17, + 0xFE18, 0xFE19, 0xFE1A, 0xFE1B, 0xFE1C, 0xFE1D, 0xFE1E, 0xFE1F, + /* 2 */ 0xFE20, 0xFE21, 0xFE22, 0xFE23, 0xFE24, 0xFE25, 0xFE26, 0xFE27, + 0xFE28, 0xFE29, 0xFE2A, 0xFE2B, 0xFE2C, 0xFE2D, 0xFE2E, 0xFE2F, + /* 3 */ 0xFE30, 0xFE31, 0xFE32, 0xFE33, 0xFE34, 0xFE35, 0xFE36, 0xFE37, + 0xFE38, 0xFE39, 0xFE3A, 0xFE3B, 0xFE3C, 0xFE3D, 0xFE3E, 0xFE3F, + /* 4 */ 0xFE40, 0xFE41, 0xFE42, 0xFE43, 0xFE44, 0xFE45, 0xFE46, 0xFE47, + 0xFE48, 0xFE49, 0xFE4A, 0xFE4B, 0xFE4C, 0xFE4D, 0xFE4E, 0xFE4F, + /* 5 */ 0xFE50, 0xFE51, 0xFE52, 0xFE53, 0xFE54, 0xFE55, 0xFE56, 0xFE57, + 0xFE58, 0xFE59, 0xFE5A, 0xFE5B, 0xFE5C, 0xFE5D, 0xFE5E, 0xFE5F, + /* 6 */ 0xFE60, 0xFE61, 0xFE62, 0xFE63, 0xFE64, 0xFE65, 0xFE66, 0xFE67, + 0xFE68, 0xFE69, 0xFE6A, 0xFE6B, 0xFE6C, 0xFE6D, 0xFE6E, 0xFE6F, + /* 7 */ 0xFE70, 0xFE71, 0xFE72, 0xFE73, 0xFE74, 0xFE75, 0xFE76, 0xFE77, + 0xFE78, 0xFE79, 0xFE7A, 0xFE7B, 0xFE7C, 0xFE7D, 0xFE7E, 0xFE7F, + /* 8 */ 0xFE80, 0xFE81, 0xFE82, 0xFE83, 0xFE84, 0xFE85, 0xFE86, 0xFE87, + 0xFE88, 0xFE89, 0xFE8A, 0xFE8B, 0xFE8C, 0xFE8D, 0xFE8E, 0xFE8F, + /* 9 */ 0xFE90, 0xFE91, 0xFE92, 0xFE93, 0xFE94, 0xFE95, 0xFE96, 0xFE97, + 0xFE98, 0xFE99, 0xFE9A, 0xFE9B, 0xFE9C, 0xFE9D, 0xFE9E, 0xFE9F, + /* A */ 0xFEA0, 0xFEA1, 0xFEA2, 0xFEA3, 0xFEA4, 0xFEA5, 0xFEA6, 0xFEA7, + 0xFEA8, 0xFEA9, 0xFEAA, 0xFEAB, 0xFEAC, 0xFEAD, 0xFEAE, 0xFEAF, + /* B */ 0xFEB0, 0xFEB1, 0xFEB2, 0xFEB3, 0xFEB4, 0xFEB5, 0xFEB6, 0xFEB7, + 0xFEB8, 0xFEB9, 0xFEBA, 0xFEBB, 0xFEBC, 0xFEBD, 0xFEBE, 0xFEBF, + /* C */ 0xFEC0, 0xFEC1, 0xFEC2, 0xFEC3, 0xFEC4, 0xFEC5, 0xFEC6, 0xFEC7, + 0xFEC8, 0xFEC9, 0xFECA, 0xFECB, 0xFECC, 0xFECD, 0xFECE, 0xFECF, + /* D */ 0xFED0, 0xFED1, 0xFED2, 0xFED3, 0xFED4, 0xFED5, 0xFED6, 0xFED7, + 0xFED8, 0xFED9, 0xFEDA, 0xFEDB, 0xFEDC, 0xFEDD, 0xFEDE, 0xFEDF, + /* E */ 0xFEE0, 0xFEE1, 0xFEE2, 0xFEE3, 0xFEE4, 0xFEE5, 0xFEE6, 0xFEE7, + 0xFEE8, 0xFEE9, 0xFEEA, 0xFEEB, 0xFEEC, 0xFEED, 0xFEEE, 0xFEEF, + /* F */ 0xFEF0, 0xFEF1, 0xFEF2, 0xFEF3, 0xFEF4, 0xFEF5, 0xFEF6, 0xFEF7, + 0xFEF8, 0xFEF9, 0xFEFA, 0xFEFB, 0xFEFC, 0xFEFD, 0xFEFE, 0x0000, + + // Table 10 (for high byte 0xFF) + + /* 0 */ 0xFF00, 0xFF01, 0xFF02, 0xFF03, 0xFF04, 0xFF05, 0xFF06, 0xFF07, + 0xFF08, 0xFF09, 0xFF0A, 0xFF0B, 0xFF0C, 0xFF0D, 0xFF0E, 0xFF0F, + /* 1 */ 0xFF10, 0xFF11, 0xFF12, 0xFF13, 0xFF14, 0xFF15, 0xFF16, 0xFF17, + 0xFF18, 0xFF19, 0xFF1A, 0xFF1B, 0xFF1C, 0xFF1D, 0xFF1E, 0xFF1F, + /* 2 */ 0xFF20, 0xFF41, 0xFF42, 0xFF43, 0xFF44, 0xFF45, 0xFF46, 0xFF47, + 0xFF48, 0xFF49, 0xFF4A, 0xFF4B, 0xFF4C, 0xFF4D, 0xFF4E, 0xFF4F, + /* 3 */ 0xFF50, 0xFF51, 0xFF52, 0xFF53, 0xFF54, 0xFF55, 0xFF56, 0xFF57, + 0xFF58, 0xFF59, 0xFF5A, 0xFF3B, 0xFF3C, 0xFF3D, 0xFF3E, 0xFF3F, + /* 4 */ 0xFF40, 0xFF41, 0xFF42, 0xFF43, 0xFF44, 0xFF45, 0xFF46, 0xFF47, + 0xFF48, 0xFF49, 0xFF4A, 0xFF4B, 0xFF4C, 0xFF4D, 0xFF4E, 0xFF4F, + /* 5 */ 0xFF50, 0xFF51, 0xFF52, 0xFF53, 0xFF54, 0xFF55, 0xFF56, 0xFF57, + 0xFF58, 0xFF59, 0xFF5A, 0xFF5B, 0xFF5C, 0xFF5D, 0xFF5E, 0xFF5F, + /* 6 */ 0xFF60, 0xFF61, 0xFF62, 0xFF63, 0xFF64, 0xFF65, 0xFF66, 0xFF67, + 0xFF68, 0xFF69, 0xFF6A, 0xFF6B, 0xFF6C, 0xFF6D, 0xFF6E, 0xFF6F, + /* 7 */ 0xFF70, 0xFF71, 0xFF72, 0xFF73, 0xFF74, 0xFF75, 0xFF76, 0xFF77, + 0xFF78, 0xFF79, 0xFF7A, 0xFF7B, 0xFF7C, 0xFF7D, 0xFF7E, 0xFF7F, + /* 8 */ 0xFF80, 0xFF81, 0xFF82, 0xFF83, 0xFF84, 0xFF85, 0xFF86, 0xFF87, + 0xFF88, 0xFF89, 0xFF8A, 0xFF8B, 0xFF8C, 0xFF8D, 0xFF8E, 0xFF8F, + /* 9 */ 0xFF90, 0xFF91, 0xFF92, 0xFF93, 0xFF94, 0xFF95, 0xFF96, 0xFF97, + 0xFF98, 0xFF99, 0xFF9A, 0xFF9B, 0xFF9C, 0xFF9D, 0xFF9E, 0xFF9F, + /* A */ 0xFFA0, 0xFFA1, 0xFFA2, 0xFFA3, 0xFFA4, 0xFFA5, 0xFFA6, 0xFFA7, + 0xFFA8, 0xFFA9, 0xFFAA, 0xFFAB, 0xFFAC, 0xFFAD, 0xFFAE, 0xFFAF, + /* B */ 0xFFB0, 0xFFB1, 0xFFB2, 0xFFB3, 0xFFB4, 0xFFB5, 0xFFB6, 0xFFB7, + 0xFFB8, 0xFFB9, 0xFFBA, 0xFFBB, 0xFFBC, 0xFFBD, 0xFFBE, 0xFFBF, + /* C */ 0xFFC0, 0xFFC1, 0xFFC2, 0xFFC3, 0xFFC4, 0xFFC5, 0xFFC6, 0xFFC7, + 0xFFC8, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCF, + /* D */ 0xFFD0, 0xFFD1, 0xFFD2, 0xFFD3, 0xFFD4, 0xFFD5, 0xFFD6, 0xFFD7, + 0xFFD8, 0xFFD9, 0xFFDA, 0xFFDB, 0xFFDC, 0xFFDD, 0xFFDE, 0xFFDF, + /* E */ 0xFFE0, 0xFFE1, 0xFFE2, 0xFFE3, 0xFFE4, 0xFFE5, 0xFFE6, 0xFFE7, + 0xFFE8, 0xFFE9, 0xFFEA, 0xFFEB, 0xFFEC, 0xFFED, 0xFFEE, 0xFFEF, + /* F */ 0xFFF0, 0xFFF1, 0xFFF2, 0xFFF3, 0xFFF4, 0xFFF5, 0xFFF6, 0xFFF7, + 0xFFF8, 0xFFF9, 0xFFFA, 0xFFFB, 0xFFFC, 0xFFFD, 0xFFFE, 0xFFFF, +}; + +// Returns the next non-ignorable codepoint within string starting from the +// position indicated by index, or zero if there are no more. +// The passed-in index is automatically advanced as the characters in the input +// HFS-decomposed UTF-8 strings are read. +inline int HFSReadNextNonIgnorableCodepoint(const char* string, + int length, + int* index) { + int codepoint = 0; + while (*index < length && codepoint == 0) { + // CBU8_NEXT returns a value < 0 in error cases. For purposes of string + // comparison, we just use that value and flag it with DCHECK. + CBU8_NEXT(string, *index, length, codepoint); + DCHECK_GT(codepoint, 0); + if (codepoint > 0) { + // Check if there is a subtable for this upper byte. + int lookup_offset = lower_case_table[codepoint >> 8]; + if (lookup_offset != 0) + codepoint = lower_case_table[lookup_offset + (codepoint & 0x00FF)]; + // Note: codepoint1 may be again 0 at this point if the character was + // an ignorable. + } + } + return codepoint; +} + +} // anonymous namespace + +// Special UTF-8 version of FastUnicodeCompare. Cf: +// http://developer.apple.com/mac/library/technotes/tn/tn1150.html#StringComparisonAlgorithm +// The input strings must be in the special HFS decomposed form. +int FilePath::HFSFastUnicodeCompare(const StringType& string1, + const StringType& string2) { + int length1 = string1.length(); + int length2 = string2.length(); + int index1 = 0; + int index2 = 0; + + for (;;) { + int codepoint1 = HFSReadNextNonIgnorableCodepoint(string1.c_str(), + length1, + &index1); + int codepoint2 = HFSReadNextNonIgnorableCodepoint(string2.c_str(), + length2, + &index2); + if (codepoint1 != codepoint2) + return (codepoint1 < codepoint2) ? -1 : 1; + if (codepoint1 == 0) { + DCHECK_EQ(index1, length1); + DCHECK_EQ(index2, length2); + return 0; + } + } +} + +StringType FilePath::GetHFSDecomposedForm(const StringType& string) { + base::mac::ScopedCFTypeRef<CFStringRef> cfstring( + CFStringCreateWithBytesNoCopy( + NULL, + reinterpret_cast<const UInt8*>(string.c_str()), + string.length(), + kCFStringEncodingUTF8, + false, + kCFAllocatorNull)); + // Query the maximum length needed to store the result. In most cases this + // will overestimate the required space. The return value also already + // includes the space needed for a terminating 0. + CFIndex length = CFStringGetMaximumSizeOfFileSystemRepresentation(cfstring); + DCHECK_GT(length, 0); // should be at least 1 for the 0-terminator. + // Reserve enough space for CFStringGetFileSystemRepresentation to write into. + // Also set the length to the maximum so that we can shrink it later. + // (Increasing rather than decreasing it would clobber the string contents!) + StringType result; + result.reserve(length); + result.resize(length - 1); + Boolean success = CFStringGetFileSystemRepresentation(cfstring, + &result[0], + length); + if (success) { + // Reduce result.length() to actual string length. + result.resize(strlen(result.c_str())); + } else { + // An error occurred -> clear result. + result.clear(); + } + return result; +} + +int FilePath::CompareIgnoreCase(const StringType& string1, + const StringType& string2) { + // Quick checks for empty strings - these speed things up a bit and make the + // following code cleaner. + if (string1.empty()) + return string2.empty() ? 0 : -1; + if (string2.empty()) + return 1; + + StringType hfs1 = GetHFSDecomposedForm(string1); + StringType hfs2 = GetHFSDecomposedForm(string2); + + // GetHFSDecomposedForm() returns an empty string in an error case. + if (hfs1.empty() || hfs2.empty()) { + NOTREACHED(); + base::mac::ScopedCFTypeRef<CFStringRef> cfstring1( + CFStringCreateWithBytesNoCopy( + NULL, + reinterpret_cast<const UInt8*>(string1.c_str()), + string1.length(), + kCFStringEncodingUTF8, + false, + kCFAllocatorNull)); + base::mac::ScopedCFTypeRef<CFStringRef> cfstring2( + CFStringCreateWithBytesNoCopy( + NULL, + reinterpret_cast<const UInt8*>(string2.c_str()), + string2.length(), + kCFStringEncodingUTF8, + false, + kCFAllocatorNull)); + return CFStringCompare(cfstring1, + cfstring2, + kCFCompareCaseInsensitive); + } + + return HFSFastUnicodeCompare(hfs1, hfs2); +} + +#else // << WIN. MACOSX | other (POSIX) >> + +// Generic (POSIX) implementation of file string comparison. +// TODO(rolandsteiner) check if this is sufficient/correct. +int FilePath::CompareIgnoreCase(const StringType& string1, + const StringType& string2) { + int comparison = strcasecmp(string1.c_str(), string2.c_str()); + if (comparison < 0) + return -1; + if (comparison > 0) + return 1; + return 0; +} + +#endif // OS versions of CompareIgnoreCase() + + +void FilePath::StripTrailingSeparatorsInternal() { + // If there is no drive letter, start will be 1, which will prevent stripping + // the leading separator if there is only one separator. If there is a drive + // letter, start will be set appropriately to prevent stripping the first + // separator following the drive letter, if a separator immediately follows + // the drive letter. + StringType::size_type start = FindDriveLetter(path_) + 2; + + StringType::size_type last_stripped = StringType::npos; + for (StringType::size_type pos = path_.length(); + pos > start && IsSeparator(path_[pos - 1]); + --pos) { + // If the string only has two separators and they're at the beginning, + // don't strip them, unless the string began with more than two separators. + if (pos != start + 1 || last_stripped == start + 2 || + !IsSeparator(path_[start - 1])) { + path_.resize(pos - 1); + last_stripped = pos; + } + } +} + +FilePath FilePath::NormalizePathSeparators() const { +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + StringType copy = path_; + for (size_t i = 1; i < arraysize(kSeparators); ++i) { + std::replace(copy.begin(), copy.end(), kSeparators[i], kSeparators[0]); + } + return FilePath(copy); +#else + return *this; +#endif +} + +void PrintTo(const FilePath& path, std::ostream* out) { + *out << path.value(); +}
diff --git a/src/base/file_path.h b/src/base/file_path.h new file mode 100644 index 0000000..d1eb74c --- /dev/null +++ b/src/base/file_path.h
@@ -0,0 +1,437 @@ +// 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. + +// FilePath is a container for pathnames stored in a platform's native string +// type, providing containers for manipulation in according with the +// platform's conventions for pathnames. It supports the following path +// types: +// +// POSIX Windows +// --------------- ---------------------------------- +// Fundamental type char[] wchar_t[] +// Encoding unspecified* UTF-16 +// Separator / \, tolerant of / +// Drive letters no case-insensitive A-Z followed by : +// Alternate root // (surprise!) \\, for UNC paths +// +// * The encoding need not be specified on POSIX systems, although some +// POSIX-compliant systems do specify an encoding. Mac OS X uses UTF-8. +// Chrome OS also uses UTF-8. +// Linux does not specify an encoding, but in practice, the locale's +// character set may be used. +// +// For more arcane bits of path trivia, see below. +// +// FilePath objects are intended to be used anywhere paths are. An +// application may pass FilePath objects around internally, masking the +// underlying differences between systems, only differing in implementation +// where interfacing directly with the system. For example, a single +// OpenFile(const FilePath &) function may be made available, allowing all +// callers to operate without regard to the underlying implementation. On +// POSIX-like platforms, OpenFile might wrap fopen, and on Windows, it might +// wrap _wfopen_s, perhaps both by calling file_path.value().c_str(). This +// allows each platform to pass pathnames around without requiring conversions +// between encodings, which has an impact on performance, but more imporantly, +// has an impact on correctness on platforms that do not have well-defined +// encodings for pathnames. +// +// Several methods are available to perform common operations on a FilePath +// object, such as determining the parent directory (DirName), isolating the +// final path component (BaseName), and appending a relative pathname string +// to an existing FilePath object (Append). These methods are highly +// recommended over attempting to split and concatenate strings directly. +// These methods are based purely on string manipulation and knowledge of +// platform-specific pathname conventions, and do not consult the filesystem +// at all, making them safe to use without fear of blocking on I/O operations. +// These methods do not function as mutators but instead return distinct +// instances of FilePath objects, and are therefore safe to use on const +// objects. The objects themselves are safe to share between threads. +// +// To aid in initialization of FilePath objects from string literals, a +// FILE_PATH_LITERAL macro is provided, which accounts for the difference +// between char[]-based pathnames on POSIX systems and wchar_t[]-based +// pathnames on Windows. +// +// Paths can't contain NULs as a precaution agaist premature truncation. +// +// Because a FilePath object should not be instantiated at the global scope, +// instead, use a FilePath::CharType[] and initialize it with +// FILE_PATH_LITERAL. At runtime, a FilePath object can be created from the +// character array. Example: +// +// | const FilePath::CharType kLogFileName[] = FILE_PATH_LITERAL("log.txt"); +// | +// | void Function() { +// | FilePath log_file_path(kLogFileName); +// | [...] +// | } +// +// WARNING: FilePaths should ALWAYS be displayed with LTR directionality, even +// when the UI language is RTL. This means you always need to pass filepaths +// through base::i18n::WrapPathWithLTRFormatting() before displaying it in the +// RTL UI. +// +// This is a very common source of bugs, please try to keep this in mind. +// +// ARCANE BITS OF PATH TRIVIA +// +// - A double leading slash is actually part of the POSIX standard. Systems +// are allowed to treat // as an alternate root, as Windows does for UNC +// (network share) paths. Most POSIX systems don't do anything special +// with two leading slashes, but FilePath handles this case properly +// in case it ever comes across such a system. FilePath needs this support +// for Windows UNC paths, anyway. +// References: +// The Open Group Base Specifications Issue 7, sections 3.266 ("Pathname") +// and 4.12 ("Pathname Resolution"), available at: +// http://www.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_266 +// http://www.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_12 +// +// - Windows treats c:\\ the same way it treats \\. This was intended to +// allow older applications that require drive letters to support UNC paths +// like \\server\share\path, by permitting c:\\server\share\path as an +// equivalent. Since the OS treats these paths specially, FilePath needs +// to do the same. Since Windows can use either / or \ as the separator, +// FilePath treats c://, c:\\, //, and \\ all equivalently. +// Reference: +// The Old New Thing, "Why is a drive letter permitted in front of UNC +// paths (sometimes)?", available at: +// http://blogs.msdn.com/oldnewthing/archive/2005/11/22/495740.aspx + +#ifndef BASE_FILE_PATH_H_ +#define BASE_FILE_PATH_H_ + +#include <stddef.h> +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/hash_tables.h" +#include "base/string16.h" +#include "base/string_piece.h" // For implicit conversions. +#include "build/build_config.h" + +// Windows-style drive letter support and pathname separator characters can be +// enabled and disabled independently, to aid testing. These #defines are +// here so that the same setting can be used in both the implementation and +// in the unit test. +#if defined(OS_WIN) || defined(__LB_XB1__) || defined(__LB_XB360__) +#define FILE_PATH_USES_DRIVE_LETTERS +#define FILE_PATH_USES_WIN_SEPARATORS +#endif // OS_WIN || __LB_XB1__ || __LB_XB360__ + +class Pickle; +class PickleIterator; + +// An abstraction to isolate users from the differences between native +// pathnames on different platforms. +class BASE_EXPORT FilePath { + public: +#if defined(OS_POSIX) || defined(OS_STARBOARD) + // On most platforms, native pathnames are char arrays, and the encoding + // may or may not be specified. On Mac OS X, native pathnames are encoded + // in UTF-8. + typedef std::string StringType; +#elif defined(OS_WIN) + // On Windows, for Unicode-aware applications, native pathnames are wchar_t + // arrays encoded in UTF-16. + typedef std::wstring StringType; +#endif // OS_WIN + + typedef StringType::value_type CharType; + + // Null-terminated array of separators used to separate components in + // hierarchical paths. Each character in this array is a valid separator, + // but kSeparators[0] is treated as the canonical separator and will be used + // when composing pathnames. + static const CharType kSeparators[]; + + // A special path component meaning "this directory." + static const CharType kCurrentDirectory[]; + + // A special path component meaning "the parent directory." + static const CharType kParentDirectory[]; + + // The character used to identify a file extension. + static const CharType kExtensionSeparator; + + FilePath(); + FilePath(const FilePath& that); + explicit FilePath(const StringType& path); + ~FilePath(); + FilePath& operator=(const FilePath& that); + + bool operator==(const FilePath& that) const; + + bool operator!=(const FilePath& that) const; + + // Required for some STL containers and operations + bool operator<(const FilePath& that) const { + return path_ < that.path_; + } + + const StringType& value() const { return path_; } + + bool empty() const { return path_.empty(); } + + void clear() { path_.clear(); } + + // Returns true if |character| is in kSeparators. + static bool IsSeparator(CharType character); + + // Returns a vector of all of the components of the provided path. It is + // equivalent to calling DirName().value() on the path's root component, + // and BaseName().value() on each child component. + void GetComponents(std::vector<FilePath::StringType>* components) const; + + // Returns true if this FilePath is a strict parent of the |child|. Absolute + // and relative paths are accepted i.e. is /foo parent to /foo/bar and + // is foo parent to foo/bar. Does not convert paths to absolute, follow + // symlinks or directory navigation (e.g. ".."). A path is *NOT* its own + // parent. + bool IsParent(const FilePath& child) const; + + // If IsParent(child) holds, appends to path (if non-NULL) the + // relative path to child and returns true. For example, if parent + // holds "/Users/johndoe/Library/Application Support", child holds + // "/Users/johndoe/Library/Application Support/Google/Chrome/Default", and + // *path holds "/Users/johndoe/Library/Caches", then after + // parent.AppendRelativePath(child, path) is called *path will hold + // "/Users/johndoe/Library/Caches/Google/Chrome/Default". Otherwise, + // returns false. + bool AppendRelativePath(const FilePath& child, FilePath* path) const; + + // Returns a FilePath corresponding to the directory containing the path + // named by this object, stripping away the file component. If this object + // only contains one component, returns a FilePath identifying + // kCurrentDirectory. If this object already refers to the root directory, + // returns a FilePath identifying the root directory. + FilePath DirName() const WARN_UNUSED_RESULT; + + // Returns a FilePath corresponding to the last path component of this + // object, either a file or a directory. If this object already refers to + // the root directory, returns a FilePath identifying the root directory; + // this is the only situation in which BaseName will return an absolute path. + FilePath BaseName() const WARN_UNUSED_RESULT; + + // 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. + // 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 "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; + + // Inserts |suffix| after the file name portion of |path| but before the + // extension. Returns "" if BaseName() == "." or "..". + // Examples: + // path == "C:\pics\jojo.jpg" suffix == " (1)", returns "C:\pics\jojo (1).jpg" + // path == "jojo.jpg" suffix == " (1)", returns "jojo (1).jpg" + // path == "C:\pics\jojo" suffix == " (1)", returns "C:\pics\jojo (1)" + // path == "C:\pics.old\jojo" suffix == " (1)", returns "C:\pics.old\jojo (1)" + FilePath InsertBeforeExtension( + const StringType& suffix) const WARN_UNUSED_RESULT; + FilePath InsertBeforeExtensionASCII( + const base::StringPiece& suffix) const WARN_UNUSED_RESULT; + + // Adds |extension| to |file_name|. Returns the current FilePath if + // |extension| is empty. Returns "" if BaseName() == "." or "..". + FilePath AddExtension( + const StringType& extension) const WARN_UNUSED_RESULT; + + // Replaces the extension of |file_name| with |extension|. If |file_name| + // does not have an extension, then |extension| is added. If |extension| is + // empty, then the extension is removed from |file_name|. + // Returns "" if BaseName() == "." or "..". + FilePath ReplaceExtension( + const StringType& extension) const WARN_UNUSED_RESULT; + + // Returns true if the file path matches the specified extension. The test is + // case insensitive. Don't forget the leading period if appropriate. + bool MatchesExtension(const StringType& extension) const; + + // Returns a FilePath by appending a separator and the supplied path + // component to this object's path. Append takes care to avoid adding + // excessive separators if this object's path already ends with a separator. + // If this object's path is kCurrentDirectory, a new FilePath corresponding + // only to |component| is returned. |component| must be a relative path; + // it is an error to pass an absolute path. + FilePath Append(const StringType& component) const WARN_UNUSED_RESULT; + FilePath Append(const FilePath& component) const WARN_UNUSED_RESULT; + + // Although Windows StringType is std::wstring, since the encoding it uses for + // paths is well defined, it can handle ASCII path components as well. + // Mac uses UTF8, and since ASCII is a subset of that, it works there as well. + // On Linux, although it can use any 8-bit encoding for paths, we assume that + // ASCII is a valid subset, regardless of the encoding, since many operating + // system paths will always be ASCII. + FilePath AppendASCII(const base::StringPiece& component) + const WARN_UNUSED_RESULT; + + // Returns true if this FilePath contains an absolute path. On Windows, an + // absolute path begins with either a drive letter specification followed by + // a separator character, or with two separator characters. On POSIX + // platforms, an absolute path begins with a separator character. + bool IsAbsolute() const; + + // Returns a copy of this FilePath that does not end with a trailing + // separator. + FilePath StripTrailingSeparators() const WARN_UNUSED_RESULT; + + // Returns true if this FilePath contains any attempt to reference a parent + // directory (i.e. has a path component that is ".." + bool ReferencesParent() const; + + // Return a Unicode human-readable version of this path. + // Warning: you can *not*, in general, go from a display name back to a real + // path. Only use this when displaying paths to users, not just when you + // want to stuff a string16 into some other API. + string16 LossyDisplayName() const; + + // Return the path as ASCII, or the empty string if the path is not ASCII. + // This should only be used for cases where the FilePath is representing a + // known-ASCII filename. + std::string MaybeAsASCII() const; + + // Return the path as UTF-8. + // + // This function is *unsafe* as there is no way to tell what encoding is + // used in file names on POSIX systems other than Mac and Chrome OS, + // although UTF-8 is practically used everywhere these days. To mitigate + // the encoding issue, this function internally calls + // SysNativeMBToWide() on POSIX systems other than Mac and Chrome OS, + // per assumption that the current locale's encoding is used in file + // names, but this isn't a perfect solution. + // + // Once it becomes safe to to stop caring about non-UTF-8 file names, + // the SysNativeMBToWide() hack will be removed from the code, along + // with "Unsafe" in the function name. + std::string AsUTF8Unsafe() 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. + // + // Like AsUTF8Unsafe(), this function is unsafe. This function + // internally calls SysWideToNativeMB() on POSIX systems other than Mac + // and Chrome OS, to mitigate the encoding issue. See the comment at + // AsUTF8Unsafe() for details. + static FilePath FromUTF8Unsafe(const std::string& utf8); + + void WriteToPickle(Pickle* pickle) const; + bool ReadFromPickle(PickleIterator* iter); + + // Normalize all path separators to backslash on Windows + // (if FILE_PATH_USES_WIN_SEPARATORS is true), or do nothing on POSIX systems. + FilePath NormalizePathSeparators() const; + + // Compare two strings in the same way the file system does. + // Note that these always ignore case, even on file systems that are case- + // sensitive. If case-sensitive comparison is ever needed, add corresponding + // methods here. + // The methods are written as a static method so that they can also be used + // on parts of a file path, e.g., just the extension. + // CompareIgnoreCase() returns -1, 0 or 1 for less-than, equal-to and + // greater-than respectively. + static int CompareIgnoreCase(const StringType& string1, + const StringType& string2); + static bool CompareEqualIgnoreCase(const StringType& string1, + const StringType& string2) { + return CompareIgnoreCase(string1, string2) == 0; + } + static bool CompareLessIgnoreCase(const StringType& string1, + const StringType& string2) { + return CompareIgnoreCase(string1, string2) < 0; + } + +#if defined(OS_MACOSX) + // Returns the string in the special canonical decomposed form as defined for + // HFS, which is close to, but not quite, decomposition form D. See + // http://developer.apple.com/mac/library/technotes/tn/tn1150.html#UnicodeSubtleties + // for further comments. + // Returns the epmty string if the conversion failed. + static StringType GetHFSDecomposedForm(const FilePath::StringType& string); + + // Special UTF-8 version of FastUnicodeCompare. Cf: + // http://developer.apple.com/mac/library/technotes/tn/tn1150.html#StringComparisonAlgorithm + // IMPORTANT: The input strings must be in the special HFS decomposed form! + // (cf. above GetHFSDecomposedForm method) + static int HFSFastUnicodeCompare(const StringType& string1, + const StringType& string2); +#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 + // directory, so "////" will become "/", not "". A leading pair of + // separators is never stripped, to support alternate roots. This is used to + // support UNC paths on Windows. + void StripTrailingSeparatorsInternal(); + + StringType path_; +}; + +// This is required by googletest to print a readable output on test failures. +BASE_EXPORT extern void PrintTo(const FilePath& path, std::ostream* out); + +// Macros for string literal initialization of FilePath::CharType[], and for +// using a FilePath::CharType[] in a printf-style format string. +#if defined(OS_POSIX) || defined(OS_STARBOARD) +#define FILE_PATH_LITERAL(x) x +#define PRFilePath "s" +#define PRFilePathLiteral "%s" +#elif defined(OS_WIN) +#define FILE_PATH_LITERAL(x) L ## x +#define PRFilePath "ls" +#define PRFilePathLiteral L"%ls" +#endif // OS_WIN + +// Provide a hash function so that hash_sets and maps can contain FilePath +// objects. +namespace BASE_HASH_NAMESPACE { + +#if defined(BASE_HASH_USE_HASH_STRUCT) +template<> +struct hash<FilePath> { + size_t operator()(const FilePath& f) const { + return hash<FilePath::StringType>()(f.value()); + } +}; + +#else +inline size_t hash_value(const FilePath& f) { + return hash_value(f.value()); +} + +#endif // defined(BASE_HASH_USE_HASH_STRUCT) + +} // namespace BASE_HASH_NAMESPACE + +#endif // BASE_FILE_PATH_H_
diff --git a/src/base/file_path_unittest.cc b/src/base/file_path_unittest.cc new file mode 100644 index 0000000..b61d5d2 --- /dev/null +++ b/src/base/file_path_unittest.cc
@@ -0,0 +1,1213 @@ +// 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/basictypes.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +// This macro helps avoid wrapped lines in the test structs. +#define FPL(x) FILE_PATH_LITERAL(x) + +// This macro constructs strings which can contain NULs. +#define FPS(x) FilePath::StringType(FPL(x), arraysize(FPL(x)) - 1) + +struct UnaryTestData { + const FilePath::CharType* input; + const FilePath::CharType* expected; +}; + +struct UnaryBooleanTestData { + const FilePath::CharType* input; + bool expected; +}; + +struct BinaryTestData { + const FilePath::CharType* inputs[2]; + const FilePath::CharType* expected; +}; + +struct BinaryBooleanTestData { + const FilePath::CharType* inputs[2]; + bool expected; +}; + +struct BinaryIntTestData { + const FilePath::CharType* inputs[2]; + int expected; +}; + +struct UTF8TestData { + const FilePath::CharType* native; + const char* utf8; +}; + +// file_util winds up using autoreleased objects on the Mac, so this needs +// to be a PlatformTest +class FilePathTest : public PlatformTest { + protected: + virtual void SetUp() OVERRIDE { + PlatformTest::SetUp(); + } + virtual void TearDown() OVERRIDE { + PlatformTest::TearDown(); + } +}; + +TEST_F(FilePathTest, DirName) { + const struct UnaryTestData cases[] = { + { FPL(""), FPL(".") }, + { FPL("aa"), FPL(".") }, + { FPL("/aa/bb"), FPL("/aa") }, + { FPL("/aa/bb/"), FPL("/aa") }, + { FPL("/aa/bb//"), FPL("/aa") }, + { FPL("/aa/bb/ccc"), FPL("/aa/bb") }, + { FPL("/aa"), FPL("/") }, + { FPL("/aa/"), FPL("/") }, + { FPL("/"), FPL("/") }, + { FPL("//"), FPL("//") }, + { FPL("///"), FPL("/") }, + { FPL("aa/"), FPL(".") }, + { FPL("aa/bb"), FPL("aa") }, + { FPL("aa/bb/"), FPL("aa") }, + { FPL("aa/bb//"), FPL("aa") }, + { FPL("aa//bb//"), FPL("aa") }, + { FPL("aa//bb/"), FPL("aa") }, + { FPL("aa//bb"), FPL("aa") }, + { FPL("//aa/bb"), FPL("//aa") }, + { FPL("//aa/"), FPL("//") }, + { FPL("//aa"), FPL("//") }, + { FPL("0:"), FPL(".") }, + { FPL("@:"), FPL(".") }, + { FPL("[:"), FPL(".") }, + { FPL("`:"), FPL(".") }, + { FPL("{:"), FPL(".") }, + { FPL("\xB3:"), FPL(".") }, + { FPL("\xC5:"), FPL(".") }, +#if defined(OS_WIN) + { FPL("\x0143:"), FPL(".") }, +#endif // OS_WIN +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("c:"), FPL("c:") }, + { FPL("C:"), FPL("C:") }, + { FPL("A:"), FPL("A:") }, + { FPL("Z:"), FPL("Z:") }, + { FPL("a:"), FPL("a:") }, + { FPL("z:"), FPL("z:") }, + { FPL("c:aa"), FPL("c:") }, + { FPL("c:/"), FPL("c:/") }, + { FPL("c://"), FPL("c://") }, + { FPL("c:///"), FPL("c:/") }, + { FPL("c:/aa"), FPL("c:/") }, + { FPL("c:/aa/"), FPL("c:/") }, + { FPL("c:/aa/bb"), FPL("c:/aa") }, + { FPL("c:aa/bb"), FPL("c:aa") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("\\aa\\bb"), FPL("\\aa") }, + { FPL("\\aa\\bb\\"), FPL("\\aa") }, + { FPL("\\aa\\bb\\\\"), FPL("\\aa") }, + { FPL("\\aa\\bb\\ccc"), FPL("\\aa\\bb") }, + { FPL("\\aa"), FPL("\\") }, + { FPL("\\aa\\"), FPL("\\") }, + { FPL("\\"), FPL("\\") }, + { FPL("\\\\"), FPL("\\\\") }, + { FPL("\\\\\\"), FPL("\\") }, + { FPL("aa\\"), FPL(".") }, + { FPL("aa\\bb"), FPL("aa") }, + { FPL("aa\\bb\\"), FPL("aa") }, + { FPL("aa\\bb\\\\"), FPL("aa") }, + { FPL("aa\\\\bb\\\\"), FPL("aa") }, + { FPL("aa\\\\bb\\"), FPL("aa") }, + { FPL("aa\\\\bb"), FPL("aa") }, + { FPL("\\\\aa\\bb"), FPL("\\\\aa") }, + { FPL("\\\\aa\\"), FPL("\\\\") }, + { FPL("\\\\aa"), FPL("\\\\") }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("c:\\"), FPL("c:\\") }, + { FPL("c:\\\\"), FPL("c:\\\\") }, + { FPL("c:\\\\\\"), FPL("c:\\") }, + { FPL("c:\\aa"), FPL("c:\\") }, + { FPL("c:\\aa\\"), FPL("c:\\") }, + { FPL("c:\\aa\\bb"), FPL("c:\\aa") }, + { FPL("c:aa\\bb"), FPL("c:aa") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + FilePath observed = input.DirName(); + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed.value()) << + "i: " << i << ", input: " << input.value(); + } +} + +TEST_F(FilePathTest, BaseName) { + const struct UnaryTestData cases[] = { + { FPL(""), FPL("") }, + { FPL("aa"), FPL("aa") }, + { FPL("/aa/bb"), FPL("bb") }, + { FPL("/aa/bb/"), FPL("bb") }, + { FPL("/aa/bb//"), FPL("bb") }, + { FPL("/aa/bb/ccc"), FPL("ccc") }, + { FPL("/aa"), FPL("aa") }, + { FPL("/"), FPL("/") }, + { FPL("//"), FPL("//") }, + { FPL("///"), FPL("/") }, + { FPL("aa/"), FPL("aa") }, + { FPL("aa/bb"), FPL("bb") }, + { FPL("aa/bb/"), FPL("bb") }, + { FPL("aa/bb//"), FPL("bb") }, + { FPL("aa//bb//"), FPL("bb") }, + { FPL("aa//bb/"), FPL("bb") }, + { FPL("aa//bb"), FPL("bb") }, + { FPL("//aa/bb"), FPL("bb") }, + { FPL("//aa/"), FPL("aa") }, + { FPL("//aa"), FPL("aa") }, + { FPL("0:"), FPL("0:") }, + { FPL("@:"), FPL("@:") }, + { FPL("[:"), FPL("[:") }, + { FPL("`:"), FPL("`:") }, + { FPL("{:"), FPL("{:") }, + { FPL("\xB3:"), FPL("\xB3:") }, + { FPL("\xC5:"), FPL("\xC5:") }, +#if defined(OS_WIN) + { FPL("\x0143:"), FPL("\x0143:") }, +#endif // OS_WIN +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("c:"), FPL("") }, + { FPL("C:"), FPL("") }, + { FPL("A:"), FPL("") }, + { FPL("Z:"), FPL("") }, + { FPL("a:"), FPL("") }, + { FPL("z:"), FPL("") }, + { FPL("c:aa"), FPL("aa") }, + { FPL("c:/"), FPL("/") }, + { FPL("c://"), FPL("//") }, + { FPL("c:///"), FPL("/") }, + { FPL("c:/aa"), FPL("aa") }, + { FPL("c:/aa/"), FPL("aa") }, + { FPL("c:/aa/bb"), FPL("bb") }, + { FPL("c:aa/bb"), FPL("bb") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("\\aa\\bb"), FPL("bb") }, + { FPL("\\aa\\bb\\"), FPL("bb") }, + { FPL("\\aa\\bb\\\\"), FPL("bb") }, + { FPL("\\aa\\bb\\ccc"), FPL("ccc") }, + { FPL("\\aa"), FPL("aa") }, + { FPL("\\"), FPL("\\") }, + { FPL("\\\\"), FPL("\\\\") }, + { FPL("\\\\\\"), FPL("\\") }, + { FPL("aa\\"), FPL("aa") }, + { FPL("aa\\bb"), FPL("bb") }, + { FPL("aa\\bb\\"), FPL("bb") }, + { FPL("aa\\bb\\\\"), FPL("bb") }, + { FPL("aa\\\\bb\\\\"), FPL("bb") }, + { FPL("aa\\\\bb\\"), FPL("bb") }, + { FPL("aa\\\\bb"), FPL("bb") }, + { FPL("\\\\aa\\bb"), FPL("bb") }, + { FPL("\\\\aa\\"), FPL("aa") }, + { FPL("\\\\aa"), FPL("aa") }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("c:\\"), FPL("\\") }, + { FPL("c:\\\\"), FPL("\\\\") }, + { FPL("c:\\\\\\"), FPL("\\") }, + { FPL("c:\\aa"), FPL("aa") }, + { FPL("c:\\aa\\"), FPL("aa") }, + { FPL("c:\\aa\\bb"), FPL("bb") }, + { FPL("c:aa\\bb"), FPL("bb") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + FilePath observed = input.BaseName(); + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed.value()) << + "i: " << i << ", input: " << input.value(); + } +} + +TEST_F(FilePathTest, Append) { + const struct BinaryTestData cases[] = { + { { FPL(""), FPL("cc") }, FPL("cc") }, + { { FPL("."), FPL("ff") }, FPL("ff") }, + { { FPL("/"), FPL("cc") }, FPL("/cc") }, + { { FPL("/aa"), FPL("") }, FPL("/aa") }, + { { FPL("/aa/"), FPL("") }, FPL("/aa") }, + { { FPL("//aa"), FPL("") }, FPL("//aa") }, + { { FPL("//aa/"), FPL("") }, FPL("//aa") }, + { { FPL("//"), FPL("aa") }, FPL("//aa") }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:"), FPL("a") }, FPL("c:a") }, + { { FPL("c:"), FPL("") }, FPL("c:") }, + { { FPL("c:/"), FPL("a") }, FPL("c:/a") }, + { { FPL("c://"), FPL("a") }, FPL("c://a") }, + { { FPL("c:///"), FPL("a") }, FPL("c:/a") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + // Append introduces the default separator character, so these test cases + // need to be defined with different expected results on platforms that use + // different default separator characters. + { { FPL("\\"), FPL("cc") }, FPL("\\cc") }, + { { FPL("\\aa"), FPL("") }, FPL("\\aa") }, + { { FPL("\\aa\\"), FPL("") }, FPL("\\aa") }, + { { FPL("\\\\aa"), FPL("") }, FPL("\\\\aa") }, + { { FPL("\\\\aa\\"), FPL("") }, FPL("\\\\aa") }, + { { FPL("\\\\"), FPL("aa") }, FPL("\\\\aa") }, + { { FPL("/aa/bb"), FPL("cc") }, FPL("/aa/bb\\cc") }, + { { FPL("/aa/bb/"), FPL("cc") }, FPL("/aa/bb\\cc") }, + { { FPL("aa/bb/"), FPL("cc") }, FPL("aa/bb\\cc") }, + { { FPL("aa/bb"), FPL("cc") }, FPL("aa/bb\\cc") }, + { { FPL("a/b"), FPL("c") }, FPL("a/b\\c") }, + { { FPL("a/b/"), FPL("c") }, FPL("a/b\\c") }, + { { FPL("//aa"), FPL("bb") }, FPL("//aa\\bb") }, + { { FPL("//aa/"), FPL("bb") }, FPL("//aa\\bb") }, + { { FPL("\\aa\\bb"), FPL("cc") }, FPL("\\aa\\bb\\cc") }, + { { FPL("\\aa\\bb\\"), FPL("cc") }, FPL("\\aa\\bb\\cc") }, + { { FPL("aa\\bb\\"), FPL("cc") }, FPL("aa\\bb\\cc") }, + { { FPL("aa\\bb"), FPL("cc") }, FPL("aa\\bb\\cc") }, + { { FPL("a\\b"), FPL("c") }, FPL("a\\b\\c") }, + { { FPL("a\\b\\"), FPL("c") }, FPL("a\\b\\c") }, + { { FPL("\\\\aa"), FPL("bb") }, FPL("\\\\aa\\bb") }, + { { FPL("\\\\aa\\"), FPL("bb") }, FPL("\\\\aa\\bb") }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:\\"), FPL("a") }, FPL("c:\\a") }, + { { FPL("c:\\\\"), FPL("a") }, FPL("c:\\\\a") }, + { { FPL("c:\\\\\\"), FPL("a") }, FPL("c:\\a") }, + { { FPL("c:\\"), FPL("") }, FPL("c:\\") }, + { { FPL("c:\\a"), FPL("b") }, FPL("c:\\a\\b") }, + { { FPL("c:\\a\\"), FPL("b") }, FPL("c:\\a\\b") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#else // FILE_PATH_USES_WIN_SEPARATORS + { { FPL("/aa/bb"), FPL("cc") }, FPL("/aa/bb/cc") }, + { { FPL("/aa/bb/"), FPL("cc") }, FPL("/aa/bb/cc") }, + { { FPL("aa/bb/"), FPL("cc") }, FPL("aa/bb/cc") }, + { { FPL("aa/bb"), FPL("cc") }, FPL("aa/bb/cc") }, + { { FPL("a/b"), FPL("c") }, FPL("a/b/c") }, + { { FPL("a/b/"), FPL("c") }, FPL("a/b/c") }, + { { FPL("//aa"), FPL("bb") }, FPL("//aa/bb") }, + { { FPL("//aa/"), FPL("bb") }, FPL("//aa/bb") }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:/"), FPL("a") }, FPL("c:/a") }, + { { FPL("c:/"), FPL("") }, FPL("c:/") }, + { { FPL("c:/a"), FPL("b") }, FPL("c:/a/b") }, + { { FPL("c:/a/"), FPL("b") }, FPL("c:/a/b") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath root(cases[i].inputs[0]); + FilePath::StringType leaf(cases[i].inputs[1]); + FilePath observed_str = root.Append(leaf); + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed_str.value()) << + "i: " << i << ", root: " << root.value() << ", leaf: " << leaf; + FilePath observed_path = root.Append(FilePath(leaf)); + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed_path.value()) << + "i: " << i << ", root: " << root.value() << ", leaf: " << leaf; + + // TODO(erikkay): It would be nice to have a unicode test append value to + // handle the case when AppendASCII is passed UTF8 +#if defined(OS_WIN) + std::string ascii = WideToUTF8(leaf); +#elif defined(OS_POSIX) || defined(OS_STARBOARD) + std::string ascii = leaf; +#endif + observed_str = root.AppendASCII(ascii); + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed_str.value()) << + "i: " << i << ", root: " << root.value() << ", leaf: " << leaf; + } +} + +TEST_F(FilePathTest, StripTrailingSeparators) { + const struct UnaryTestData cases[] = { + { FPL(""), FPL("") }, + { FPL("/"), FPL("/") }, + { FPL("//"), FPL("//") }, + { FPL("///"), FPL("/") }, + { FPL("////"), FPL("/") }, + { FPL("a/"), FPL("a") }, + { FPL("a//"), FPL("a") }, + { FPL("a///"), FPL("a") }, + { FPL("a////"), FPL("a") }, + { FPL("/a"), FPL("/a") }, + { FPL("/a/"), FPL("/a") }, + { FPL("/a//"), FPL("/a") }, + { FPL("/a///"), FPL("/a") }, + { FPL("/a////"), FPL("/a") }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("c:"), FPL("c:") }, + { FPL("c:/"), FPL("c:/") }, + { FPL("c://"), FPL("c://") }, + { FPL("c:///"), FPL("c:/") }, + { FPL("c:////"), FPL("c:/") }, + { FPL("c:/a"), FPL("c:/a") }, + { FPL("c:/a/"), FPL("c:/a") }, + { FPL("c:/a//"), FPL("c:/a") }, + { FPL("c:/a///"), FPL("c:/a") }, + { FPL("c:/a////"), FPL("c:/a") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("\\"), FPL("\\") }, + { FPL("\\\\"), FPL("\\\\") }, + { FPL("\\\\\\"), FPL("\\") }, + { FPL("\\\\\\\\"), FPL("\\") }, + { FPL("a\\"), FPL("a") }, + { FPL("a\\\\"), FPL("a") }, + { FPL("a\\\\\\"), FPL("a") }, + { FPL("a\\\\\\\\"), FPL("a") }, + { FPL("\\a"), FPL("\\a") }, + { FPL("\\a\\"), FPL("\\a") }, + { FPL("\\a\\\\"), FPL("\\a") }, + { FPL("\\a\\\\\\"), FPL("\\a") }, + { FPL("\\a\\\\\\\\"), FPL("\\a") }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("c:\\"), FPL("c:\\") }, + { FPL("c:\\\\"), FPL("c:\\\\") }, + { FPL("c:\\\\\\"), FPL("c:\\") }, + { FPL("c:\\\\\\\\"), FPL("c:\\") }, + { FPL("c:\\a"), FPL("c:\\a") }, + { FPL("c:\\a\\"), FPL("c:\\a") }, + { FPL("c:\\a\\\\"), FPL("c:\\a") }, + { FPL("c:\\a\\\\\\"), FPL("c:\\a") }, + { FPL("c:\\a\\\\\\\\"), FPL("c:\\a") }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + FilePath observed = input.StripTrailingSeparators(); + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed.value()) << + "i: " << i << ", input: " << input.value(); + } +} + +TEST_F(FilePathTest, IsAbsolute) { + const struct UnaryBooleanTestData cases[] = { + { FPL(""), false }, + { FPL("a"), false }, + { FPL("c:"), false }, + { FPL("c:a"), false }, + { FPL("a/b"), false }, + { FPL("//"), true }, + { FPL("//a"), true }, + { FPL("c:a/b"), false }, + { FPL("?:/a"), false }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("/"), false }, + { FPL("/a"), false }, + { FPL("/."), false }, + { FPL("/.."), false }, + { FPL("c:/"), true }, + { FPL("c:/a"), true }, + { FPL("c:/."), true }, + { FPL("c:/.."), true }, + { FPL("C:/a"), true }, + { FPL("d:/a"), true }, +#else // FILE_PATH_USES_DRIVE_LETTERS + { FPL("/"), true }, + { FPL("/a"), true }, + { FPL("/."), true }, + { FPL("/.."), true }, + { FPL("c:/"), false }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("a\\b"), false }, + { FPL("\\\\"), true }, + { FPL("\\\\a"), true }, + { FPL("a\\b"), false }, + { FPL("\\\\"), true }, + { FPL("//a"), true }, + { FPL("c:a\\b"), false }, + { FPL("?:\\a"), false }, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("\\"), false }, + { FPL("\\a"), false }, + { FPL("\\."), false }, + { FPL("\\.."), false }, + { FPL("c:\\"), true }, + { FPL("c:\\"), true }, + { FPL("c:\\a"), true }, + { FPL("c:\\."), true }, + { FPL("c:\\.."), true }, + { FPL("C:\\a"), true }, + { FPL("d:\\a"), true }, +#else // FILE_PATH_USES_DRIVE_LETTERS + { FPL("\\"), true }, + { FPL("\\a"), true }, + { FPL("\\."), true }, + { FPL("\\.."), true }, + { FPL("c:\\"), false }, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + bool observed = input.IsAbsolute(); + EXPECT_EQ(cases[i].expected, observed) << + "i: " << i << ", input: " << input.value(); + } +} + +TEST_F(FilePathTest, PathComponentsTest) { + const struct UnaryTestData cases[] = { + { FPL("//foo/bar/baz/"), FPL("|//|foo|bar|baz")}, + { FPL("///"), FPL("|/")}, + { FPL("/foo//bar//baz/"), FPL("|/|foo|bar|baz")}, + { FPL("/foo/bar/baz/"), FPL("|/|foo|bar|baz")}, + { FPL("/foo/bar/baz//"), FPL("|/|foo|bar|baz")}, + { FPL("/foo/bar/baz///"), FPL("|/|foo|bar|baz")}, + { FPL("/foo/bar/baz"), FPL("|/|foo|bar|baz")}, + { FPL("/foo/bar.bot/baz.txt"), FPL("|/|foo|bar.bot|baz.txt")}, + { FPL("//foo//bar/baz"), FPL("|//|foo|bar|baz")}, + { FPL("/"), FPL("|/")}, + { FPL("foo"), FPL("|foo")}, + { FPL(""), FPL("")}, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { FPL("e:/foo"), FPL("|e:|/|foo")}, + { FPL("e:/"), FPL("|e:|/")}, + { FPL("e:"), FPL("|e:")}, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("../foo"), FPL("|..|foo")}, + { FPL("./foo"), FPL("|foo")}, + { FPL("../foo/bar/"), FPL("|..|foo|bar") }, + { FPL("\\\\foo\\bar\\baz\\"), FPL("|\\\\|foo|bar|baz")}, + { FPL("\\\\\\"), FPL("|\\")}, + { FPL("\\foo\\\\bar\\\\baz\\"), FPL("|\\|foo|bar|baz")}, + { FPL("\\foo\\bar\\baz\\"), FPL("|\\|foo|bar|baz")}, + { FPL("\\foo\\bar\\baz\\\\"), FPL("|\\|foo|bar|baz")}, + { FPL("\\foo\\bar\\baz\\\\\\"), FPL("|\\|foo|bar|baz")}, + { FPL("\\foo\\bar\\baz"), FPL("|\\|foo|bar|baz")}, + { FPL("\\foo\\bar/baz\\\\\\"), FPL("|\\|foo|bar|baz")}, + { FPL("/foo\\bar\\baz"), FPL("|/|foo|bar|baz")}, + { FPL("\\foo\\bar.bot\\baz.txt"), FPL("|\\|foo|bar.bot|baz.txt")}, + { FPL("\\\\foo\\\\bar\\baz"), FPL("|\\\\|foo|bar|baz")}, + { FPL("\\"), FPL("|\\")}, +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + std::vector<FilePath::StringType> comps; + input.GetComponents(&comps); + + FilePath::StringType observed; + for (size_t j = 0; j < comps.size(); ++j) { + observed.append(FILE_PATH_LITERAL("|"), 1); + observed.append(comps[j]); + } + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed) << + "i: " << i << ", input: " << input.value(); + } +} + +TEST_F(FilePathTest, IsParentTest) { + const struct BinaryBooleanTestData cases[] = { + { { FPL("/"), FPL("/foo/bar/baz") }, true}, + { { FPL("/foo/bar"), FPL("/foo/bar/baz") }, true}, + { { FPL("/foo/bar/"), FPL("/foo/bar/baz") }, true}, + { { FPL("//foo/bar/"), FPL("//foo/bar/baz") }, true}, + { { FPL("/foo/bar"), FPL("/foo2/bar/baz") }, false}, + { { FPL("/foo/bar.txt"), FPL("/foo/bar/baz") }, false}, + { { FPL("/foo/bar"), FPL("/foo/bar2/baz") }, false}, + { { FPL("/foo/bar"), FPL("/foo/bar") }, false}, + { { FPL("/foo/bar/baz"), FPL("/foo/bar") }, false}, + { { FPL("foo/bar"), FPL("foo/bar/baz") }, true}, + { { FPL("foo/bar"), FPL("foo2/bar/baz") }, false}, + { { FPL("foo/bar"), FPL("foo/bar2/baz") }, false}, + { { FPL(""), FPL("foo") }, false}, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:/foo/bar"), FPL("c:/foo/bar/baz") }, true}, + { { FPL("E:/foo/bar"), FPL("e:/foo/bar/baz") }, true}, + { { FPL("f:/foo/bar"), FPL("F:/foo/bar/baz") }, true}, + { { FPL("E:/Foo/bar"), FPL("e:/foo/bar/baz") }, false}, + { { FPL("f:/foo/bar"), FPL("F:/foo/Bar/baz") }, false}, + { { FPL("c:/"), FPL("c:/foo/bar/baz") }, true}, + { { FPL("c:"), FPL("c:/foo/bar/baz") }, true}, + { { FPL("c:/foo/bar"), FPL("d:/foo/bar/baz") }, false}, + { { FPL("c:/foo/bar"), FPL("D:/foo/bar/baz") }, false}, + { { FPL("C:/foo/bar"), FPL("d:/foo/bar/baz") }, false}, + { { FPL("c:/foo/bar"), FPL("c:/foo2/bar/baz") }, false}, + { { FPL("e:/foo/bar"), FPL("E:/foo2/bar/baz") }, false}, + { { FPL("F:/foo/bar"), FPL("f:/foo2/bar/baz") }, false}, + { { FPL("c:/foo/bar"), FPL("c:/foo/bar2/baz") }, false}, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("\\foo\\bar"), FPL("\\foo\\bar\\baz") }, true}, + { { FPL("\\foo/bar"), FPL("\\foo\\bar\\baz") }, true}, + { { FPL("\\foo/bar"), FPL("\\foo/bar/baz") }, true}, + { { FPL("\\"), FPL("\\foo\\bar\\baz") }, true}, + { { FPL(""), FPL("\\foo\\bar\\baz") }, false}, + { { FPL("\\foo\\bar"), FPL("\\foo2\\bar\\baz") }, false}, + { { FPL("\\foo\\bar"), FPL("\\foo\\bar2\\baz") }, false}, +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath parent(cases[i].inputs[0]); + FilePath child(cases[i].inputs[1]); + + EXPECT_EQ(parent.IsParent(child), cases[i].expected) << + "i: " << i << ", parent: " << parent.value() << ", child: " << + child.value(); + } +} + +TEST_F(FilePathTest, AppendRelativePathTest) { + const struct BinaryTestData cases[] = { +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("/"), FPL("/foo/bar/baz") }, FPL("foo\\bar\\baz")}, +#else // FILE_PATH_USES_WIN_SEPARATORS + { { FPL("/"), FPL("/foo/bar/baz") }, FPL("foo/bar/baz")}, +#endif // FILE_PATH_USES_WIN_SEPARATORS + { { FPL("/foo/bar"), FPL("/foo/bar/baz") }, FPL("baz")}, + { { FPL("/foo/bar/"), FPL("/foo/bar/baz") }, FPL("baz")}, + { { FPL("//foo/bar/"), FPL("//foo/bar/baz") }, FPL("baz")}, + { { FPL("/foo/bar"), FPL("/foo2/bar/baz") }, FPL("")}, + { { FPL("/foo/bar.txt"), FPL("/foo/bar/baz") }, FPL("")}, + { { FPL("/foo/bar"), FPL("/foo/bar2/baz") }, FPL("")}, + { { FPL("/foo/bar"), FPL("/foo/bar") }, FPL("")}, + { { FPL("/foo/bar/baz"), FPL("/foo/bar") }, FPL("")}, + { { FPL("foo/bar"), FPL("foo/bar/baz") }, FPL("baz")}, + { { FPL("foo/bar"), FPL("foo2/bar/baz") }, FPL("")}, + { { FPL("foo/bar"), FPL("foo/bar2/baz") }, FPL("")}, + { { FPL(""), FPL("foo") }, FPL("")}, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:/foo/bar"), FPL("c:/foo/bar/baz") }, FPL("baz")}, + { { FPL("E:/foo/bar"), FPL("e:/foo/bar/baz") }, FPL("baz")}, + { { FPL("f:/foo/bar"), FPL("F:/foo/bar/baz") }, FPL("baz")}, + { { FPL("E:/Foo/bar"), FPL("e:/foo/bar/baz") }, FPL("")}, + { { FPL("f:/foo/bar"), FPL("F:/foo/Bar/baz") }, FPL("")}, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("c:/"), FPL("c:/foo/bar/baz") }, FPL("foo\\bar\\baz")}, + // TODO(akalin): Figure out how to handle the corner case in the + // commented-out test case below. Appending to an empty path gives + // /foo\bar\baz but appending to a nonempty path "blah" gives + // blah\foo\bar\baz. + // { { FPL("c:"), FPL("c:/foo/bar/baz") }, FPL("foo\\bar\\baz")}, +#endif // FILE_PATH_USES_WIN_SEPARATORS + { { FPL("c:/foo/bar"), FPL("d:/foo/bar/baz") }, FPL("")}, + { { FPL("c:/foo/bar"), FPL("D:/foo/bar/baz") }, FPL("")}, + { { FPL("C:/foo/bar"), FPL("d:/foo/bar/baz") }, FPL("")}, + { { FPL("c:/foo/bar"), FPL("c:/foo2/bar/baz") }, FPL("")}, + { { FPL("e:/foo/bar"), FPL("E:/foo2/bar/baz") }, FPL("")}, + { { FPL("F:/foo/bar"), FPL("f:/foo2/bar/baz") }, FPL("")}, + { { FPL("c:/foo/bar"), FPL("c:/foo/bar2/baz") }, FPL("")}, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("\\foo\\bar"), FPL("\\foo\\bar\\baz") }, FPL("baz")}, + { { FPL("\\foo/bar"), FPL("\\foo\\bar\\baz") }, FPL("baz")}, + { { FPL("\\foo/bar"), FPL("\\foo/bar/baz") }, FPL("baz")}, + { { FPL("\\"), FPL("\\foo\\bar\\baz") }, FPL("foo\\bar\\baz")}, + { { FPL(""), FPL("\\foo\\bar\\baz") }, FPL("")}, + { { FPL("\\foo\\bar"), FPL("\\foo2\\bar\\baz") }, FPL("")}, + { { FPL("\\foo\\bar"), FPL("\\foo\\bar2\\baz") }, FPL("")}, +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + const FilePath base(FPL("blah")); + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath parent(cases[i].inputs[0]); + FilePath child(cases[i].inputs[1]); + { + FilePath result; + bool success = parent.AppendRelativePath(child, &result); + EXPECT_EQ(cases[i].expected[0] != '\0', success) << + "i: " << i << ", parent: " << parent.value() << ", child: " << + child.value(); + EXPECT_STREQ(cases[i].expected, result.value().c_str()) << + "i: " << i << ", parent: " << parent.value() << ", child: " << + child.value(); + } + { + FilePath result(base); + bool success = parent.AppendRelativePath(child, &result); + EXPECT_EQ(cases[i].expected[0] != '\0', success) << + "i: " << i << ", parent: " << parent.value() << ", child: " << + child.value(); + EXPECT_EQ(base.Append(cases[i].expected).value(), result.value()) << + "i: " << i << ", parent: " << parent.value() << ", child: " << + child.value(); + } + } +} + +TEST_F(FilePathTest, EqualityTest) { + const struct BinaryBooleanTestData cases[] = { + { { FPL("/foo/bar/baz"), FPL("/foo/bar/baz") }, true}, + { { FPL("/foo/bar"), FPL("/foo/bar/baz") }, false}, + { { FPL("/foo/bar/baz"), FPL("/foo/bar") }, false}, + { { FPL("//foo/bar/"), FPL("//foo/bar/") }, true}, + { { FPL("/foo/bar"), FPL("/foo2/bar") }, false}, + { { FPL("/foo/bar.txt"), FPL("/foo/bar") }, false}, + { { FPL("foo/bar"), FPL("foo/bar") }, true}, + { { FPL("foo/bar"), FPL("foo/bar/baz") }, false}, + { { FPL(""), FPL("foo") }, false}, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:/foo/bar"), FPL("c:/foo/bar") }, true}, + { { FPL("E:/foo/bar"), FPL("e:/foo/bar") }, true}, + { { FPL("f:/foo/bar"), FPL("F:/foo/bar") }, true}, + { { FPL("E:/Foo/bar"), FPL("e:/foo/bar") }, false}, + { { FPL("f:/foo/bar"), FPL("F:/foo/Bar") }, false}, + { { FPL("c:/"), FPL("c:/") }, true}, + { { FPL("c:"), FPL("c:") }, true}, + { { FPL("c:/foo/bar"), FPL("d:/foo/bar") }, false}, + { { FPL("c:/foo/bar"), FPL("D:/foo/bar") }, false}, + { { FPL("C:/foo/bar"), FPL("d:/foo/bar") }, false}, + { { FPL("c:/foo/bar"), FPL("c:/foo2/bar") }, false}, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("\\foo\\bar"), FPL("\\foo\\bar") }, true}, + { { FPL("\\foo/bar"), FPL("\\foo/bar") }, true}, + { { FPL("\\foo/bar"), FPL("\\foo\\bar") }, false}, + { { FPL("\\"), FPL("\\") }, true}, + { { FPL("\\"), FPL("/") }, false}, + { { FPL(""), FPL("\\") }, false}, + { { FPL("\\foo\\bar"), FPL("\\foo2\\bar") }, false}, + { { FPL("\\foo\\bar"), FPL("\\foo\\bar2") }, false}, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:\\foo\\bar"), FPL("c:\\foo\\bar") }, true}, + { { FPL("E:\\foo\\bar"), FPL("e:\\foo\\bar") }, true}, + { { FPL("f:\\foo\\bar"), FPL("F:\\foo/bar") }, false}, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#endif // FILE_PATH_USES_WIN_SEPARATORS + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath a(cases[i].inputs[0]); + FilePath b(cases[i].inputs[1]); + + EXPECT_EQ(a == b, cases[i].expected) << + "equality i: " << i << ", a: " << a.value() << ", b: " << + b.value(); + } + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath a(cases[i].inputs[0]); + FilePath b(cases[i].inputs[1]); + + EXPECT_EQ(a != b, !cases[i].expected) << + "inequality i: " << i << ", a: " << a.value() << ", b: " << + b.value(); + } +} + +TEST_F(FilePathTest, Extension) { + FilePath base_dir(FILE_PATH_LITERAL("base_dir")); + + FilePath jpg = base_dir.Append(FILE_PATH_LITERAL("foo.jpg")); + EXPECT_EQ(FILE_PATH_LITERAL(".jpg"), jpg.Extension()); + + FilePath base = jpg.BaseName().RemoveExtension(); + EXPECT_EQ(FILE_PATH_LITERAL("foo"), base.value()); + + FilePath path_no_ext = base_dir.Append(base); + EXPECT_EQ(path_no_ext.value(), jpg.RemoveExtension().value()); + + EXPECT_EQ(path_no_ext.value(), path_no_ext.RemoveExtension().value()); + EXPECT_EQ(FILE_PATH_LITERAL(""), path_no_ext.Extension()); +} + +TEST_F(FilePathTest, Extension2) { + const struct UnaryTestData cases[] = { +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("C:\\a\\b\\c.ext"), FPL(".ext") }, + { FPL("C:\\a\\b\\c."), FPL(".") }, + { FPL("C:\\a\\b\\c"), FPL("") }, + { FPL("C:\\a\\b\\"), FPL("") }, + { FPL("C:\\a\\b.\\"), FPL(".") }, + { FPL("C:\\a\\b\\c.ext1.ext2"), FPL(".ext2") }, + { FPL("C:\\foo.bar\\\\\\"), FPL(".bar") }, + { FPL("C:\\foo.bar\\.."), FPL("") }, + { FPL("C:\\foo.bar\\..\\\\"), FPL("") }, +#endif + { FPL("/foo/bar/baz.ext"), FPL(".ext") }, + { FPL("/foo/bar/baz."), FPL(".") }, + { FPL("/foo/bar/baz.."), FPL(".") }, + { FPL("/foo/bar/baz"), FPL("") }, + { 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("") }, + { FPL("./foo.ext"), FPL(".ext") }, + { FPL("/foo.ext1/bar.ext2"), FPL(".ext2") }, + { 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") }, + }; + for (unsigned int i = 0; i < arraysize(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(); + } +} + +TEST_F(FilePathTest, InsertBeforeExtension) { + const struct BinaryTestData cases[] = { + { { FPL(""), FPL("") }, FPL("") }, + { { FPL(""), FPL("txt") }, FPL("") }, + { { FPL("."), FPL("txt") }, FPL("") }, + { { FPL(".."), FPL("txt") }, FPL("") }, + { { FPL("foo.dll"), FPL("txt") }, FPL("footxt.dll") }, + { { FPL("."), FPL("") }, FPL(".") }, + { { FPL("foo.dll"), FPL(".txt") }, FPL("foo.txt.dll") }, + { { FPL("foo"), FPL("txt") }, FPL("footxt") }, + { { FPL("foo"), FPL(".txt") }, FPL("foo.txt") }, + { { FPL("foo.baz.dll"), FPL("txt") }, FPL("foo.baztxt.dll") }, + { { FPL("foo.baz.dll"), FPL(".txt") }, FPL("foo.baz.txt.dll") }, + { { FPL("foo.dll"), FPL("") }, FPL("foo.dll") }, + { { FPL("foo.dll"), FPL(".") }, FPL("foo..dll") }, + { { FPL("foo"), FPL("") }, FPL("foo") }, + { { FPL("foo"), FPL(".") }, FPL("foo.") }, + { { FPL("foo.baz.dll"), FPL("") }, FPL("foo.baz.dll") }, + { { FPL("foo.baz.dll"), FPL(".") }, FPL("foo.baz..dll") }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("\\"), FPL("") }, FPL("\\") }, + { { FPL("\\"), FPL("txt") }, FPL("\\txt") }, + { { FPL("\\."), FPL("txt") }, FPL("") }, + { { FPL("\\.."), FPL("txt") }, FPL("") }, + { { FPL("\\."), FPL("") }, FPL("\\.") }, + { { FPL("C:\\bar\\foo.dll"), FPL("txt") }, + FPL("C:\\bar\\footxt.dll") }, + { { FPL("C:\\bar.baz\\foodll"), FPL("txt") }, + FPL("C:\\bar.baz\\foodlltxt") }, + { { FPL("C:\\bar.baz\\foo.dll"), FPL("txt") }, + FPL("C:\\bar.baz\\footxt.dll") }, + { { FPL("C:\\bar.baz\\foo.dll.exe"), FPL("txt") }, + FPL("C:\\bar.baz\\foo.dlltxt.exe") }, + { { FPL("C:\\bar.baz\\foo"), FPL("") }, + FPL("C:\\bar.baz\\foo") }, + { { FPL("C:\\bar.baz\\foo.exe"), FPL("") }, + FPL("C:\\bar.baz\\foo.exe") }, + { { FPL("C:\\bar.baz\\foo.dll.exe"), FPL("") }, + FPL("C:\\bar.baz\\foo.dll.exe") }, + { { FPL("C:\\bar\\baz\\foo.exe"), FPL(" (1)") }, + FPL("C:\\bar\\baz\\foo (1).exe") }, + { { FPL("C:\\foo.baz\\\\"), FPL(" (1)") }, FPL("C:\\foo (1).baz") }, + { { FPL("C:\\foo.baz\\..\\"), FPL(" (1)") }, FPL("") }, +#endif + { { FPL("/"), FPL("") }, FPL("/") }, + { { FPL("/"), FPL("txt") }, FPL("/txt") }, + { { FPL("/."), FPL("txt") }, FPL("") }, + { { FPL("/.."), FPL("txt") }, FPL("") }, + { { FPL("/."), FPL("") }, FPL("/.") }, + { { FPL("/bar/foo.dll"), FPL("txt") }, FPL("/bar/footxt.dll") }, + { { FPL("/bar.baz/foodll"), FPL("txt") }, FPL("/bar.baz/foodlltxt") }, + { { FPL("/bar.baz/foo.dll"), FPL("txt") }, FPL("/bar.baz/footxt.dll") }, + { { FPL("/bar.baz/foo.dll.exe"), FPL("txt") }, + FPL("/bar.baz/foo.dlltxt.exe") }, + { { FPL("/bar.baz/foo"), FPL("") }, FPL("/bar.baz/foo") }, + { { FPL("/bar.baz/foo.exe"), FPL("") }, FPL("/bar.baz/foo.exe") }, + { { FPL("/bar.baz/foo.dll.exe"), FPL("") }, FPL("/bar.baz/foo.dll.exe") }, + { { FPL("/bar/baz/foo.exe"), FPL(" (1)") }, FPL("/bar/baz/foo (1).exe") }, + { { FPL("/bar/baz/..////"), FPL(" (1)") }, FPL("") }, + }; + for (unsigned int i = 0; i < arraysize(cases); ++i) { + FilePath path(cases[i].inputs[0]); + FilePath result = path.InsertBeforeExtension(cases[i].inputs[1]); + EXPECT_EQ(cases[i].expected, result.value()) << "i: " << i << + ", path: " << path.value() << ", insert: " << cases[i].inputs[1]; + } +} + +TEST_F(FilePathTest, RemoveExtension) { + const struct UnaryTestData cases[] = { + { FPL(""), FPL("") }, + { FPL("."), FPL(".") }, + { FPL(".."), FPL("..") }, + { FPL("foo.dll"), FPL("foo") }, + { FPL("./foo.dll"), FPL("./foo") }, + { FPL("foo..dll"), FPL("foo.") }, + { FPL("foo"), FPL("foo") }, + { 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\\..\\\\") }, +#endif + { FPL("/foo.bar/foo"), FPL("/foo.bar/foo") }, + { FPL("/foo.bar/..////"), FPL("/foo.bar/..////") }, + }; + for (unsigned int i = 0; i < arraysize(cases); ++i) { + FilePath path(cases[i].input); + FilePath removed = path.RemoveExtension(); + EXPECT_EQ(cases[i].expected, removed.value()) << "i: " << i << + ", path: " << path.value(); + } +} + +TEST_F(FilePathTest, ReplaceExtension) { + const struct BinaryTestData cases[] = { + { { FPL(""), FPL("") }, FPL("") }, + { { FPL(""), FPL("txt") }, FPL("") }, + { { FPL("."), FPL("txt") }, FPL("") }, + { { FPL(".."), FPL("txt") }, FPL("") }, + { { FPL("."), FPL("") }, FPL("") }, + { { FPL("foo.dll"), FPL("txt") }, FPL("foo.txt") }, + { { FPL("./foo.dll"), FPL("txt") }, FPL("./foo.txt") }, + { { FPL("foo..dll"), FPL("txt") }, FPL("foo..txt") }, + { { FPL("foo.dll"), FPL(".txt") }, FPL("foo.txt") }, + { { FPL("foo"), FPL("txt") }, FPL("foo.txt") }, + { { FPL("foo."), FPL("txt") }, FPL("foo.txt") }, + { { FPL("foo.."), FPL("txt") }, FPL("foo..txt") }, + { { FPL("foo"), FPL(".txt") }, FPL("foo.txt") }, + { { FPL("foo.baz.dll"), FPL("txt") }, FPL("foo.baz.txt") }, + { { FPL("foo.baz.dll"), FPL(".txt") }, FPL("foo.baz.txt") }, + { { FPL("foo.dll"), FPL("") }, FPL("foo") }, + { { FPL("foo.dll"), FPL(".") }, FPL("foo") }, + { { FPL("foo"), FPL("") }, FPL("foo") }, + { { FPL("foo"), FPL(".") }, FPL("foo") }, + { { FPL("foo.baz.dll"), FPL("") }, FPL("foo.baz") }, + { { FPL("foo.baz.dll"), FPL(".") }, FPL("foo.baz") }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("C:\\foo.bar\\foo"), FPL("baz") }, FPL("C:\\foo.bar\\foo.baz") }, + { { FPL("C:\\foo.bar\\..\\\\"), FPL("baz") }, FPL("") }, +#endif + { { FPL("/foo.bar/foo"), FPL("baz") }, FPL("/foo.bar/foo.baz") }, + { { FPL("/foo.bar/..////"), FPL("baz") }, FPL("") }, + }; + for (unsigned int i = 0; i < arraysize(cases); ++i) { + FilePath path(cases[i].inputs[0]); + FilePath replaced = path.ReplaceExtension(cases[i].inputs[1]); + EXPECT_EQ(cases[i].expected, replaced.value()) << "i: " << i << + ", path: " << path.value() << ", replace: " << cases[i].inputs[1]; + } +} + +TEST_F(FilePathTest, AddExtension) { + const struct BinaryTestData cases[] = { + { { FPL(""), FPL("") }, FPL("") }, + { { FPL(""), FPL("txt") }, FPL("") }, + { { FPL("."), FPL("txt") }, FPL("") }, + { { FPL(".."), FPL("txt") }, FPL("") }, + { { FPL("."), FPL("") }, FPL("") }, + { { FPL("foo.dll"), FPL("txt") }, FPL("foo.dll.txt") }, + { { FPL("./foo.dll"), FPL("txt") }, FPL("./foo.dll.txt") }, + { { FPL("foo..dll"), FPL("txt") }, FPL("foo..dll.txt") }, + { { FPL("foo.dll"), FPL(".txt") }, FPL("foo.dll.txt") }, + { { FPL("foo"), FPL("txt") }, FPL("foo.txt") }, + { { FPL("foo."), FPL("txt") }, FPL("foo.txt") }, + { { FPL("foo.."), FPL("txt") }, FPL("foo..txt") }, + { { FPL("foo"), FPL(".txt") }, FPL("foo.txt") }, + { { FPL("foo.baz.dll"), FPL("txt") }, FPL("foo.baz.dll.txt") }, + { { FPL("foo.baz.dll"), FPL(".txt") }, FPL("foo.baz.dll.txt") }, + { { FPL("foo.dll"), FPL("") }, FPL("foo.dll") }, + { { FPL("foo.dll"), FPL(".") }, FPL("foo.dll") }, + { { FPL("foo"), FPL("") }, FPL("foo") }, + { { FPL("foo"), FPL(".") }, FPL("foo") }, + { { FPL("foo.baz.dll"), FPL("") }, FPL("foo.baz.dll") }, + { { FPL("foo.baz.dll"), FPL(".") }, FPL("foo.baz.dll") }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("C:\\foo.bar\\foo"), FPL("baz") }, FPL("C:\\foo.bar\\foo.baz") }, + { { FPL("C:\\foo.bar\\..\\\\"), FPL("baz") }, FPL("") }, +#endif + { { FPL("/foo.bar/foo"), FPL("baz") }, FPL("/foo.bar/foo.baz") }, + { { FPL("/foo.bar/..////"), FPL("baz") }, FPL("") }, + }; + for (unsigned int i = 0; i < arraysize(cases); ++i) { + FilePath path(cases[i].inputs[0]); + FilePath added = path.AddExtension(cases[i].inputs[1]); + EXPECT_EQ(cases[i].expected, added.value()) << "i: " << i << + ", path: " << path.value() << ", add: " << cases[i].inputs[1]; + } +} + +TEST_F(FilePathTest, MatchesExtension) { + const struct BinaryBooleanTestData cases[] = { + { { FPL("foo"), FPL("") }, true}, + { { FPL("foo"), FPL(".") }, false}, + { { FPL("foo."), FPL("") }, false}, + { { FPL("foo."), FPL(".") }, true}, + { { FPL("foo.txt"), FPL(".dll") }, false}, + { { FPL("foo.txt"), FPL(".txt") }, true}, + { { FPL("foo.txt.dll"), FPL(".txt") }, false}, + { { FPL("foo.txt.dll"), FPL(".dll") }, true}, + { { FPL("foo.TXT"), FPL(".txt") }, true}, + { { FPL("foo.txt"), FPL(".TXT") }, true}, + { { FPL("foo.tXt"), FPL(".txt") }, true}, + { { FPL("foo.txt"), FPL(".tXt") }, true}, + { { FPL("foo.tXt"), FPL(".TXT") }, true}, + { { FPL("foo.tXt"), FPL(".tXt") }, true}, +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + { { FPL("c:/foo.txt.dll"), FPL(".txt") }, false}, + { { FPL("c:/foo.txt"), FPL(".txt") }, true}, +#endif // FILE_PATH_USES_DRIVE_LETTERS +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("c:\\bar\\foo.txt.dll"), FPL(".txt") }, false}, + { { FPL("c:\\bar\\foo.txt"), FPL(".txt") }, true}, +#endif // FILE_PATH_USES_DRIVE_LETTERS + { { FPL("/bar/foo.txt.dll"), FPL(".txt") }, false}, + { { FPL("/bar/foo.txt"), FPL(".txt") }, true}, +#if defined(OS_WIN) || defined(OS_MACOSX) + // Umlauts A, O, U: direct comparison, and upper case vs. lower case + { { FPL("foo.\u00E4\u00F6\u00FC"), FPL(".\u00E4\u00F6\u00FC") }, true}, + { { FPL("foo.\u00C4\u00D6\u00DC"), FPL(".\u00E4\u00F6\u00FC") }, true}, + // C with circumflex: direct comparison, and upper case vs. lower case + { { FPL("foo.\u0109"), FPL(".\u0109") }, true}, + { { FPL("foo.\u0108"), FPL(".\u0109") }, true}, +#endif + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath path(cases[i].inputs[0]); + FilePath::StringType ext(cases[i].inputs[1]); + + EXPECT_EQ(cases[i].expected, path.MatchesExtension(ext)) << + "i: " << i << ", path: " << path.value() << ", ext: " << ext; + } +} + +TEST_F(FilePathTest, CompareIgnoreCase) { + const struct BinaryIntTestData cases[] = { + { { FPL("foo"), FPL("foo") }, 0}, + { { FPL("FOO"), FPL("foo") }, 0}, + { { FPL("foo.ext"), FPL("foo.ext") }, 0}, + { { FPL("FOO.EXT"), FPL("foo.ext") }, 0}, + { { FPL("Foo.Ext"), FPL("foo.ext") }, 0}, + { { FPL("foO"), FPL("foo") }, 0}, + { { FPL("foo"), FPL("foO") }, 0}, + { { FPL("fOo"), FPL("foo") }, 0}, + { { FPL("foo"), FPL("fOo") }, 0}, + { { FPL("bar"), FPL("foo") }, -1}, + { { FPL("foo"), FPL("bar") }, 1}, + { { FPL("BAR"), FPL("foo") }, -1}, + { { FPL("FOO"), FPL("bar") }, 1}, + { { FPL("bar"), FPL("FOO") }, -1}, + { { FPL("foo"), FPL("BAR") }, 1}, + { { FPL("BAR"), FPL("FOO") }, -1}, + { { FPL("FOO"), FPL("BAR") }, 1}, + // German "Eszett" (lower case and the new-fangled upper case) + // Note that uc(<lowercase eszett>) => "SS", NOT <uppercase eszett>! + // However, neither Windows nor Mac OSX converts these. + // (or even have glyphs for <uppercase eszett>) + { { FPL("\u00DF"), FPL("\u00DF") }, 0}, +// TODO: Since Cobalt on Windows is compiled in POSIX emulation mode, strings +// are single-byte. Unicode characters cannot be represented in +// single-byte encoding, so tests below are doomed to fail. Re-enable +// tests after getting rid of POSIX emulation. +// TODO: Starboard, the replacement for POSIX emulation, would like path +// encoding to be consistent across platforms in common code. Lowest +// common denominator suggests we should still use single-byte paths. The +// TODO is to discuss and act on these TODOs. +#if !defined(COBALT_WIN) && !defined(OS_STARBOARD) + { { FPL("\u1E9E"), FPL("\u1E9E") }, 0}, +#endif +// CompareIgnoreCase is tertiary. These glyphs don't exist. +#if !defined(COBALT) && !defined(OS_STARBOARD) + { { FPL("\u00DF"), FPL("\u1E9E") }, 1}, + { { FPL("SS"), FPL("\u00DF") }, -1}, + { { FPL("SS"), FPL("\u1E9E") }, 1}, +#endif +#if defined(OS_WIN) || defined(OS_MACOSX) + // Umlauts A, O, U: direct comparison, and upper case vs. lower case + { { FPL("\u00E4\u00F6\u00FC"), FPL("\u00E4\u00F6\u00FC") }, 0}, + { { FPL("\u00C4\u00D6\u00DC"), FPL("\u00E4\u00F6\u00FC") }, 0}, + // C with circumflex: direct comparison, and upper case vs. lower case + { { FPL("\u0109"), FPL("\u0109") }, 0}, + { { FPL("\u0108"), FPL("\u0109") }, 0}, + // Cyrillic letter SHA: direct comparison, and upper case vs. lower case + { { FPL("\u0428"), FPL("\u0428") }, 0}, + { { FPL("\u0428"), FPL("\u0448") }, 0}, + // Greek letter DELTA: direct comparison, and upper case vs. lower case + { { FPL("\u0394"), FPL("\u0394") }, 0}, + { { FPL("\u0394"), FPL("\u03B4") }, 0}, + // Japanese full-width A: direct comparison, and upper case vs. lower case + // Note that full-width and standard characters are considered different. + { { FPL("\uFF21"), FPL("\uFF21") }, 0}, + { { FPL("\uFF21"), FPL("\uFF41") }, 0}, + { { FPL("A"), FPL("\uFF21") }, -1}, + { { FPL("A"), FPL("\uFF41") }, -1}, + { { FPL("a"), FPL("\uFF21") }, -1}, + { { FPL("a"), FPL("\uFF41") }, -1}, +#endif +#if defined(OS_MACOSX) + // Codepoints > 0x1000 + // Georgian letter DON: direct comparison, and upper case vs. lower case + { { FPL("\u10A3"), FPL("\u10A3") }, 0}, + { { FPL("\u10A3"), FPL("\u10D3") }, 0}, + // Combining characters vs. pre-composed characters, upper and lower case + { { FPL("k\u0301u\u032Do\u0304\u0301n"), FPL("\u1E31\u1E77\u1E53n") }, 0}, + { { FPL("k\u0301u\u032Do\u0304\u0301n"), FPL("kuon") }, 1}, + { { FPL("kuon"), FPL("k\u0301u\u032Do\u0304\u0301n") }, -1}, + { { FPL("K\u0301U\u032DO\u0304\u0301N"), FPL("KUON") }, 1}, + { { FPL("KUON"), FPL("K\u0301U\u032DO\u0304\u0301N") }, -1}, + { { FPL("k\u0301u\u032Do\u0304\u0301n"), FPL("KUON") }, 1}, + { { FPL("K\u0301U\u032DO\u0304\u0301N"), FPL("\u1E31\u1E77\u1E53n") }, 0}, + { { FPL("k\u0301u\u032Do\u0304\u0301n"), FPL("\u1E30\u1E76\u1E52n") }, 0}, + { { FPL("k\u0301u\u032Do\u0304\u0302n"), FPL("\u1E30\u1E76\u1E52n") }, 1}, +#endif + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath::StringType s1(cases[i].inputs[0]); + FilePath::StringType s2(cases[i].inputs[1]); + int result = FilePath::CompareIgnoreCase(s1, s2); + EXPECT_EQ(cases[i].expected, result) << + "i: " << i << ", s1: " << s1 << ", s2: " << s2; + } +} + +TEST_F(FilePathTest, ReferencesParent) { + const struct UnaryBooleanTestData cases[] = { + { FPL("."), false }, + { FPL(".."), true }, + { FPL("a.."), false }, + { FPL("..a"), false }, + { FPL("../"), true }, + { FPL("/.."), true }, + { FPL("/../"), true }, + { FPL("/a../"), false }, + { FPL("/..a/"), false }, + { FPL("//.."), true }, + { FPL("..//"), true }, + { FPL("//..//"), true }, + { FPL("a//..//c"), true }, + { FPL("../b/c"), true }, + { FPL("/../b/c"), true }, + { FPL("a/b/.."), true }, + { FPL("a/b/../"), true }, + { FPL("a/../c"), true }, + { FPL("a/b/c"), false }, + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + bool observed = input.ReferencesParent(); + EXPECT_EQ(cases[i].expected, observed) << + "i: " << i << ", input: " << input.value(); + } +} + +TEST_F(FilePathTest, FromUTF8Unsafe_And_AsUTF8Unsafe) { + const struct UTF8TestData cases[] = { + { FPL("foo.txt"), "foo.txt" }, +#if !defined(__LB_SHELL__) + // "aeo" with accents. Use http://0xcc.net/jsescape/ to decode them. + { FPL("\u00E0\u00E8\u00F2.txt"), "\xC3\xA0\xC3\xA8\xC3\xB2.txt" }, + // Full-width "ABC". + { FPL("\uFF21\uFF22\uFF23.txt"), + "\xEF\xBC\xA1\xEF\xBC\xA2\xEF\xBC\xA3.txt" }, +#endif + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + // Test FromUTF8Unsafe() works. + FilePath from_utf8 = FilePath::FromUTF8Unsafe(cases[i].utf8); + EXPECT_EQ(cases[i].native, from_utf8.value()) + << "i: " << i << ", input: " << cases[i].native; + // Test AsUTF8Unsafe() works. + FilePath from_native = FilePath(cases[i].native); + EXPECT_EQ(cases[i].utf8, from_native.AsUTF8Unsafe()) + << "i: " << i << ", input: " << cases[i].native; + // Test the two file paths are identical. + EXPECT_EQ(from_utf8.value(), from_native.value()); + } +} + +TEST_F(FilePathTest, ConstructWithNUL) { + // Assert FPS() works. + ASSERT_TRUE(FPS("a\0b").length() == 3); + + // Test constructor strips '\0' + FilePath path(FPS("a\0b")); + EXPECT_TRUE(path.value().length() == 1); + EXPECT_EQ(path.value(), FPL("a")); +} + +TEST_F(FilePathTest, AppendWithNUL) { + // Assert FPS() works. + ASSERT_TRUE(FPS("b\0b").length() == 3); + + // Test Append() strips '\0' + FilePath path(FPL("a")); + path = path.Append(FPS("b\0b")); + EXPECT_TRUE(path.value().length() == 3); +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + EXPECT_EQ(path.value(), FPL("a\\b")); +#else + EXPECT_EQ(path.value(), FPL("a/b")); +#endif +} + +TEST_F(FilePathTest, ReferencesParentWithNUL) { + // Assert FPS() works. + ASSERT_TRUE(FPS("..\0").length() == 3); + + // Test ReferencesParent() doesn't break with "..\0" + FilePath path(FPS("..\0")); + EXPECT_TRUE(path.ReferencesParent()); +} + +#if defined(FILE_PATH_USES_WIN_SEPARATORS) +TEST_F(FilePathTest, NormalizePathSeparators) { + const struct UnaryTestData cases[] = { + { FPL("foo/bar"), FPL("foo\\bar") }, + { FPL("foo/bar\\betz"), FPL("foo\\bar\\betz") }, + { FPL("foo\\bar"), FPL("foo\\bar") }, + { FPL("foo\\bar/betz"), FPL("foo\\bar\\betz") }, + { FPL("foo"), FPL("foo") }, + // Trailing slashes don't automatically get stripped. That's what + // StripTrailingSeparators() is for. + { FPL("foo\\"), FPL("foo\\") }, + { FPL("foo/"), FPL("foo\\") }, + { FPL("foo/bar\\"), FPL("foo\\bar\\") }, + { FPL("foo\\bar/"), FPL("foo\\bar\\") }, + { FPL("foo/bar/"), FPL("foo\\bar\\") }, + { FPL("foo\\bar\\"), FPL("foo\\bar\\") }, + { FPL("\\foo/bar"), FPL("\\foo\\bar") }, + { FPL("/foo\\bar"), FPL("\\foo\\bar") }, + { FPL("c:/foo/bar/"), FPL("c:\\foo\\bar\\") }, + { FPL("/foo/bar/"), FPL("\\foo\\bar\\") }, + { FPL("\\foo\\bar\\"), FPL("\\foo\\bar\\") }, + { FPL("c:\\foo/bar"), FPL("c:\\foo\\bar") }, + { FPL("//foo\\bar\\"), FPL("\\\\foo\\bar\\") }, + { FPL("\\\\foo\\bar\\"), FPL("\\\\foo\\bar\\") }, + { FPL("//foo\\bar\\"), FPL("\\\\foo\\bar\\") }, + // This method does not normalize the number of path separators. + { FPL("foo\\\\bar"), FPL("foo\\\\bar") }, + { FPL("foo//bar"), FPL("foo\\\\bar") }, + { FPL("foo/\\bar"), FPL("foo\\\\bar") }, + { FPL("foo\\/bar"), FPL("foo\\\\bar") }, + { FPL("///foo\\\\bar"), FPL("\\\\\\foo\\\\bar") }, + { FPL("foo//bar///"), FPL("foo\\\\bar\\\\\\") }, + { FPL("foo/\\bar/\\"), FPL("foo\\\\bar\\\\") }, + { FPL("/\\foo\\/bar"), FPL("\\\\foo\\\\bar") }, + }; + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + FilePath observed = input.NormalizePathSeparators(); + EXPECT_EQ(FilePath::StringType(cases[i].expected), observed.value()) << + "i: " << i << ", input: " << input.value(); + } +} + +#endif
diff --git a/src/base/file_util.cc b/src/base/file_util.cc new file mode 100644 index 0000000..cf12128 --- /dev/null +++ b/src/base/file_util.cc
@@ -0,0 +1,444 @@ +// 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" + +#if defined(OS_WIN) +#include <io.h> +#endif +#include <stdio.h> + +#include <fstream> + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "base/string_piece.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" + +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 +// extension, where 1 <= nnn <= kMaxUniqueFiles. +// Also used by code that cleans up said files. +static const int kMaxUniqueFiles = 100; + +} // namespace + +namespace file_util { + +bool g_bug108724_debug = false; + +bool EndsWithSeparator(const FilePath& path) { + FilePath::StringType value = path.value(); + if (value.empty()) + return false; + + return FilePath::IsSeparator(value[value.size() - 1]); +} + +bool EnsureEndsWithSeparator(FilePath* path) { + if (!DirectoryExists(*path)) + return false; + + if (EndsWithSeparator(*path)) + return true; + + FilePath::StringType& path_str = + const_cast<FilePath::StringType&>(path->value()); + path_str.append(&FilePath::kSeparators[0], 1); + + return true; +} + +void InsertBeforeExtension(FilePath* path, const FilePath::StringType& suffix) { + FilePath::StringType& value = + const_cast<FilePath::StringType&>(path->value()); + + const FilePath::StringType::size_type last_dot = + value.rfind(kExtensionSeparator); + const FilePath::StringType::size_type last_separator = + value.find_last_of(FilePath::StringType(FilePath::kSeparators)); + + if (last_dot == FilePath::StringType::npos || + (last_separator != std::wstring::npos && last_dot < last_separator)) { + // The path looks something like "C:\pics.old\jojo" or "C:\pics\jojo". + // We should just append the suffix to the entire path. + value.append(suffix); + return; + } + + value.insert(last_dot, suffix); +} + +bool ContentsEqual(const FilePath& filename1, const FilePath& filename2) { + // We open the file in binary format even if they are text files because + // we are just comparing that bytes are exactly same in both files and not + // doing anything smart with text formatting. + std::ifstream file1(filename1.value().c_str(), + std::ios::in | std::ios::binary); + std::ifstream file2(filename2.value().c_str(), + std::ios::in | std::ios::binary); + + // Even if both files aren't openable (and thus, in some sense, "equal"), + // any unusable file yields a result of "false". + if (!file1.is_open() || !file2.is_open()) + return false; + + const int BUFFER_SIZE = 2056; + char buffer1[BUFFER_SIZE], buffer2[BUFFER_SIZE]; + do { + file1.read(buffer1, BUFFER_SIZE); + file2.read(buffer2, BUFFER_SIZE); + + if ((file1.eof() != file2.eof()) || + (file1.gcount() != file2.gcount()) || + (memcmp(buffer1, buffer2, file1.gcount()))) { + file1.close(); + file2.close(); + return false; + } + } while (!file1.eof() || !file2.eof()); + + file1.close(); + file2.close(); + return true; +} + +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) +bool TextContentsEqual(const FilePath& filename1, const FilePath& filename2) { + std::ifstream file1(filename1.value().c_str(), std::ios::in); + std::ifstream file2(filename2.value().c_str(), std::ios::in); + + // Even if both files aren't openable (and thus, in some sense, "equal"), + // any unusable file yields a result of "false". + if (!file1.is_open() || !file2.is_open()) + return false; + + do { + std::string line1, line2; + getline(file1, line1); + getline(file2, line2); + + // Check for mismatched EOF states, or any error state. + if ((file1.eof() != file2.eof()) || + file1.bad() || file2.bad()) { + return false; + } + + // Trim all '\r' and '\n' characters from the end of the line. + std::string::size_type end1 = line1.find_last_not_of("\r\n"); + if (end1 == std::string::npos) + line1.clear(); + else if (end1 + 1 < line1.length()) + line1.erase(end1 + 1); + + std::string::size_type end2 = line2.find_last_not_of("\r\n"); + if (end2 == std::string::npos) + line2.clear(); + else if (end2 + 1 < line2.length()) + line2.erase(end2 + 1); + + if (line1 != line2) + return false; + } while (!file1.eof() || !file2.eof()); + + return true; +} +#endif + +bool ReadFileToString(const FilePath& path, std::string* contents) { + if (path.ReferencesParent()) + return false; + +#if defined(COBALT) + // Use a smaller buffer so we don't run out of stack space. + const size_t kReadBufferSize = 1 << 12; +#else + const size_t kReadBufferSize = 1 << 16; +#endif + +#if defined(OS_STARBOARD) + base::PlatformFile file = base::CreatePlatformFile( + path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, NULL, NULL); + if (file == base::kInvalidPlatformFileValue) { + return false; + } + + char buf[kReadBufferSize]; + size_t len; + while ((len = base::ReadPlatformFileAtCurrentPos(file, buf, sizeof(buf))) > + 0) { + if (contents) { + contents->append(buf, len); + } + } + base::ClosePlatformFile(file); + + return true; +#else + FILE* file = OpenFile(path, "rb"); + if (!file) { + return false; + } + + char buf[kReadBufferSize]; + size_t len; + while ((len = fread(buf, 1, sizeof(buf), file)) > 0) { + if (contents) + contents->append(buf, len); + } + CloseFile(file); + + return true; +#endif +} + +bool IsDirectoryEmpty(const FilePath& dir_path) { + FileEnumerator files(dir_path, false, + FileEnumerator::FILES | FileEnumerator::DIRECTORIES); + if (files.Next().value().empty()) + return true; + return false; +} + +#if !defined(OS_STARBOARD) +FILE* CreateAndOpenTemporaryFile(FilePath* path) { + FilePath directory; + if (!GetTempDir(&directory)) + return NULL; + + return CreateAndOpenTemporaryFileInDir(directory, path); +} +#endif // !defined(OS_STARBOARD) + +bool GetFileSize(const FilePath& file_path, int64* file_size) { + base::PlatformFileInfo info; + if (!GetFileInfo(file_path, &info)) + return false; + *file_size = info.size; + return true; +} + +bool IsDot(const FilePath& path) { + return FILE_PATH_LITERAL(".") == path.BaseName().value(); +} + +bool IsDotDot(const FilePath& path) { + return FILE_PATH_LITERAL("..") == path.BaseName().value(); +} + +#if !defined(OS_STARBOARD) +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; + +#if defined(COBALT_WIN) + // Ensure that this makes it through to the CRT. + flags |= base::PLATFORM_FILE_WRITE; +#endif // COBALT_WIN + +#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; +#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); + 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; + return fclose(file) == 0; +} + +bool TruncateFile(FILE* file) { + if (file == NULL) + return false; + long current_offset = ftell(file); + if (current_offset == -1) + return false; +#if defined(OS_WIN) + int fd = _fileno(file); + if (_chsize(fd, current_offset) != 0) + return false; +#else + int fd = fileno(file); + if (ftruncate(fd, current_offset) != 0) + return false; +#endif + return true; +} +#endif // !defined(OS_STARBOARD) + +int GetUniquePathNumber( + const FilePath& path, + const FilePath::StringType& suffix) { + bool have_suffix = !suffix.empty(); + if (!PathExists(path) && + (!have_suffix || !PathExists(FilePath(path.value() + suffix)))) { + return 0; + } + + FilePath new_path; + for (int count = 1; count <= kMaxUniqueFiles; ++count) { + new_path = path.InsertBeforeExtensionASCII(StringPrintf(" (%d)", count)); + if (!PathExists(new_path) && + (!have_suffix || !PathExists(FilePath(new_path.value() + suffix)))) { + return count; + } + } + + return -1; +} + +bool ContainsPath(const FilePath &parent, const FilePath& child) { + FilePath abs_parent = FilePath(parent); + FilePath abs_child = FilePath(child); + + if (!file_util::AbsolutePath(&abs_parent) || + !file_util::AbsolutePath(&abs_child)) + return false; + +#if defined(OS_WIN) + // file_util::AbsolutePath() does not flatten case on Windows, so we must do + // a case-insensitive compare. + if (!StartsWith(abs_child.value(), abs_parent.value(), false)) +#else + if (!StartsWithASCII(abs_child.value(), abs_parent.value(), true)) +#endif + return false; + + // file_util::AbsolutePath() normalizes '/' to '\' on Windows, so we only need + // to check kSeparators[0]. + if (abs_child.value().length() <= abs_parent.value().length() || + abs_child.value()[abs_parent.value().length()] != + FilePath::kSeparators[0]) + return false; + + return true; +} + +int64 ComputeDirectorySize(const FilePath& root_path) { + int64 running_size = 0; + FileEnumerator file_iter(root_path, true, FileEnumerator::FILES); + for (FilePath current = file_iter.Next(); !current.empty(); + current = file_iter.Next()) { + FileEnumerator::FindInfo info; + file_iter.GetFindInfo(&info); +#if defined(OS_WIN) + LARGE_INTEGER li = { info.nFileSizeLow, info.nFileSizeHigh }; + running_size += li.QuadPart; +#elif defined(OS_STARBOARD) + running_size += info.sb_info.size; +#else + running_size += info.stat.st_size; +#endif + } + return running_size; +} + +int64 ComputeFilesSize(const FilePath& directory, + const FilePath::StringType& pattern) { + int64 running_size = 0; + FileEnumerator file_iter(directory, false, FileEnumerator::FILES, pattern); + for (FilePath current = file_iter.Next(); !current.empty(); + current = file_iter.Next()) { + FileEnumerator::FindInfo info; + file_iter.GetFindInfo(&info); +#if defined(OS_WIN) + LARGE_INTEGER li = { info.nFileSizeLow, info.nFileSizeHigh }; + running_size += li.QuadPart; +#elif defined(OS_STARBOARD) + running_size += info.sb_info.size; +#else + running_size += info.stat.st_size; +#endif + } + return running_size; +} + +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) +/////////////////////////////////////////////// +// MemoryMappedFile + +MemoryMappedFile::~MemoryMappedFile() { + CloseHandles(); +} + +bool MemoryMappedFile::Initialize(const FilePath& file_name) { + if (IsValid()) + return false; + + if (!MapFileToMemory(file_name)) { + CloseHandles(); + return false; + } + + return true; +} + +bool MemoryMappedFile::Initialize(base::PlatformFile file) { + if (IsValid()) + return false; + + file_ = file; + + if (!MapFileToMemoryInternal()) { + CloseHandles(); + return false; + } + + return true; +} + +bool MemoryMappedFile::IsValid() const { + return data_ != NULL; +} + +bool MemoryMappedFile::MapFileToMemory(const FilePath& file_name) { + file_ = base::CreatePlatformFile( + file_name, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, + NULL, NULL); + + if (file_ == base::kInvalidPlatformFileValue) { + DLOG(ERROR) << "Couldn't open " << file_name.value(); + return false; + } + + return MapFileToMemoryInternal(); +} +#endif + +/////////////////////////////////////////////// +// FileEnumerator +// +// Note: the main logic is in file_util_<platform>.cc + +bool FileEnumerator::ShouldSkip(const FilePath& path) { + FilePath::StringType basename = path.BaseName().value(); + return IsDot(path) || (IsDotDot(path) && !(INCLUDE_DOT_DOT & file_type_)); +} + +} // namespace
diff --git a/src/base/file_util.h b/src/base/file_util.h new file mode 100644 index 0000000..4392627 --- /dev/null +++ b/src/base/file_util.h
@@ -0,0 +1,679 @@ +// 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. + +// This file contains utility functions for dealing with the local +// filesystem. + +#ifndef BASE_FILE_UTIL_H_ +#define BASE_FILE_UTIL_H_ + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#elif defined(OS_POSIX) +#include <sys/stat.h> +#include <unistd.h> +#elif defined(OS_STARBOARD) +#include "starboard/file.h" +#endif + +#include <stdio.h> + +#include <set> +#include <stack> +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/platform_file.h" +#include "base/string16.h" + +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#endif + +namespace base { +class Time; +} + +namespace file_util { + +extern bool g_bug108724_debug; + +//----------------------------------------------------------------------------- +// Functions that operate purely on a path string w/o touching the filesystem: + +// Returns true if the given path ends with a path separator character. +BASE_EXPORT bool EndsWithSeparator(const FilePath& path); + +// Makes sure that |path| ends with a separator IFF path is a directory that +// exists. Returns true if |path| is an existing directory, false otherwise. +BASE_EXPORT bool EnsureEndsWithSeparator(FilePath* path); + +// Convert provided relative path into an absolute path. Returns false on +// error. On POSIX, this function fails if the path does not exist. +BASE_EXPORT bool AbsolutePath(FilePath* path); + +// Returns true if |parent| contains |child|. Both paths are converted to +// absolute paths before doing the comparison. +BASE_EXPORT bool ContainsPath(const FilePath& parent, const FilePath& child); + +//----------------------------------------------------------------------------- +// Functions that involve filesystem access or modification: + +#if !defined(OS_STARBOARD) +// Returns the number of files matching the current path that were +// created on or after the given |file_time|. Doesn't count ".." or ".". +// +// Note for POSIX environments: a file created before |file_time| +// can be mis-detected as a newer file due to low precision of +// timestmap of file creation time. If you need to avoid such +// mis-detection perfectly, you should wait one second before +// obtaining |file_time|. +BASE_EXPORT int CountFilesCreatedAfter(const FilePath& path, + const base::Time& file_time); +#endif + +// Returns the total number of bytes used by all the files under |root_path|. +// If the path does not exist the function returns 0. +// +// This function is implemented using the FileEnumerator class so it is not +// particularly speedy in any platform. +BASE_EXPORT int64 ComputeDirectorySize(const FilePath& root_path); + +// Returns the total number of bytes used by all files matching the provided +// |pattern|, on this |directory| (without recursion). If the path does not +// exist the function returns 0. +// +// This function is implemented using the FileEnumerator class so it is not +// particularly speedy in any platform. +BASE_EXPORT int64 ComputeFilesSize(const FilePath& directory, + const FilePath::StringType& pattern); + +// Deletes the given path, whether it's a file or a directory. +// If it's a directory, it's perfectly happy to delete all of the +// directory's contents. Passing true to recursive deletes +// subdirectories and their contents as well. +// Returns true if successful, false otherwise. +// +// In posix environment and if |path| is a symbolic link, this deletes only +// the symlink. (even if the symlink points to a non-existent file) +// +// WARNING: USING THIS WITH recursive==true IS EQUIVALENT +// TO "rm -rf", SO USE WITH CAUTION. +BASE_EXPORT bool Delete(const FilePath& path, bool recursive); + +#if defined(OS_WIN) +// Schedules to delete the given path, whether it's a file or a directory, until +// the operating system is restarted. +// Note: +// 1) The file/directory to be deleted should exist in a temp folder. +// 2) The directory to be deleted must be empty. +BASE_EXPORT bool DeleteAfterReboot(const FilePath& path); +#endif + +#if !defined(OS_STARBOARD) +// NOTE: file_util::Move is used in a small number of places. We won't +// implement it in Starboard for the time being, but we may bring it back if it +// is is deemed necessary. + +// Moves the given path, whether it's a file or a directory. +// If a simple rename is not possible, such as in the case where the paths are +// on different volumes, this will attempt to copy and delete. Returns +// true for success. +BASE_EXPORT bool Move(const FilePath& from_path, const FilePath& to_path); + +// Renames file |from_path| to |to_path|. Both paths must be on the same +// volume, or the function will fail. Destination file will be created +// if it doesn't exist. Prefer this function over Move when dealing with +// temporary files. On Windows it preserves attributes of the target file. +// Returns true on success. +BASE_EXPORT bool ReplaceFile(const FilePath& from_path, + const FilePath& to_path); +#endif + +// Copies a single file. Use CopyDirectory to copy directories. +BASE_EXPORT bool CopyFile(const FilePath& from_path, const FilePath& to_path); + +#if !defined(OS_STARBOARD) +// NOTE: file_util::CopyDirectory is only referenced by disabled tests. + +// Copies the given path, and optionally all subdirectories and their contents +// as well. +// If there are files existing under to_path, always overwrite. +// Returns true if successful, false otherwise. +// Don't use wildcards on the names, it may stop working without notice. +// +// If you only need to copy a file use CopyFile, it's faster. +BASE_EXPORT bool CopyDirectory(const FilePath& from_path, + const FilePath& to_path, + bool recursive); +#endif + +// Returns true if the given path exists on the local filesystem, +// false otherwise. +BASE_EXPORT bool PathExists(const FilePath& path); + +// Returns true if the given path is writable by the user, false otherwise. +BASE_EXPORT bool PathIsWritable(const FilePath& path); + +// Returns true if the given path exists and is a directory, false otherwise. +BASE_EXPORT bool DirectoryExists(const FilePath& path); + +#if defined(OS_WIN) +// Gets the creation time of the given file (expressed in the local timezone), +// and returns it via the creation_time parameter. Returns true if successful, +// false otherwise. +BASE_EXPORT bool GetFileCreationLocalTime(const std::wstring& filename, + LPSYSTEMTIME creation_time); + +// Same as above, but takes a previously-opened file handle instead of a name. +BASE_EXPORT bool GetFileCreationLocalTimeFromHandle(HANDLE file_handle, + LPSYSTEMTIME creation_time); +#endif // defined(OS_WIN) + +// Returns true if the contents of the two files given are equal, false +// otherwise. If either file can't be read, returns false. +BASE_EXPORT bool ContentsEqual(const FilePath& filename1, + const FilePath& filename2); + +#if !defined(__LB_SHELL__) && !defined(COBALT) +// Returns true if the contents of the two text files given are equal, false +// otherwise. This routine treats "\r\n" and "\n" as equivalent. +BASE_EXPORT bool TextContentsEqual(const FilePath& filename1, + const FilePath& filename2); +#endif + +// Read the file at |path| into |contents|, returning true on success. +// This function fails if the |path| contains path traversal components ('..'). +// |contents| may be NULL, in which case this function is useful for its +// side effect of priming the disk cache. +// Useful for unit tests. +BASE_EXPORT bool ReadFileToString(const FilePath& path, std::string* contents); + +#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 successfuly read from |fd|. +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 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 FilePath& symlink, FilePath* target); + +// Bits ans masks of the file permission. +enum FilePermissionBits { + FILE_PERMISSION_MASK = S_IRWXU | S_IRWXG | S_IRWXO, + FILE_PERMISSION_USER_MASK = S_IRWXU, + FILE_PERMISSION_GROUP_MASK = S_IRWXG, + FILE_PERMISSION_OTHERS_MASK = S_IRWXO, + + FILE_PERMISSION_READ_BY_USER = S_IRUSR, + FILE_PERMISSION_WRITE_BY_USER = S_IWUSR, + FILE_PERMISSION_EXECUTE_BY_USER = S_IXUSR, + FILE_PERMISSION_READ_BY_GROUP = S_IRGRP, + FILE_PERMISSION_WRITE_BY_GROUP = S_IWGRP, + FILE_PERMISSION_EXECUTE_BY_GROUP = S_IXGRP, + FILE_PERMISSION_READ_BY_OTHERS = S_IROTH, + FILE_PERMISSION_WRITE_BY_OTHERS = S_IWOTH, + FILE_PERMISSION_EXECUTE_BY_OTHERS = S_IXOTH, +}; + +// 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 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 FilePath& path, + int mode); +#endif // defined(OS_POSIX) + +#if defined(OS_WIN) +// Copy from_path to to_path recursively and then delete from_path recursively. +// Returns true if all operations succeed. +// This function simulates Move(), but unlike Move() it works across volumes. +// This fuction is not transactional. +BASE_EXPORT bool CopyAndDeleteDirectory(const FilePath& from_path, + const FilePath& to_path); +#endif // defined(OS_WIN) + +// Return 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(FilePath* path); +// Get a temporary directory for shared memory files. +// Only useful on POSIX; redirects to GetTempDir() on Windows. +BASE_EXPORT bool GetShmemTempDir(FilePath* path, bool executable); + +// Get the home directory. This is more complicated than just getenv("HOME") +// as it knows to fall back on getpwent() etc. +BASE_EXPORT FilePath GetHomeDir(); + +// 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(FilePath* path); + +// Same as CreateTemporaryFile but the file is created in |dir|. +BASE_EXPORT bool CreateTemporaryFileInDir(const FilePath& dir, + FilePath* temp_file); + +#if !defined(OS_STARBOARD) +// 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 occured. +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(FilePath* path, + bool executable); +// Similar to CreateAndOpenTemporaryFile, but the file is created in |dir|. +BASE_EXPORT FILE* CreateAndOpenTemporaryFileInDir(const FilePath& dir, + FilePath* path); +#endif + +// 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 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 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. +BASE_EXPORT bool CreateDirectory(const FilePath& full_path); + +// Returns the file size. Returns true on success. +BASE_EXPORT bool GetFileSize(const FilePath& file_path, int64* file_size); + +// Returns true if the given path's base name is ".". +BASE_EXPORT bool IsDot(const FilePath& path); + +// Returns true if the given path's base name is "..". +BASE_EXPORT bool IsDotDot(const FilePath& path); + +#if !defined(OS_STARBOARD) +// Sets |real_path| to |path| with symbolic links and junctions expanded. +// On windows, make sure the path starts with a lettered drive. +// |path| must reference a file. Function will fail if |path| points to +// 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 FilePath& path, FilePath* real_path); +#endif + +#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 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 FilePath& path, + FilePath* nt_path); +#endif + +// This function will return if the given file is a symlink or not. +BASE_EXPORT bool IsLink(const FilePath& file_path); + +// Returns information about the given file path. +BASE_EXPORT bool GetFileInfo(const FilePath& file_path, + base::PlatformFileInfo* info); + +#if !defined(OS_STARBOARD) +// Sets the time of the last access and the time of the last modification. +BASE_EXPORT bool TouchFile(const 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 FilePath& path, + const base::Time& last_modified); + +#if defined(OS_POSIX) && !defined(__LB_WIIU__) +// Store inode number of |path| in |inode|. Return true on success. +BASE_EXPORT bool GetInode(const FilePath& path, ino_t* inode); +#endif + +// Wrapper for fopen-like calls. Returns non-NULL FILE* on success. +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); + +// Truncates an open file to end at the location of the current file pointer. +// This is a cross-platform analog to Windows' SetEndOfFile() function. +BASE_EXPORT bool TruncateFile(FILE* file); +#endif + +// Reads the given number of bytes from the file into the buffer. Returns +// the number of read bytes, or -1 on error. +BASE_EXPORT int ReadFile(const FilePath& filename, char* data, int size); + +// 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 FilePath& filename, const char* data, int size); + +#if defined(OS_POSIX) +// Append the data to |fd|. Does not close |fd| when done. +BASE_EXPORT int WriteFileDescriptor(const int fd, const char* data, int size); +#endif + +// Append the given buffer into the file. Returns the number of bytes written, +// or -1 on error. +BASE_EXPORT int AppendToFile(const FilePath& filename, + const char* data, int size); + +#if !defined(OS_STARBOARD) +// Gets the current working directory for the process. +BASE_EXPORT bool GetCurrentDirectory(FilePath* path); + +// Sets the current working directory for the process. +BASE_EXPORT bool SetCurrentDirectory(const FilePath& path); +#endif + +// Attempts to find a number that can be appended to the |path| to make it +// unique. If |path| does not exist, 0 is returned. If it fails to find such +// a number, -1 is returned. If |suffix| is not empty, also checks the +// existence of it with the given suffix. +BASE_EXPORT int GetUniquePathNumber(const FilePath& path, + const FilePath::StringType& suffix); + +#if defined(OS_POSIX) +// Test that |path| can only be changed by a given user and members of +// a given set of groups. +// Specifically, test that all parts of |path| under (and including) |base|: +// * Exist. +// * Are owned by a specific user. +// * Are not writable by all users. +// * Are owned by a memeber of a given set of groups, or are not writable by +// their group. +// * Are not symbolic links. +// This is useful for checking that a config file is administrator-controlled. +// |base| must contain |path|. +BASE_EXPORT bool VerifyPathControlledByUser(const FilePath& base, + const FilePath& path, + uid_t owner_uid, + const std::set<gid_t>& group_gids); +#endif // defined(OS_POSIX) + +#if defined(OS_MACOSX) && !defined(OS_IOS) +// Is |path| writable only by a user with administrator privileges? +// This function uses Mac OS conventions. The super user is assumed to have +// uid 0, and the administrator group is assumed to be named "admin". +// Testing that |path|, and every parent directory including the root of +// the filesystem, are owned by the superuser, controlled by the group +// "admin", are not writable by all users, and contain no symbolic links. +// Will return false if |path| does not exist. +BASE_EXPORT bool VerifyPathControlledByAdmin(const FilePath& path); +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + +#if !defined(OS_STARBOARD) +// A class to handle auto-closing of FILE*'s. +class ScopedFILEClose { + public: + inline void operator()(FILE* x) const { + if (x) { + fclose(x); + } + } +}; + +typedef scoped_ptr_malloc<FILE, ScopedFILEClose> ScopedFILE; +#endif + +#if defined(OS_POSIX) +// A class to handle auto-closing of FDs. +class ScopedFDClose { + public: + inline void operator()(int* x) const { + if (x && *x >= 0) { + if (HANDLE_EINTR(close(*x)) < 0) + DPLOG(ERROR) << "close"; + } + } +}; + +typedef scoped_ptr_malloc<int, ScopedFDClose> ScopedFD; +#endif // OS_POSIX + +// A class for enumerating the files in a provided path. The order of the +// results is not guaranteed. +// +// DO NOT USE FROM THE MAIN THREAD of your application unless it is a test +// program where latency does not matter. This class is blocking. +class BASE_EXPORT FileEnumerator { + public: +#if defined(OS_WIN) + typedef WIN32_FIND_DATA FindInfo; +#elif defined(OS_POSIX) + typedef struct { + struct stat stat; + std::string filename; + } FindInfo; +#elif defined(OS_STARBOARD) + typedef struct { + SbFileInfo sb_info; + std::string filename; + } FindInfo; +#endif + + enum FileType { + FILES = 1 << 0, + DIRECTORIES = 1 << 1, + INCLUDE_DOT_DOT = 1 << 2, +#if defined(OS_POSIX) + SHOW_SYM_LINKS = 1 << 4, +#endif + }; + + // |root_path| is the starting directory to search for. It may or may not end + // in a slash. + // + // If |recursive| is true, this will enumerate all matches in any + // subdirectories matched as well. It does a breadth-first search, so all + // files in one directory will be returned before any files in a + // subdirectory. + // + // |file_type|, a bit mask of FileType, specifies whether the enumerator + // should match files, directories, or both. + // + // |pattern| is an optional pattern for which files to match. This + // works like shell globbing. For example, "*.txt" or "Foo???.doc". + // However, be careful in specifying patterns that aren't cross platform + // since the underlying code uses OS-specific matching routines. In general, + // Windows matching is less featureful than others, so test there first. + // If unspecified, this will match all files. + // NOTE: the pattern only matches the contents of root_path, not files in + // recursive subdirectories. + // TODO(erikkay): Fix the pattern matching to work at all levels. + FileEnumerator(const FilePath& root_path, + bool recursive, + int file_type); + FileEnumerator(const FilePath& root_path, + bool recursive, + int file_type, + const FilePath::StringType& pattern); + ~FileEnumerator(); + + // Returns an empty string if there are no more results. + FilePath Next(); + + // Write the file info into |info|. + void GetFindInfo(FindInfo* info); + + // Looks inside a FindInfo and determines if it's a directory. + static bool IsDirectory(const FindInfo& info); + + static FilePath GetFilename(const FindInfo& find_info); + static int64 GetFilesize(const FindInfo& find_info); + static base::Time GetLastModifiedTime(const FindInfo& find_info); + + private: + // Returns true if the given path should be skipped in enumeration. + bool ShouldSkip(const FilePath& path); + + +#if defined(OS_WIN) + // True when find_data_ is valid. + bool has_find_data_; + WIN32_FIND_DATA find_data_; + HANDLE find_handle_; +#elif defined(OS_POSIX) + struct DirectoryEntryInfo { + FilePath filename; + struct stat stat; + }; + + // Read the filenames in source into the vector of DirectoryEntryInfo's + static bool ReadDirectory(std::vector<DirectoryEntryInfo>* entries, + const FilePath& source, bool show_links); + + // The files in the current directory + std::vector<DirectoryEntryInfo> directory_entries_; + + // The next entry to use from the directory_entries_ vector + size_t current_directory_entry_; +#elif defined(OS_STARBOARD) + struct DirectoryEntryInfo { + FilePath filename; + SbFileInfo sb_info; + }; + + // Read the filenames in source into the vector of DirectoryEntryInfo's + static bool ReadDirectory(std::vector<DirectoryEntryInfo>* entries, + const FilePath& source); + + // The files in the current directory + std::vector<DirectoryEntryInfo> directory_entries_; + + // The next entry to use from the directory_entries_ vector + size_t current_directory_entry_; +#endif + + FilePath root_path_; + bool recursive_; + int file_type_; + FilePath::StringType pattern_; // Empty when we want to find everything. + + // A stack that keeps track of which subdirectories we still need to + // enumerate in the breadth-first search. + std::stack<FilePath> pending_paths_; + + DISALLOW_COPY_AND_ASSIGN(FileEnumerator); +}; + +#if !defined(__LB_SHELL__) && !defined(COBALT) +class BASE_EXPORT MemoryMappedFile { + public: + // The default constructor sets all members to invalid/null values. + MemoryMappedFile(); + ~MemoryMappedFile(); + + // Opens an existing file and maps it into memory. Access is restricted to + // read only. If this object already points to a valid memory mapped file + // then this method will fail and return false. If it cannot open the file, + // the file does not exist, or the memory mapping fails, it will return false. + // Later we may want to allow the user to specify access. + bool Initialize(const FilePath& file_name); + // As above, but works with an already-opened file. MemoryMappedFile will take + // ownership of |file| and close it when done. + bool Initialize(base::PlatformFile file); + +#if defined(OS_WIN) + // Opens an existing file and maps it as an image section. Please refer to + // the Initialize function above for additional information. + bool InitializeAsImageSection(const FilePath& file_name); +#endif // OS_WIN + + const uint8* data() const { return data_; } + size_t length() const { return length_; } + + // Is file_ a valid file handle that points to an open, memory mapped file? + bool IsValid() const; + + private: + // Open the given file and pass it to MapFileToMemoryInternal(). + bool MapFileToMemory(const FilePath& file_name); + + // Map the file to memory, set data_ to that memory address. Return true on + // success, false on any kind of failure. This is a helper for Initialize(). + bool MapFileToMemoryInternal(); + + // Closes all open handles. Later we may want to make this public. + void CloseHandles(); + +#if defined(OS_WIN) + // MapFileToMemoryInternal calls this function. It provides the ability to + // pass in flags which control the mapped section. + bool MapFileToMemoryInternalEx(int flags); + + HANDLE file_mapping_; +#endif + base::PlatformFile file_; + uint8* data_; + size_t length_; + + DISALLOW_COPY_AND_ASSIGN(MemoryMappedFile); +}; +#endif + +// Returns whether the file has been modified since a particular date. +BASE_EXPORT bool HasFileBeenModifiedSince( + const FileEnumerator::FindInfo& find_info, + const base::Time& cutoff_time); + +#if defined(OS_LINUX) +// Broad categories of file systems as returned by statfs() on Linux. +enum FileSystemType { + FILE_SYSTEM_UNKNOWN, // statfs failed. + FILE_SYSTEM_0, // statfs.f_type == 0 means unknown, may indicate AFS. + FILE_SYSTEM_ORDINARY, // on-disk filesystem like ext2 + FILE_SYSTEM_NFS, + FILE_SYSTEM_SMB, + FILE_SYSTEM_CODA, + FILE_SYSTEM_MEMORY, // in-memory file system + FILE_SYSTEM_CGROUP, // cgroup control. + FILE_SYSTEM_OTHER, // any other value. + FILE_SYSTEM_TYPE_COUNT +}; + +// Attempts determine the FileSystemType for |path|. +// Returns false if |path| doesn't exist. +BASE_EXPORT bool GetFileSystemType(const FilePath& path, FileSystemType* type); +#endif + +} // namespace file_util + +#endif // BASE_FILE_UTIL_H_
diff --git a/src/base/file_util_android.cc b/src/base/file_util_android.cc new file mode 100644 index 0000000..79db279 --- /dev/null +++ b/src/base/file_util_android.cc
@@ -0,0 +1,16 @@ +// 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/file_path.h" +#include "base/path_service.h" + +namespace file_util { + +bool GetShmemTempDir(FilePath* path, bool executable) { + return PathService::Get(base::DIR_CACHE, path); +} + +} // namespace file_util
diff --git a/src/base/file_util_linux.cc b/src/base/file_util_linux.cc new file mode 100644 index 0000000..f7d4f6e --- /dev/null +++ b/src/base/file_util_linux.cc
@@ -0,0 +1,62 @@ +// 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/file_util.h" + +#include "base/file_path.h" + +#include <errno.h> +#include <sys/vfs.h> + +namespace file_util { + +bool GetFileSystemType(const FilePath& path, FileSystemType* type) { + struct statfs statfs_buf; + if (statfs(path.value().c_str(), &statfs_buf) < 0) { + if (errno == ENOENT) + return false; + *type = FILE_SYSTEM_UNKNOWN; + return true; + } + + // While you would think the possible values of f_type would be available + // in a header somewhere, it appears that is not the case. These values + // are copied from the statfs man page. + switch (statfs_buf.f_type) { + case 0: + *type = FILE_SYSTEM_0; + break; + case 0xEF53: // ext2, ext3. + case 0x4D44: // dos + case 0x5346544E: // NFTS + case 0x52654973: // reiser + case 0x58465342: // XFS + case 0x9123683E: // btrfs + case 0x3153464A: // JFS + *type = FILE_SYSTEM_ORDINARY; + break; + case 0x6969: // NFS + *type = FILE_SYSTEM_NFS; + break; + case 0xFF534D42: // CIFS + case 0x517B: // SMB + *type = FILE_SYSTEM_SMB; + break; + case 0x73757245: // Coda + *type = FILE_SYSTEM_CODA; + break; + case 0x858458f6: // ramfs + case 0x01021994: // tmpfs + *type = FILE_SYSTEM_MEMORY; + break; + case 0x27e0eb: // CGROUP + *type = FILE_SYSTEM_CGROUP; + break; + default: + *type = FILE_SYSTEM_OTHER; + } + return true; +} + +} // namespace
diff --git a/src/base/file_util_mac.mm b/src/base/file_util_mac.mm new file mode 100644 index 0000000..0638167 --- /dev/null +++ b/src/base/file_util_mac.mm
@@ -0,0 +1,36 @@ +// 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" + +#import <Foundation/Foundation.h> +#include <copyfile.h> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/mac/foundation_util.h" +#include "base/string_util.h" +#include "base/threading/thread_restrictions.h" + +namespace file_util { + +bool GetTempDir(FilePath* path) { + NSString* tmp = NSTemporaryDirectory(); + if (tmp == nil) + return false; + *path = base::mac::NSStringToFilePath(tmp); + return true; +} + +bool GetShmemTempDir(FilePath* path, bool executable) { + return GetTempDir(path); +} + +bool CopyFile(const FilePath& from_path, const FilePath& to_path) { + base::ThreadRestrictions::AssertIOAllowed(); + return (copyfile(from_path.value().c_str(), + to_path.value().c_str(), NULL, COPYFILE_ALL) == 0); +} + +} // namespace
diff --git a/src/base/file_util_posix.cc b/src/base/file_util_posix.cc new file mode 100644 index 0000000..6a4a309 --- /dev/null +++ b/src/base/file_util_posix.cc
@@ -0,0 +1,1177 @@ +// 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 <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <fnmatch.h> +#include <libgen.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/errno.h> +#include <sys/mman.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#if defined(OS_MACOSX) +#include <AvailabilityMacros.h> +#include "base/mac/foundation_util.h" +#elif !defined(OS_ANDROID) && !defined(__LB_SHELL__) +#include <glib.h> +#endif + +#include <fstream> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/path_service.h" +#include "base/posix/eintr_wrapper.h" +#include "base/stl_util.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/sys_string_conversions.h" +#include "base/threading/thread_restrictions.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" + +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) +#include "base/os_compat_android.h" +#endif + +#if !defined(OS_IOS) && !defined(__LB_SHELL__) +#include <grp.h> +#endif + +#if defined(OS_CHROMEOS) +#include "base/chromeos/chromeos_version.h" +#endif + +namespace file_util { + +namespace { + +#if defined(OS_BSD) || defined(OS_MACOSX) || defined(__LB_SHELL__) +typedef struct stat stat_wrapper_t; +static int CallStat(const char *path, stat_wrapper_t *sb) { + base::ThreadRestrictions::AssertIOAllowed(); + return stat(path, sb); +} +static int CallLstat(const char *path, stat_wrapper_t *sb) { + base::ThreadRestrictions::AssertIOAllowed(); + return lstat(path, sb); +} +#else +typedef struct stat64 stat_wrapper_t; +static int CallStat(const char *path, stat_wrapper_t *sb) { + base::ThreadRestrictions::AssertIOAllowed(); + return stat64(path, sb); +} +static int CallLstat(const char *path, stat_wrapper_t *sb) { + base::ThreadRestrictions::AssertIOAllowed(); + return lstat64(path, sb); +} +#endif + +#if !defined(__LB_PS4__) && !defined(__LB_ANDROID__) +// Helper for NormalizeFilePath(), defined below. +bool RealPath(const FilePath& path, FilePath* real_path) { + base::ThreadRestrictions::AssertIOAllowed(); // For realpath(). + FilePath::CharType buf[PATH_MAX]; + if (!realpath(path.value().c_str(), buf)) + return false; + + *real_path = FilePath(buf); + return true; +} +#endif + +// Helper for VerifyPathControlledByUser. +bool VerifySpecificPathControlledByUser(const FilePath& path, + uid_t owner_uid, + const std::set<gid_t>& group_gids) { + stat_wrapper_t stat_info; + if (CallLstat(path.value().c_str(), &stat_info) != 0) { + DPLOG(ERROR) << "Failed to get information on path " + << path.value(); + return false; + } + + if (S_ISLNK(stat_info.st_mode)) { + DLOG(ERROR) << "Path " << path.value() + << " is a symbolic link."; + return false; + } + + if (stat_info.st_uid != owner_uid) { + DLOG(ERROR) << "Path " << path.value() + << " is owned by the wrong user."; + return false; + } + + if ((stat_info.st_mode & S_IWGRP) && + !ContainsKey(group_gids, stat_info.st_gid)) { + DLOG(ERROR) << "Path " << path.value() + << " is writable by an unprivileged group."; + return false; + } + + if (stat_info.st_mode & S_IWOTH) { + DLOG(ERROR) << "Path " << path.value() + << " is writable by any user."; + return false; + } + + return true; +} + +} // namespace + +static std::string TempFileName() { +#if defined(OS_MACOSX) + return StringPrintf(".%s.XXXXXX", base::mac::BaseBundleID()); +#endif + +#if defined(GOOGLE_CHROME_BUILD) + return std::string(".com.google.Chrome.XXXXXX"); +#else + return std::string(".org.chromium.Chromium.XXXXXX"); +#endif +} + +#if !defined(__LB_PS4__) && !defined(__LB_ANDROID__) +bool AbsolutePath(FilePath* path) { + base::ThreadRestrictions::AssertIOAllowed(); // For realpath(). + char full_path[PATH_MAX]; + if (realpath(path->value().c_str(), full_path) == NULL) + return false; + *path = FilePath(full_path); + return true; +} +#endif + +int CountFilesCreatedAfter(const FilePath& path, + const base::Time& comparison_time) { + base::ThreadRestrictions::AssertIOAllowed(); + int file_count = 0; + + DIR* dir = opendir(path.value().c_str()); + if (dir) { +#if !defined(OS_LINUX) && !defined(OS_MACOSX) && !defined(OS_BSD) && \ + !defined(OS_SOLARIS) && !defined(OS_ANDROID) && !defined(__LB_SHELL__) + #error Port warning: depending on the definition of struct dirent, \ + additional space for pathname may be needed +#endif + struct dirent ent_buf; + struct dirent* ent; + while (readdir_r(dir, &ent_buf, &ent) == 0 && ent) { + if ((strcmp(ent->d_name, ".") == 0) || + (strcmp(ent->d_name, "..") == 0)) + continue; + + stat_wrapper_t st; + int test = CallStat(path.Append(ent->d_name).value().c_str(), &st); + if (test != 0) { + DPLOG(ERROR) << "stat64 failed"; + continue; + } + // Here, we use Time::TimeT(), which discards microseconds. This + // means that files which are newer than |comparison_time| may + // be considered older. If we don't discard microseconds, it + // introduces another issue. Suppose the following case: + // + // 1. Get |comparison_time| by Time::Now() and the value is 10.1 (secs). + // 2. Create a file and the current time is 10.3 (secs). + // + // As POSIX doesn't have microsecond precision for |st_ctime|, + // the creation time of the file created in the step 2 is 10 and + // the file is considered older than |comparison_time|. After + // all, we may have to accept either of the two issues: 1. files + // which are older than |comparison_time| are considered newer + // (current implementation) 2. files newer than + // |comparison_time| are considered older. + if (static_cast<time_t>(st.st_ctime) >= comparison_time.ToTimeT()) + ++file_count; + } + closedir(dir); + } + return file_count; +} + +// TODO(erikkay): The Windows version of this accepts paths like "foo/bar/*" +// which works both with and without the recursive flag. I'm not sure we need +// that functionality. If not, remove from file_util_win.cc, otherwise add it +// here. +bool Delete(const FilePath& path, bool recursive) { + base::ThreadRestrictions::AssertIOAllowed(); + const char* path_str = path.value().c_str(); + stat_wrapper_t file_info; + int test = CallLstat(path_str, &file_info); + if (test != 0) { + // The Windows version defines this condition as success. + bool ret = (errno == ENOENT || errno == ENOTDIR); + return ret; + } + if (!S_ISDIR(file_info.st_mode)) + return (unlink(path_str) == 0); + if (!recursive) + return (rmdir(path_str) == 0); + + bool success = true; + std::stack<std::string> directories; + directories.push(path.value()); + FileEnumerator traversal(path, true, + FileEnumerator::FILES | FileEnumerator::DIRECTORIES | + FileEnumerator::SHOW_SYM_LINKS); + for (FilePath current = traversal.Next(); success && !current.empty(); + current = traversal.Next()) { + FileEnumerator::FindInfo info; + traversal.GetFindInfo(&info); + + if (S_ISDIR(info.stat.st_mode)) + directories.push(current.value()); + else + success = (unlink(current.value().c_str()) == 0); + } + + while (success && !directories.empty()) { + FilePath dir = FilePath(directories.top()); + directories.pop(); + success = (rmdir(dir.value().c_str()) == 0); + } + return success; +} + +bool Move(const FilePath& from_path, const FilePath& to_path) { + base::ThreadRestrictions::AssertIOAllowed(); + // Windows compatibility: if to_path exists, from_path and to_path + // must be the same type, either both files, or both directories. + stat_wrapper_t to_file_info; + if (CallStat(to_path.value().c_str(), &to_file_info) == 0) { + stat_wrapper_t from_file_info; + if (CallStat(from_path.value().c_str(), &from_file_info) == 0) { + if (S_ISDIR(to_file_info.st_mode) != S_ISDIR(from_file_info.st_mode)) + return false; + } else { + return false; + } + } + + if (rename(from_path.value().c_str(), to_path.value().c_str()) == 0) + return true; + + if (!CopyDirectory(from_path, to_path, true)) + return false; + + Delete(from_path, true); + return true; +} + +bool ReplaceFile(const FilePath& from_path, const FilePath& to_path) { + base::ThreadRestrictions::AssertIOAllowed(); + return (rename(from_path.value().c_str(), to_path.value().c_str()) == 0); +} + +bool CopyDirectory(const FilePath& from_path, + const FilePath& to_path, + bool recursive) { + base::ThreadRestrictions::AssertIOAllowed(); + // Some old callers of CopyDirectory want it to support wildcards. + // After some discussion, we decided to fix those callers. + // Break loudly here if anyone tries to do this. + // TODO(evanm): remove this once we're sure it's ok. + DCHECK(to_path.value().find('*') == std::string::npos); + DCHECK(from_path.value().find('*') == std::string::npos); + + char top_dir[PATH_MAX]; + if (base::strlcpy(top_dir, from_path.value().c_str(), + arraysize(top_dir)) >= arraysize(top_dir)) { + return false; + } + + // This function does not properly handle destinations within the source + FilePath real_to_path = to_path; + if (PathExists(real_to_path)) { + if (!AbsolutePath(&real_to_path)) + return false; + } else { + real_to_path = real_to_path.DirName(); + if (!AbsolutePath(&real_to_path)) + return false; + } + FilePath real_from_path = from_path; + if (!AbsolutePath(&real_from_path)) + return false; + if (real_to_path.value().size() >= real_from_path.value().size() && + real_to_path.value().compare(0, real_from_path.value().size(), + real_from_path.value()) == 0) + return false; + + bool success = true; + int traverse_type = FileEnumerator::FILES | FileEnumerator::SHOW_SYM_LINKS; + if (recursive) + traverse_type |= FileEnumerator::DIRECTORIES; + FileEnumerator traversal(from_path, recursive, traverse_type); + + // We have to mimic windows behavior here. |to_path| may not exist yet, + // start the loop with |to_path|. + FileEnumerator::FindInfo info; + FilePath current = from_path; + if (stat(from_path.value().c_str(), &info.stat) < 0) { + DLOG(ERROR) << "CopyDirectory() couldn't stat source directory: " + << from_path.value() << " errno = " << errno; + success = false; + } + struct stat to_path_stat; + FilePath from_path_base = from_path; + if (recursive && stat(to_path.value().c_str(), &to_path_stat) == 0 && + S_ISDIR(to_path_stat.st_mode)) { + // If the destination already exists and is a directory, then the + // top level of source needs to be copied. + from_path_base = from_path.DirName(); + } + + // The Windows version of this function assumes that non-recursive calls + // will always have a directory for from_path. + DCHECK(recursive || S_ISDIR(info.stat.st_mode)); + + while (success && !current.empty()) { + // current is the source path, including from_path, so append + // the suffix after from_path to to_path to create the target_path. + FilePath target_path(to_path); + if (from_path_base != current) { + if (!from_path_base.AppendRelativePath(current, &target_path)) { + success = false; + break; + } + } + + if (S_ISDIR(info.stat.st_mode)) { + if (mkdir(target_path.value().c_str(), info.stat.st_mode & 01777) != 0 && + errno != EEXIST) { + DLOG(ERROR) << "CopyDirectory() couldn't create directory: " + << target_path.value() << " errno = " << errno; + success = false; + } + } else if (S_ISREG(info.stat.st_mode)) { + if (!CopyFile(current, target_path)) { + DLOG(ERROR) << "CopyDirectory() couldn't create file: " + << target_path.value(); + success = false; + } + } else { + DLOG(WARNING) << "CopyDirectory() skipping non-regular file: " + << current.value(); + } + + current = traversal.Next(); + traversal.GetFindInfo(&info); + } + + return success; +} + +bool PathExists(const FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + return access(path.value().c_str(), F_OK) == 0; +} + +bool PathIsWritable(const FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + return access(path.value().c_str(), W_OK) == 0; +} + +#if !defined(__LB_XB1__) && !defined(__LB_XB360__) +bool DirectoryExists(const FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + stat_wrapper_t file_info; + if (CallStat(path.value().c_str(), &file_info) == 0) + return S_ISDIR(file_info.st_mode); + return false; +} +#endif + +// TODO(erikkay): implement +#if 0 +bool GetFileCreationLocalTimeFromHandle(int fd, + LPSYSTEMTIME creation_time) { + if (!file_handle) + return false; + + FILETIME utc_filetime; + if (!GetFileTime(file_handle, &utc_filetime, NULL, NULL)) + return false; + + FILETIME local_filetime; + if (!FileTimeToLocalFileTime(&utc_filetime, &local_filetime)) + return false; + + return !!FileTimeToSystemTime(&local_filetime, creation_time); +} + +bool GetFileCreationLocalTime(const std::string& filename, + LPSYSTEMTIME creation_time) { + ScopedHandle file_handle( + CreateFile(filename.c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); + return GetFileCreationLocalTimeFromHandle(file_handle.Get(), creation_time); +} +#endif + +bool ReadFromFD(int fd, char* buffer, size_t bytes) { + size_t total_read = 0; + while (total_read < bytes) { + ssize_t bytes_read = + HANDLE_EINTR(read(fd, buffer + total_read, bytes - total_read)); + if (bytes_read <= 0) + break; + total_read += bytes_read; + } + return total_read == bytes; +} + +bool CreateSymbolicLink(const FilePath& target_path, + const FilePath& symlink_path) { + DCHECK(!symlink_path.empty()); + DCHECK(!target_path.empty()); + return ::symlink(target_path.value().c_str(), + symlink_path.value().c_str()) != -1; +} + +bool ReadSymbolicLink(const FilePath& symlink_path, + FilePath* target_path) { + DCHECK(!symlink_path.empty()); + DCHECK(target_path); + char buf[PATH_MAX]; + ssize_t count = ::readlink(symlink_path.value().c_str(), buf, arraysize(buf)); + + if (count <= 0) { + target_path->clear(); + return false; + } + + *target_path = FilePath(FilePath::StringType(buf, count)); + return true; +} + +bool GetPosixFilePermissions(const FilePath& path, int* mode) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(mode); + + stat_wrapper_t file_info; + // Uses stat(), because on symbolic link, lstat() does not return valid + // permission bits in st_mode + if (CallStat(path.value().c_str(), &file_info) != 0) + return false; + + *mode = file_info.st_mode & FILE_PERMISSION_MASK; + return true; +} + +bool SetPosixFilePermissions(const FilePath& path, + int mode) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK((mode & ~FILE_PERMISSION_MASK) == 0); + + // Calls stat() so that we can preserve the higher bits like S_ISGID. + stat_wrapper_t stat_buf; + if (CallStat(path.value().c_str(), &stat_buf) != 0) + return false; + + // Clears the existing permission bits, and adds the new ones. + mode_t updated_mode_bits = stat_buf.st_mode & ~FILE_PERMISSION_MASK; + updated_mode_bits |= mode & FILE_PERMISSION_MASK; + + if (HANDLE_EINTR(chmod(path.value().c_str(), updated_mode_bits)) != 0) + return false; + + 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(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)); +} + +bool CreateTemporaryFile(FilePath* path) { + base::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))); + return true; +} + +#if !defined(__LB_SHELL__) +FILE* CreateAndOpenTemporaryShmemFile(FilePath* path, bool executable) { + FilePath directory; + if (!GetShmemTempDir(&directory, executable)) + return NULL; + + return CreateAndOpenTemporaryFileInDir(directory, path); +} +#endif + +FILE* CreateAndOpenTemporaryFileInDir(const FilePath& dir, FilePath* path) { + int fd = CreateAndOpenFdForTemporaryFile(dir, path); + if (fd < 0) + return NULL; + + FILE* file = fdopen(fd, "a+"); + if (!file) + ignore_result(HANDLE_EINTR(close(fd))); + return file; +} + +bool CreateTemporaryFileInDir(const FilePath& dir, FilePath* temp_file) { + base::ThreadRestrictions::AssertIOAllowed(); // For call to close(). + int fd = CreateAndOpenFdForTemporaryFile(dir, temp_file); + return ((fd >= 0) && !HANDLE_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(). + DCHECK(name_tmpl.find("XXXXXX") != FilePath::StringType::npos) + << "Directory name template must contain \"XXXXXX\"."; + + FilePath sub_dir = base_dir.Append(name_tmpl); + std::string sub_dir_string = sub_dir.value(); + + // this should be OK since mkdtemp just replaces characters in place + char* buffer = const_cast<char*>(sub_dir_string.c_str()); + char* dtemp = mkdtemp(buffer); + if (!dtemp) { + DPLOG(ERROR) << "mkdtemp"; + return false; + } + *new_dir = FilePath(dtemp); + return true; +} + +bool CreateTemporaryDirInDir(const FilePath& base_dir, + const FilePath::StringType& prefix, + FilePath* new_dir) { + FilePath::StringType mkdtemp_template = prefix; + mkdtemp_template.append(FILE_PATH_LITERAL("XXXXXX")); + return CreateTemporaryDirInDirImpl(base_dir, mkdtemp_template, new_dir); +} + +bool CreateNewTempDirectory(const FilePath::StringType& prefix, + FilePath* new_temp_path) { + FilePath tmpdir; + if (!GetTempDir(&tmpdir)) + return false; + + return CreateTemporaryDirInDirImpl(tmpdir, TempFileName(), new_temp_path); +} + +#if !defined(__LB_XB1__) && !defined(__LB_XB360__) +bool CreateDirectory(const FilePath& full_path) { + base::ThreadRestrictions::AssertIOAllowed(); // For call to mkdir(). + std::vector<FilePath> subpaths; + + // Collect a list of all parent directories. + FilePath last_path = full_path; + subpaths.push_back(full_path); + for (FilePath path = full_path.DirName(); + path.value() != last_path.value(); path = path.DirName()) { + subpaths.push_back(path); + last_path = path; + } + + // Iterate through the parents and create the missing ones. + for (std::vector<FilePath>::reverse_iterator i = subpaths.rbegin(); + i != subpaths.rend(); ++i) { + if (DirectoryExists(*i)) + continue; + if (mkdir(i->value().c_str(), 0700) == 0) + continue; + // Mkdir failed, but it might have failed with EEXIST, or some other error + // due to the the directory appearing out of thin air. This can occur if + // two processes are trying to create the same file system tree at the same + // time. Check to see if it exists and make sure it is a directory. + if (!DirectoryExists(*i)) + return false; + } + return true; +} +#endif + +// TODO(rkc): Refactor GetFileInfo and FileEnumerator to handle symlinks +// correctly. http://code.google.com/p/chromium-os/issues/detail?id=15948 +bool IsLink(const FilePath& file_path) { + stat_wrapper_t st; + // If we can't lstat the file, it's safe to assume that the file won't at + // least be a 'followable' link. + if (CallLstat(file_path.value().c_str(), &st) != 0) + return false; + + if (S_ISLNK(st.st_mode)) + return true; + else + return false; +} + +bool GetFileInfo(const FilePath& file_path, base::PlatformFileInfo* results) { + stat_wrapper_t file_info; + if (CallStat(file_path.value().c_str(), &file_info) != 0) + return false; + results->is_directory = S_ISDIR(file_info.st_mode); + results->size = file_info.st_size; + 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); + return true; +} + +#if !defined(__LB_SHELL__) +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; +} +#endif + +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(); + FILE* result = NULL; + do { + result = fopen(filename.value().c_str(), mode); + } while (!result && errno == EINTR); + return result; +} + +int ReadFile(const FilePath& filename, char* data, int size) { + base::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) + return ret; + return bytes_read; +} + +int WriteFile(const FilePath& filename, const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + int fd = HANDLE_EINTR(creat(filename.value().c_str(), 0666)); + if (fd < 0) + return -1; + + int bytes_written = WriteFileDescriptor(fd, data, size); + if (int ret = HANDLE_EINTR(close(fd)) < 0) + return ret; + return bytes_written; +} + +int WriteFileDescriptor(const int fd, const char* data, int size) { + // Allow for partial writes. + ssize_t bytes_written_total = 0; + for (ssize_t bytes_written_partial = 0; bytes_written_total < size; + bytes_written_total += bytes_written_partial) { + bytes_written_partial = + HANDLE_EINTR(write(fd, data + bytes_written_total, + size - bytes_written_total)); + if (bytes_written_partial < 0) + return -1; + } + + return bytes_written_total; +} + +int AppendToFile(const FilePath& filename, const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + int fd = HANDLE_EINTR(open(filename.value().c_str(), O_WRONLY | O_APPEND)); + if (fd < 0) + return -1; + + int bytes_written = WriteFileDescriptor(fd, data, size); + if (int ret = HANDLE_EINTR(close(fd)) < 0) + return ret; + return bytes_written; +} + +// Gets the current working directory for the process. +bool GetCurrentDirectory(FilePath* dir) { + // getcwd can return ENOENT, which implies it checks against the disk. + base::ThreadRestrictions::AssertIOAllowed(); + + char system_buffer[PATH_MAX] = ""; + if (!getcwd(system_buffer, sizeof(system_buffer))) { + NOTREACHED(); + return false; + } + *dir = FilePath(system_buffer); + return true; +} + +// Sets the current working directory for the process. +bool SetCurrentDirectory(const FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + int ret = chdir(path.value().c_str()); + return !ret; +} + +/////////////////////////////////////////////// +// FileEnumerator + +FileEnumerator::FileEnumerator(const FilePath& root_path, + bool recursive, + int file_type) + : current_directory_entry_(0), + root_path_(root_path), + recursive_(recursive), + file_type_(file_type) { + // INCLUDE_DOT_DOT must not be specified if recursive. + DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_))); + pending_paths_.push(root_path); +} + +FileEnumerator::FileEnumerator(const FilePath& root_path, + bool recursive, + int file_type, + const FilePath::StringType& pattern) + : current_directory_entry_(0), + root_path_(root_path), + recursive_(recursive), + file_type_(file_type), + pattern_(root_path.Append(pattern).value()) { + // INCLUDE_DOT_DOT must not be specified if recursive. + DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_))); + // The Windows version of this code appends the pattern to the root_path, + // potentially only matching against items in the top-most directory. + // Do the same here. + if (pattern.empty()) + pattern_ = FilePath::StringType(); + pending_paths_.push(root_path); +} + +FileEnumerator::~FileEnumerator() { +} + +FilePath FileEnumerator::Next() { + ++current_directory_entry_; + + // While we've exhausted the entries in the current directory, do the next + while (current_directory_entry_ >= directory_entries_.size()) { + if (pending_paths_.empty()) + return FilePath(); + + root_path_ = pending_paths_.top(); + root_path_ = root_path_.StripTrailingSeparators(); + pending_paths_.pop(); + + std::vector<DirectoryEntryInfo> entries; + if (!ReadDirectory( + &entries, root_path_, (file_type_ & SHOW_SYM_LINKS) != 0)) + continue; + + directory_entries_.clear(); + current_directory_entry_ = 0; + for (std::vector<DirectoryEntryInfo>::const_iterator + i = entries.begin(); i != entries.end(); ++i) { + FilePath full_path = root_path_.Append(i->filename); + if (ShouldSkip(full_path)) + continue; + + if (pattern_.size() && + fnmatch(pattern_.c_str(), full_path.value().c_str(), FNM_NOESCAPE)) + continue; + + if (recursive_ && S_ISDIR(i->stat.st_mode)) + pending_paths_.push(full_path); + + if ((S_ISDIR(i->stat.st_mode) && (file_type_ & DIRECTORIES)) || + (!S_ISDIR(i->stat.st_mode) && (file_type_ & FILES))) + directory_entries_.push_back(*i); + } + } + + return root_path_.Append(directory_entries_[current_directory_entry_ + ].filename); +} + +void FileEnumerator::GetFindInfo(FindInfo* info) { + DCHECK(info); + + if (current_directory_entry_ >= directory_entries_.size()) + return; + + DirectoryEntryInfo* cur_entry = &directory_entries_[current_directory_entry_]; + memcpy(&(info->stat), &(cur_entry->stat), sizeof(info->stat)); + info->filename.assign(cur_entry->filename.value()); +} + +// static +bool FileEnumerator::IsDirectory(const FindInfo& info) { + return S_ISDIR(info.stat.st_mode); +} + +// static +FilePath FileEnumerator::GetFilename(const FindInfo& find_info) { + return FilePath(find_info.filename); +} + +// static +int64 FileEnumerator::GetFilesize(const FindInfo& find_info) { + return find_info.stat.st_size; +} + +// static +base::Time FileEnumerator::GetLastModifiedTime(const FindInfo& find_info) { + return base::Time::FromTimeT(find_info.stat.st_mtime); +} + +bool FileEnumerator::ReadDirectory(std::vector<DirectoryEntryInfo>* entries, + const FilePath& source, bool show_links) { + base::ThreadRestrictions::AssertIOAllowed(); + DIR* dir = opendir(source.value().c_str()); + if (!dir) + return false; + +#if !defined(OS_LINUX) && !defined(OS_MACOSX) && !defined(OS_BSD) && \ + !defined(OS_SOLARIS) && !defined(OS_ANDROID) && !defined(__LB_SHELL__) + #error Port warning: depending on the definition of struct dirent, \ + additional space for pathname may be needed +#endif + + struct dirent dent_buf; + struct dirent* dent; + while (readdir_r(dir, &dent_buf, &dent) == 0 && dent) { + DirectoryEntryInfo info; + info.filename = FilePath(dent->d_name); + + FilePath full_name = source.Append(dent->d_name); + int ret; + if (show_links) + ret = lstat(full_name.value().c_str(), &info.stat); + else + ret = stat(full_name.value().c_str(), &info.stat); + if (ret < 0) { + // Print the stat() error message unless it was ENOENT and we're + // following symlinks. + if (!(errno == ENOENT && !show_links)) { + DPLOG(ERROR) << "Couldn't stat " + << source.Append(dent->d_name).value(); + } + memset(&info.stat, 0, sizeof(info.stat)); + } + entries->push_back(info); + } + + closedir(dir); + return true; +} + +/////////////////////////////////////////////// +// MemoryMappedFile +#if !defined(__LB_SHELL__) +MemoryMappedFile::MemoryMappedFile() + : file_(base::kInvalidPlatformFileValue), + data_(NULL), + length_(0) { +} + +bool MemoryMappedFile::MapFileToMemoryInternal() { + base::ThreadRestrictions::AssertIOAllowed(); + + struct stat file_stat; + if (fstat(file_, &file_stat) == base::kInvalidPlatformFileValue) { + DLOG(ERROR) << "Couldn't fstat " << file_ << ", errno " << errno; + return false; + } + length_ = file_stat.st_size; + + data_ = static_cast<uint8*>( + mmap(NULL, length_, PROT_READ, MAP_SHARED, file_, 0)); + if (data_ == MAP_FAILED) + DLOG(ERROR) << "Couldn't mmap " << file_ << ", errno " << errno; + + return data_ != MAP_FAILED; +} + +void MemoryMappedFile::CloseHandles() { + base::ThreadRestrictions::AssertIOAllowed(); + + if (data_ != NULL) + munmap(data_, length_); + if (file_ != base::kInvalidPlatformFileValue) + ignore_result(HANDLE_EINTR(close(file_))); + + data_ = NULL; + length_ = 0; + file_ = base::kInvalidPlatformFileValue; +} +#endif + +bool HasFileBeenModifiedSince(const FileEnumerator::FindInfo& find_info, + const base::Time& cutoff_time) { + return static_cast<time_t>(find_info.stat.st_mtime) >= cutoff_time.ToTimeT(); +} + +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) +#if !defined(__LB_SHELL__) +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); + Delete(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(__LB_SHELL__) + +bool CopyFile(const FilePath& from_path, const FilePath& to_path) { + base::ThreadRestrictions::AssertIOAllowed(); + int infile = HANDLE_EINTR(open(from_path.value().c_str(), O_RDONLY)); + if (infile < 0) + return false; + + int outfile = HANDLE_EINTR(creat(to_path.value().c_str(), 0666)); + if (outfile < 0) { + ignore_result(HANDLE_EINTR(close(infile))); + return false; + } + + const size_t kBufferSize = 32768; + std::vector<char> buffer(kBufferSize); + bool result = true; + + while (result) { + ssize_t bytes_read = HANDLE_EINTR(read(infile, &buffer[0], buffer.size())); + if (bytes_read < 0) { + result = false; + break; + } + if (bytes_read == 0) + break; + // Allow for partial writes + ssize_t bytes_written_per_read = 0; + do { + ssize_t bytes_written_partial = HANDLE_EINTR(write( + outfile, + &buffer[bytes_written_per_read], + bytes_read - bytes_written_per_read)); + if (bytes_written_partial < 0) { + result = false; + break; + } + bytes_written_per_read += bytes_written_partial; + } while (bytes_written_per_read < bytes_read); + } + + if (HANDLE_EINTR(close(infile)) < 0) + result = false; + if (HANDLE_EINTR(close(outfile)) < 0) + result = false; + + return result; +} +#endif // !defined(OS_MACOSX) + +bool VerifyPathControlledByUser(const FilePath& base, + const FilePath& path, + uid_t owner_uid, + const std::set<gid_t>& group_gids) { + if (base != path && !base.IsParent(path)) { + DLOG(ERROR) << "|base| must be a subdirectory of |path|. base = \"" + << base.value() << "\", path = \"" << path.value() << "\""; + return false; + } + + std::vector<FilePath::StringType> base_components; + std::vector<FilePath::StringType> path_components; + + base.GetComponents(&base_components); + path.GetComponents(&path_components); + + std::vector<FilePath::StringType>::const_iterator ib, ip; + for (ib = base_components.begin(), ip = path_components.begin(); + ib != base_components.end(); ++ib, ++ip) { + // |base| must be a subpath of |path|, so all components should match. + // If these CHECKs fail, look at the test that base is a parent of + // path at the top of this function. + DCHECK(ip != path_components.end()); + DCHECK(*ip == *ib); + } + + FilePath current_path = base; + if (!VerifySpecificPathControlledByUser(current_path, owner_uid, group_gids)) + return false; + + for (; ip != path_components.end(); ++ip) { + current_path = current_path.Append(*ip); + if (!VerifySpecificPathControlledByUser( + current_path, owner_uid, group_gids)) + return false; + } + return true; +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +bool VerifyPathControlledByAdmin(const FilePath& path) { + const unsigned kRootUid = 0; + const FilePath kFileSystemRoot("/"); + + // The name of the administrator group on mac os. + const char* const kAdminGroupNames[] = { + "admin", + "wheel" + }; + + // Reading the groups database may touch the file system. + base::ThreadRestrictions::AssertIOAllowed(); + + std::set<gid_t> allowed_group_ids; + for (int i = 0, ie = arraysize(kAdminGroupNames); i < ie; ++i) { + struct group *group_record = getgrnam(kAdminGroupNames[i]); + if (!group_record) { + DPLOG(ERROR) << "Could not get the group ID of group \"" + << kAdminGroupNames[i] << "\"."; + continue; + } + + allowed_group_ids.insert(group_record->gr_gid); + } + + return VerifyPathControlledByUser( + kFileSystemRoot, path, kRootUid, allowed_group_ids); +} +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + +} // namespace file_util
diff --git a/src/base/file_util_proxy.cc b/src/base/file_util_proxy.cc new file mode 100644 index 0000000..2b075b0 --- /dev/null +++ b/src/base/file_util_proxy.cc
@@ -0,0 +1,449 @@ +// 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_proxy.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/file_util.h" +#include "base/location.h" +#include "base/message_loop_proxy.h" +#include "base/task_runner.h" +#include "base/task_runner_util.h" + +namespace base { + +namespace { + +void CallWithTranslatedParameter(const FileUtilProxy::StatusCallback& callback, + bool value) { + DCHECK(!callback.is_null()); + callback.Run(value ? PLATFORM_FILE_OK : PLATFORM_FILE_ERROR_FAILED); +} + +// Helper classes or routines for individual methods. +class CreateOrOpenHelper { + public: + CreateOrOpenHelper(TaskRunner* task_runner, + const FileUtilProxy::CloseTask& close_task) + : task_runner_(task_runner), + close_task_(close_task), + file_handle_(kInvalidPlatformFileValue), + created_(false), + error_(PLATFORM_FILE_OK) {} + + ~CreateOrOpenHelper() { + if (file_handle_ != kInvalidPlatformFileValue) { + task_runner_->PostTask( + FROM_HERE, + base::Bind(base::IgnoreResult(close_task_), file_handle_)); + } + } + + void RunWork(const FileUtilProxy::CreateOrOpenTask& task) { + error_ = task.Run(&file_handle_, &created_); + } + + void Reply(const FileUtilProxy::CreateOrOpenCallback& callback, + const FileUtilProxy::CreateOrOpenTask& open_task) { + DCHECK(!callback.is_null()); + if (error_ != PLATFORM_FILE_ERROR_TOO_MANY_OPENED) { + callback.Run(error_, PassPlatformFile(&file_handle_), created_); + return; + } + DLOG(WARNING) << "Too many files are opened, retrying ..."; + CreateOrOpenHelper* helper = + new CreateOrOpenHelper(task_runner_, close_task_); + task_runner_->PostTaskAndReply( + FROM_HERE, + Bind(&CreateOrOpenHelper::RunWork, Unretained(helper), open_task), + Bind(&CreateOrOpenHelper::Reply, Owned(helper), callback, open_task)); + } + + private: + scoped_refptr<TaskRunner> task_runner_; + FileUtilProxy::CloseTask close_task_; + PlatformFile file_handle_; + bool created_; + PlatformFileError error_; + DISALLOW_COPY_AND_ASSIGN(CreateOrOpenHelper); +}; + +class CreateTemporaryHelper { + public: + explicit CreateTemporaryHelper(TaskRunner* task_runner) + : task_runner_(task_runner), + file_handle_(kInvalidPlatformFileValue), + error_(PLATFORM_FILE_OK) {} + + ~CreateTemporaryHelper() { + if (file_handle_ != kInvalidPlatformFileValue) { + FileUtilProxy::Close(task_runner_, file_handle_, + FileUtilProxy::StatusCallback()); + } + } + + 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_); + + int file_flags = + PLATFORM_FILE_WRITE | + PLATFORM_FILE_TEMPORARY | + PLATFORM_FILE_CREATE_ALWAYS | + additional_file_flags; + + error_ = PLATFORM_FILE_OK; + file_handle_ = CreatePlatformFile(file_path_, file_flags, NULL, &error_); + } + + void Reply(const FileUtilProxy::CreateTemporaryCallback& callback) { + DCHECK(!callback.is_null()); + callback.Run(error_, PassPlatformFile(&file_handle_), file_path_); + } + + private: + scoped_refptr<TaskRunner> task_runner_; + PlatformFile file_handle_; + FilePath file_path_; + PlatformFileError error_; + DISALLOW_COPY_AND_ASSIGN(CreateTemporaryHelper); +}; + +class GetFileInfoHelper { + public: + GetFileInfoHelper() + : error_(PLATFORM_FILE_OK) {} + + void RunWorkForFilePath(const FilePath& file_path) { + if (!file_util::PathExists(file_path)) { + error_ = PLATFORM_FILE_ERROR_NOT_FOUND; + return; + } + if (!file_util::GetFileInfo(file_path, &file_info_)) + error_ = PLATFORM_FILE_ERROR_FAILED; + } + + void RunWorkForPlatformFile(PlatformFile file) { + if (!GetPlatformFileInfo(file, &file_info_)) + error_ = PLATFORM_FILE_ERROR_FAILED; + } + + void Reply(const FileUtilProxy::GetFileInfoCallback& callback) { + if (!callback.is_null()) { + callback.Run(error_, file_info_); + } + } + + private: + PlatformFileError error_; + PlatformFileInfo file_info_; + DISALLOW_COPY_AND_ASSIGN(GetFileInfoHelper); +}; + +class ReadHelper { + public: + explicit ReadHelper(int bytes_to_read) + : buffer_(new char[bytes_to_read]), + bytes_to_read_(bytes_to_read), + bytes_read_(0) {} + + void RunWork(PlatformFile file, int64 offset) { + bytes_read_ = ReadPlatformFile(file, offset, buffer_.get(), bytes_to_read_); + } + + void Reply(const FileUtilProxy::ReadCallback& callback) { + if (!callback.is_null()) { + PlatformFileError error = + (bytes_read_ < 0) ? PLATFORM_FILE_ERROR_FAILED : PLATFORM_FILE_OK; + callback.Run(error, buffer_.get(), bytes_read_); + } + } + + private: + scoped_array<char> buffer_; + int bytes_to_read_; + int bytes_read_; + DISALLOW_COPY_AND_ASSIGN(ReadHelper); +}; + +class WriteHelper { + public: + WriteHelper(const char* buffer, int bytes_to_write) + : buffer_(new char[bytes_to_write]), + bytes_to_write_(bytes_to_write), + bytes_written_(0) { + memcpy(buffer_.get(), buffer, bytes_to_write); + } + + void RunWork(PlatformFile file, int64 offset) { + bytes_written_ = WritePlatformFile(file, offset, buffer_.get(), + bytes_to_write_); + } + + void Reply(const FileUtilProxy::WriteCallback& callback) { + if (!callback.is_null()) { + PlatformFileError error = + (bytes_written_ < 0) ? PLATFORM_FILE_ERROR_FAILED : PLATFORM_FILE_OK; + callback.Run(error, bytes_written_); + } + } + + private: + scoped_array<char> buffer_; + int bytes_to_write_; + int bytes_written_; + DISALLOW_COPY_AND_ASSIGN(WriteHelper); +}; + + +PlatformFileError CreateOrOpenAdapter( + const FilePath& file_path, int file_flags, + PlatformFile* file_handle, bool* created) { + DCHECK(file_handle); + DCHECK(created); + if (!file_util::DirectoryExists(file_path.DirName())) { + // If its parent does not exist, should return NOT_FOUND error. + return PLATFORM_FILE_ERROR_NOT_FOUND; + } + PlatformFileError error = PLATFORM_FILE_OK; + *file_handle = CreatePlatformFile(file_path, file_flags, created, &error); + return error; +} + +PlatformFileError CloseAdapter(PlatformFile file_handle) { + if (!ClosePlatformFile(file_handle)) { + return PLATFORM_FILE_ERROR_FAILED; + } + return PLATFORM_FILE_OK; +} + +PlatformFileError DeleteAdapter(const FilePath& file_path, bool recursive) { + if (!file_util::PathExists(file_path)) { + return PLATFORM_FILE_ERROR_NOT_FOUND; + } + if (!file_util::Delete(file_path, recursive)) { + if (!recursive && !file_util::IsDirectoryEmpty(file_path)) { + return PLATFORM_FILE_ERROR_NOT_EMPTY; + } + return PLATFORM_FILE_ERROR_FAILED; + } + return PLATFORM_FILE_OK; +} + +} // namespace + +// static +bool FileUtilProxy::CreateOrOpen( + TaskRunner* task_runner, + const FilePath& file_path, int file_flags, + const CreateOrOpenCallback& callback) { + return RelayCreateOrOpen( + task_runner, + base::Bind(&CreateOrOpenAdapter, file_path, file_flags), + base::Bind(&CloseAdapter), + callback); +} + +// static +bool FileUtilProxy::CreateTemporary( + TaskRunner* task_runner, + int additional_file_flags, + const CreateTemporaryCallback& callback) { + CreateTemporaryHelper* helper = new CreateTemporaryHelper(task_runner); + return task_runner->PostTaskAndReply( + FROM_HERE, + Bind(&CreateTemporaryHelper::RunWork, Unretained(helper), + additional_file_flags), + Bind(&CreateTemporaryHelper::Reply, Owned(helper), callback)); +} + +// static +bool FileUtilProxy::Close( + TaskRunner* task_runner, + base::PlatformFile file_handle, + const StatusCallback& callback) { + return RelayClose( + task_runner, + base::Bind(&CloseAdapter), + file_handle, callback); +} + +// Retrieves the information about a file. It is invalid to pass NULL for the +// callback. +bool FileUtilProxy::GetFileInfo( + TaskRunner* task_runner, + const FilePath& file_path, + const GetFileInfoCallback& callback) { + GetFileInfoHelper* helper = new GetFileInfoHelper; + return task_runner->PostTaskAndReply( + FROM_HERE, + Bind(&GetFileInfoHelper::RunWorkForFilePath, + Unretained(helper), file_path), + Bind(&GetFileInfoHelper::Reply, Owned(helper), callback)); +} + +// static +bool FileUtilProxy::GetFileInfoFromPlatformFile( + TaskRunner* task_runner, + PlatformFile file, + const GetFileInfoCallback& callback) { + GetFileInfoHelper* helper = new GetFileInfoHelper; + return task_runner->PostTaskAndReply( + FROM_HERE, + Bind(&GetFileInfoHelper::RunWorkForPlatformFile, + Unretained(helper), file), + Bind(&GetFileInfoHelper::Reply, Owned(helper), callback)); +} + +// static +bool FileUtilProxy::Delete(TaskRunner* task_runner, + const FilePath& file_path, + bool recursive, + const StatusCallback& callback) { + return RelayFileTask( + task_runner, FROM_HERE, + Bind(&DeleteAdapter, file_path, recursive), + callback); +} + +// static +bool FileUtilProxy::RecursiveDelete( + TaskRunner* task_runner, + const FilePath& file_path, + const StatusCallback& callback) { + return RelayFileTask( + task_runner, FROM_HERE, + Bind(&DeleteAdapter, file_path, true /* recursive */), + callback); +} + +// static +bool FileUtilProxy::Read( + TaskRunner* task_runner, + PlatformFile file, + int64 offset, + int bytes_to_read, + const ReadCallback& callback) { + if (bytes_to_read < 0) { + return false; + } + ReadHelper* helper = new ReadHelper(bytes_to_read); + return task_runner->PostTaskAndReply( + FROM_HERE, + Bind(&ReadHelper::RunWork, Unretained(helper), file, offset), + Bind(&ReadHelper::Reply, Owned(helper), callback)); +} + +// static +bool FileUtilProxy::Write( + TaskRunner* task_runner, + PlatformFile file, + int64 offset, + const char* buffer, + int bytes_to_write, + const WriteCallback& callback) { + if (bytes_to_write <= 0 || buffer == NULL) { + return false; + } + WriteHelper* helper = new WriteHelper(buffer, bytes_to_write); + return task_runner->PostTaskAndReply( + FROM_HERE, + Bind(&WriteHelper::RunWork, Unretained(helper), file, offset), + Bind(&WriteHelper::Reply, Owned(helper), callback)); +} + +#if !defined(OS_STARBOARD) +// static +bool FileUtilProxy::Touch( + TaskRunner* task_runner, + PlatformFile file, + const Time& last_access_time, + const Time& last_modified_time, + const StatusCallback& callback) { + return base::PostTaskAndReplyWithResult( + task_runner, + FROM_HERE, + Bind(&TouchPlatformFile, file, + last_access_time, last_modified_time), + Bind(&CallWithTranslatedParameter, callback)); +} + +// static +bool FileUtilProxy::Touch( + TaskRunner* task_runner, + const FilePath& file_path, + const Time& last_access_time, + const Time& last_modified_time, + const StatusCallback& callback) { + return base::PostTaskAndReplyWithResult( + task_runner, + FROM_HERE, + Bind(&file_util::TouchFile, file_path, + last_access_time, last_modified_time), + Bind(&CallWithTranslatedParameter, callback)); +} +#endif + +// static +bool FileUtilProxy::Truncate( + TaskRunner* task_runner, + PlatformFile file, + int64 length, + const StatusCallback& callback) { + return base::PostTaskAndReplyWithResult( + task_runner, + FROM_HERE, + Bind(&TruncatePlatformFile, file, length), + Bind(&CallWithTranslatedParameter, callback)); +} + +// static +bool FileUtilProxy::Flush( + TaskRunner* task_runner, + PlatformFile file, + const StatusCallback& callback) { + return base::PostTaskAndReplyWithResult( + task_runner, + FROM_HERE, + Bind(&FlushPlatformFile, file), + Bind(&CallWithTranslatedParameter, callback)); +} + +// static +bool FileUtilProxy::RelayFileTask( + TaskRunner* task_runner, + const tracked_objects::Location& from_here, + const FileTask& file_task, + const StatusCallback& callback) { + return base::PostTaskAndReplyWithResult( + task_runner, from_here, file_task, callback); +} + +// static +bool FileUtilProxy::RelayCreateOrOpen( + TaskRunner* task_runner, + const CreateOrOpenTask& open_task, + const CloseTask& close_task, + const CreateOrOpenCallback& callback) { + CreateOrOpenHelper* helper = new CreateOrOpenHelper( + task_runner, close_task); + return task_runner->PostTaskAndReply( + FROM_HERE, + Bind(&CreateOrOpenHelper::RunWork, Unretained(helper), open_task), + Bind(&CreateOrOpenHelper::Reply, Owned(helper), callback, open_task)); +} + +// static +bool FileUtilProxy::RelayClose( + TaskRunner* task_runner, + const CloseTask& close_task, + PlatformFile file_handle, + const StatusCallback& callback) { + return base::PostTaskAndReplyWithResult( + task_runner, FROM_HERE, Bind(close_task, file_handle), callback); +} + +} // namespace base
diff --git a/src/base/file_util_proxy.h b/src/base/file_util_proxy.h new file mode 100644 index 0000000..f6ecbad --- /dev/null +++ b/src/base/file_util_proxy.h
@@ -0,0 +1,198 @@ +// 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_FILE_UTIL_PROXY_H_ +#define BASE_FILE_UTIL_PROXY_H_ + +#include "base/base_export.h" +#include "base/callback_forward.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/memory/ref_counted.h" +#include "base/platform_file.h" + +namespace tracked_objects { +class Location; +}; + +namespace base { + +class TaskRunner; +class Time; + +// This class provides asynchronous access to common file routines. +class BASE_EXPORT FileUtilProxy { + public: + // Holds metadata for file or directory entry. + struct Entry { + FilePath::StringType name; + bool is_directory; + int64 size; + base::Time last_modified_time; + }; + + // This callback is used by methods that report only an error code. It is + // valid to pass a null callback to any function that takes a StatusCallback, + // in which case the operation will complete silently. + typedef Callback<void(PlatformFileError)> StatusCallback; + + typedef Callback<void(PlatformFileError, + PassPlatformFile, + bool /* created */)> CreateOrOpenCallback; + typedef Callback<void(PlatformFileError, + PassPlatformFile, + const FilePath&)> CreateTemporaryCallback; + typedef Callback<void(PlatformFileError, + const PlatformFileInfo&)> GetFileInfoCallback; + typedef Callback<void(PlatformFileError, + const char* /* data */, + int /* bytes read */)> ReadCallback; + typedef Callback<void(PlatformFileError, + int /* bytes written */)> WriteCallback; + + typedef Callback<PlatformFileError(PlatformFile*, bool*)> CreateOrOpenTask; + typedef Callback<PlatformFileError(PlatformFile)> CloseTask; + typedef Callback<PlatformFileError(void)> FileTask; + + // Creates or opens a file with the given flags. It is invalid to pass a null + // callback. If PLATFORM_FILE_CREATE is set in |file_flags| it always tries to + // create a new file at the given |file_path| and calls back with + // PLATFORM_FILE_ERROR_FILE_EXISTS if the |file_path| already exists. + static bool CreateOrOpen(TaskRunner* task_runner, + const FilePath& file_path, + int file_flags, + const CreateOrOpenCallback& callback); + + // Creates a temporary file for writing. The path and an open file handle are + // returned. It is invalid to pass a null callback. The additional file flags + // will be added on top of the default file flags which are: + // base::PLATFORM_FILE_CREATE_ALWAYS + // base::PLATFORM_FILE_WRITE + // base::PLATFORM_FILE_TEMPORARY. + // Set |additional_file_flags| to 0 for synchronous writes and set to + // base::PLATFORM_FILE_ASYNC to support asynchronous file operations. + static bool CreateTemporary( + TaskRunner* task_runner, + int additional_file_flags, + const CreateTemporaryCallback& callback); + + // Close the given file handle. + static bool Close(TaskRunner* task_runner, + PlatformFile, + const StatusCallback& callback); + + // Retrieves the information about a file. It is invalid to pass a null + // callback. + static bool GetFileInfo( + TaskRunner* task_runner, + const FilePath& file_path, + const GetFileInfoCallback& callback); + + static bool GetFileInfoFromPlatformFile( + TaskRunner* task_runner, + PlatformFile file, + const GetFileInfoCallback& callback); + + // Deletes a file or a directory. + // It is an error to delete a non-empty directory with recursive=false. + static bool Delete(TaskRunner* task_runner, + const FilePath& file_path, + bool recursive, + const StatusCallback& callback); + + // Deletes a directory and all of its contents. + static bool RecursiveDelete( + TaskRunner* task_runner, + const FilePath& file_path, + const StatusCallback& callback); + + // Reads from a file. On success, the file pointer is moved to position + // |offset + bytes_to_read| in the file. The callback can be null. + static bool Read( + TaskRunner* task_runner, + PlatformFile file, + int64 offset, + int bytes_to_read, + const ReadCallback& callback); + + // Writes to a file. If |offset| is greater than the length of the file, + // |false| is returned. On success, the file pointer is moved to position + // |offset + bytes_to_write| in the file. The callback can be null. + // |bytes_to_write| must be greater than zero. + static bool Write( + TaskRunner* task_runner, + PlatformFile file, + int64 offset, + const char* buffer, + int bytes_to_write, + const WriteCallback& callback); + +#if !defined(OS_STARBOARD) + // Touches a file. The callback can be null. + static bool Touch( + TaskRunner* task_runner, + PlatformFile file, + const Time& last_access_time, + const Time& last_modified_time, + const StatusCallback& callback); + + // Touches a file. The callback can be null. + static bool Touch( + TaskRunner* task_runner, + const FilePath& file_path, + const Time& last_access_time, + const Time& last_modified_time, + const StatusCallback& callback); +#endif + + // Truncates a file to the given length. If |length| is greater than the + // current length of the file, the file will be extended with zeroes. + // The callback can be null. + static bool Truncate( + TaskRunner* task_runner, + PlatformFile file, + int64 length, + const StatusCallback& callback); + + // Truncates a file to the given length. If |length| is greater than the + // current length of the file, the file will be extended with zeroes. + // The callback can be null. + static bool Truncate( + TaskRunner* task_runner, + const FilePath& path, + int64 length, + const StatusCallback& callback); + + // Flushes a file. The callback can be null. + static bool Flush( + TaskRunner* task_runner, + PlatformFile file, + const StatusCallback& callback); + + // Relay helpers. + static bool RelayFileTask( + TaskRunner* task_runner, + const tracked_objects::Location& from_here, + const FileTask& task, + const StatusCallback& callback); + + static bool RelayCreateOrOpen( + TaskRunner* task_runner, + const CreateOrOpenTask& open_task, + const CloseTask& close_task, + const CreateOrOpenCallback& callback); + + static bool RelayClose( + TaskRunner* task_runner, + const CloseTask& close_task, + PlatformFile, + const StatusCallback& callback); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(FileUtilProxy); +}; + +} // namespace base + +#endif // BASE_FILE_UTIL_PROXY_H_
diff --git a/src/base/file_util_proxy_unittest.cc b/src/base/file_util_proxy_unittest.cc new file mode 100644 index 0000000..e238b32 --- /dev/null +++ b/src/base/file_util_proxy_unittest.cc
@@ -0,0 +1,438 @@ +// 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_proxy.h" + +#include <map> + +#include "base/bind.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop.h" +#include "base/platform_file.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +class FileUtilProxyTest : public testing::Test { + public: + FileUtilProxyTest() + : message_loop_(MessageLoop::TYPE_IO), + file_thread_("FileUtilProxyTestFileThread"), + error_(PLATFORM_FILE_OK), + created_(false), + file_(kInvalidPlatformFileValue), + bytes_written_(-1), + weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {} + + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(dir_.CreateUniqueTempDir()); + ASSERT_TRUE(file_thread_.Start()); + } + + virtual void TearDown() OVERRIDE { + if (file_ != kInvalidPlatformFileValue) + ClosePlatformFile(file_); + } + + void DidFinish(PlatformFileError error) { + error_ = error; + MessageLoop::current()->Quit(); + } + + void DidCreateOrOpen(PlatformFileError error, + PassPlatformFile file, + bool created) { + error_ = error; + file_ = file.ReleaseValue(); + created_ = created; + MessageLoop::current()->Quit(); + } + + void DidCreateTemporary(PlatformFileError error, + PassPlatformFile file, + const FilePath& path) { + error_ = error; + file_ = file.ReleaseValue(); + path_ = path; + MessageLoop::current()->Quit(); + } + + void DidGetFileInfo(PlatformFileError error, + const PlatformFileInfo& file_info) { + error_ = error; + file_info_ = file_info; + MessageLoop::current()->Quit(); + } + + void DidRead(PlatformFileError error, + const char* data, + int bytes_read) { + error_ = error; + buffer_.resize(bytes_read); + memcpy(&buffer_[0], data, bytes_read); + MessageLoop::current()->Quit(); + } + + void DidWrite(PlatformFileError error, + int bytes_written) { + error_ = error; + bytes_written_ = bytes_written; + MessageLoop::current()->Quit(); + } + + protected: + PlatformFile GetTestPlatformFile(int flags) { + if (file_ != kInvalidPlatformFileValue) + return file_; + bool created; + PlatformFileError error; + file_ = CreatePlatformFile(test_path(), flags, &created, &error); + EXPECT_EQ(PLATFORM_FILE_OK, error); + EXPECT_NE(kInvalidPlatformFileValue, file_); + return file_; + } + + TaskRunner* file_task_runner() const { + return file_thread_.message_loop_proxy().get(); + } + const FilePath& test_dir_path() const { return dir_.path(); } + const FilePath test_path() const { return dir_.path().AppendASCII("test"); } + + MessageLoop message_loop_; + Thread file_thread_; + + ScopedTempDir dir_; + PlatformFileError error_; + bool created_; + PlatformFile file_; + FilePath path_; + PlatformFileInfo file_info_; + std::vector<char> buffer_; + int bytes_written_; + WeakPtrFactory<FileUtilProxyTest> weak_factory_; +}; + +TEST_F(FileUtilProxyTest, CreateOrOpen_Create) { + FileUtilProxy::CreateOrOpen( + file_task_runner(), + test_path(), + PLATFORM_FILE_CREATE | PLATFORM_FILE_READ, + Bind(&FileUtilProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr())); + MessageLoop::current()->Run(); + + EXPECT_EQ(PLATFORM_FILE_OK, error_); + EXPECT_TRUE(created_); + EXPECT_NE(kInvalidPlatformFileValue, file_); + EXPECT_TRUE(file_util::PathExists(test_path())); +} + +TEST_F(FileUtilProxyTest, CreateOrOpen_Open) { + // Creates a file. + file_util::WriteFile(test_path(), NULL, 0); + ASSERT_TRUE(file_util::PathExists(test_path())); + + // Opens the created file. + FileUtilProxy::CreateOrOpen( + file_task_runner(), + test_path(), + PLATFORM_FILE_OPEN | PLATFORM_FILE_READ, + Bind(&FileUtilProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr())); + MessageLoop::current()->Run(); + + EXPECT_EQ(PLATFORM_FILE_OK, error_); + EXPECT_FALSE(created_); + EXPECT_NE(kInvalidPlatformFileValue, file_); +} + +TEST_F(FileUtilProxyTest, CreateOrOpen_OpenNonExistent) { + FileUtilProxy::CreateOrOpen( + file_task_runner(), + test_path(), + PLATFORM_FILE_OPEN | PLATFORM_FILE_READ, + Bind(&FileUtilProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr())); + MessageLoop::current()->Run(); + EXPECT_EQ(PLATFORM_FILE_ERROR_NOT_FOUND, error_); + EXPECT_FALSE(created_); + EXPECT_EQ(kInvalidPlatformFileValue, file_); + EXPECT_FALSE(file_util::PathExists(test_path())); +} + +TEST_F(FileUtilProxyTest, Close) { + // Creates a file. + PlatformFile file = GetTestPlatformFile( + PLATFORM_FILE_CREATE | PLATFORM_FILE_WRITE); + +#if defined(OS_WIN) + // This fails on Windows if the file is not closed. + EXPECT_FALSE(file_util::Move(test_path(), + test_dir_path().AppendASCII("new"))); +#endif + + FileUtilProxy::Close( + file_task_runner(), + file, + Bind(&FileUtilProxyTest::DidFinish, weak_factory_.GetWeakPtr())); + MessageLoop::current()->Run(); + ASSERT_EQ(PLATFORM_FILE_OK, error_); + + // file_ has been closed by FileUtilProxy::Close + file_ = kInvalidPlatformFileValue; + +#if !defined(OS_STARBOARD) + // Now it should pass on all platforms. + EXPECT_TRUE(file_util::Move(test_path(), test_dir_path().AppendASCII("new"))); +#endif +} + +TEST_F(FileUtilProxyTest, CreateTemporary) { + FileUtilProxy::CreateTemporary( + file_task_runner(), 0 /* additional_file_flags */, + Bind(&FileUtilProxyTest::DidCreateTemporary, weak_factory_.GetWeakPtr())); + MessageLoop::current()->Run(); + EXPECT_EQ(PLATFORM_FILE_OK, error_); + EXPECT_TRUE(file_util::PathExists(path_)); + EXPECT_NE(kInvalidPlatformFileValue, file_); + + // The file should be writable. +#if defined(OS_WIN) + HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + OVERLAPPED overlapped = {0}; + overlapped.hEvent = hEvent; + DWORD bytes_written; + if (!::WriteFile(file_, "test", 4, &bytes_written, &overlapped)) { + // Temporary file is created with ASYNC flag, so WriteFile may return 0 + // with ERROR_IO_PENDING. + EXPECT_EQ(ERROR_IO_PENDING, GetLastError()); + GetOverlappedResult(file_, &overlapped, &bytes_written, TRUE); + } + EXPECT_EQ(4, bytes_written); +#else + // On POSIX ASYNC flag does not affect synchronous read/write behavior. + EXPECT_EQ(4, WritePlatformFile(file_, 0, "test", 4)); +#endif + EXPECT_TRUE(ClosePlatformFile(file_)); + file_ = kInvalidPlatformFileValue; + + // Make sure the written data can be read from the returned path. + std::string data; + EXPECT_TRUE(file_util::ReadFileToString(path_, &data)); + EXPECT_EQ("test", data); + + // Make sure we can & do delete the created file to prevent leaks on the bots. + EXPECT_TRUE(file_util::Delete(path_, false)); +} + +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); + + // Run. + FileUtilProxy::GetFileInfo( + file_task_runner(), + test_path(), + Bind(&FileUtilProxyTest::DidGetFileInfo, weak_factory_.GetWeakPtr())); + MessageLoop::current()->Run(); + + // Verify. + EXPECT_EQ(PLATFORM_FILE_OK, error_); + EXPECT_EQ(expected_info.size, file_info_.size); + EXPECT_EQ(expected_info.is_directory, file_info_.is_directory); + EXPECT_EQ(expected_info.is_symbolic_link, file_info_.is_symbolic_link); + EXPECT_EQ(expected_info.last_modified, file_info_.last_modified); + EXPECT_EQ(expected_info.last_accessed, file_info_.last_accessed); + EXPECT_EQ(expected_info.creation_time, file_info_.creation_time); +} + +TEST_F(FileUtilProxyTest, GetFileInfo_Directory) { + // Setup. + ASSERT_TRUE(file_util::CreateDirectory(test_path())); + PlatformFileInfo expected_info; + file_util::GetFileInfo(test_path(), &expected_info); + + // Run. + FileUtilProxy::GetFileInfo( + file_task_runner(), + test_path(), + Bind(&FileUtilProxyTest::DidGetFileInfo, weak_factory_.GetWeakPtr())); + MessageLoop::current()->Run(); + + // Verify. + EXPECT_EQ(PLATFORM_FILE_OK, error_); + EXPECT_EQ(expected_info.size, file_info_.size); + EXPECT_EQ(expected_info.is_directory, file_info_.is_directory); + EXPECT_EQ(expected_info.is_symbolic_link, file_info_.is_symbolic_link); + EXPECT_EQ(expected_info.last_modified, file_info_.last_modified); + EXPECT_EQ(expected_info.last_accessed, file_info_.last_accessed); + EXPECT_EQ(expected_info.creation_time, file_info_.creation_time); +} + +TEST_F(FileUtilProxyTest, Read) { + // Setup. + const char expected_data[] = "bleh"; + int expected_bytes = arraysize(expected_data); + ASSERT_EQ(expected_bytes, + file_util::WriteFile(test_path(), expected_data, expected_bytes)); + + // Run. + FileUtilProxy::Read( + file_task_runner(), + GetTestPlatformFile(PLATFORM_FILE_OPEN | PLATFORM_FILE_READ), + 0, // offset + 128, + Bind(&FileUtilProxyTest::DidRead, weak_factory_.GetWeakPtr())); + MessageLoop::current()->Run(); + + // Verify. + EXPECT_EQ(PLATFORM_FILE_OK, error_); + EXPECT_EQ(expected_bytes, static_cast<int>(buffer_.size())); + for (size_t i = 0; i < buffer_.size(); ++i) { + EXPECT_EQ(expected_data[i], buffer_[i]); + } +} + +TEST_F(FileUtilProxyTest, WriteAndFlush) { + const char data[] = "foo!"; + int data_bytes = ARRAYSIZE_UNSAFE(data); + PlatformFile file = GetTestPlatformFile( + PLATFORM_FILE_CREATE | PLATFORM_FILE_WRITE); + + FileUtilProxy::Write( + file_task_runner(), + file, + 0, // offset + data, + data_bytes, + Bind(&FileUtilProxyTest::DidWrite, weak_factory_.GetWeakPtr())); + MessageLoop::current()->Run(); + EXPECT_EQ(PLATFORM_FILE_OK, error_); + EXPECT_EQ(data_bytes, bytes_written_); + + // Flush the written data. (So that the following read should always + // succeed. On some platforms it may work with or without this flush.) + FileUtilProxy::Flush( + file_task_runner(), + file, + Bind(&FileUtilProxyTest::DidFinish, weak_factory_.GetWeakPtr())); + MessageLoop::current()->Run(); + EXPECT_EQ(PLATFORM_FILE_OK, error_); + +#if defined(__LB_WIIU__) + // Don't open a handle for reading if one is already open for writing + ClosePlatformFile(file); +#endif + + // Verify the written data. + char buffer[10]; + EXPECT_EQ(data_bytes, file_util::ReadFile(test_path(), buffer, data_bytes)); + for (int i = 0; i < data_bytes; ++i) { + EXPECT_EQ(data[i], buffer[i]); + } +} + +#if !defined(__LB_PS3__) && !defined(__LB_WIIU__) && !defined(OS_STARBOARD) +// FileUtilProxy::Touch creates a platform file and passes its descriptor to +// base::TouchPlatformFile(). +// This requires OS support for futimes. + +TEST_F(FileUtilProxyTest, Touch) { + Time last_accessed_time = Time::Now() - TimeDelta::FromDays(12345); + Time last_modified_time = Time::Now() - TimeDelta::FromHours(98765); + + FileUtilProxy::Touch( + file_task_runner(), + GetTestPlatformFile(PLATFORM_FILE_CREATE | + PLATFORM_FILE_WRITE | + PLATFORM_FILE_WRITE_ATTRIBUTES), + last_accessed_time, + last_modified_time, + Bind(&FileUtilProxyTest::DidFinish, weak_factory_.GetWeakPtr())); + MessageLoop::current()->Run(); + EXPECT_EQ(PLATFORM_FILE_OK, error_); + + PlatformFileInfo info; + file_util::GetFileInfo(test_path(), &info); + + // The returned values may only have the seconds precision, so we cast + // the double values to int here. + EXPECT_EQ(static_cast<int>(last_modified_time.ToDoubleT()), + static_cast<int>(info.last_modified.ToDoubleT())); + EXPECT_EQ(static_cast<int>(last_accessed_time.ToDoubleT()), + static_cast<int>(info.last_accessed.ToDoubleT())); +} +#endif + +TEST_F(FileUtilProxyTest, Truncate_Shrink) { + // Setup. + const char kTestData[] = "0123456789"; + ASSERT_EQ(10, file_util::WriteFile(test_path(), kTestData, 10)); + PlatformFileInfo info; + file_util::GetFileInfo(test_path(), &info); + ASSERT_EQ(10, info.size); + + // Run. + FileUtilProxy::Truncate( + file_task_runner(), + GetTestPlatformFile(PLATFORM_FILE_OPEN | PLATFORM_FILE_WRITE), + 7, + Bind(&FileUtilProxyTest::DidFinish, weak_factory_.GetWeakPtr())); + MessageLoop::current()->Run(); + + // Verify. + file_util::GetFileInfo(test_path(), &info); + ASSERT_EQ(7, info.size); + +#if defined(__LB_WIIU__) + // If the platform file is already created, GetTestPlatformFile will return + // the platform file, so the flags are irrelevant. + ClosePlatformFile(GetTestPlatformFile(0)); +#endif + + char buffer[7]; + EXPECT_EQ(7, file_util::ReadFile(test_path(), buffer, 7)); + int i = 0; + for (; i < 7; ++i) + EXPECT_EQ(kTestData[i], buffer[i]); +} + +TEST_F(FileUtilProxyTest, Truncate_Expand) { + // Setup. + const char kTestData[] = "9876543210"; + ASSERT_EQ(10, file_util::WriteFile(test_path(), kTestData, 10)); + PlatformFileInfo info; + file_util::GetFileInfo(test_path(), &info); + ASSERT_EQ(10, info.size); + + // Run. + FileUtilProxy::Truncate( + file_task_runner(), + GetTestPlatformFile(PLATFORM_FILE_OPEN | PLATFORM_FILE_WRITE), + 53, + Bind(&FileUtilProxyTest::DidFinish, weak_factory_.GetWeakPtr())); + MessageLoop::current()->Run(); + + // Verify. + file_util::GetFileInfo(test_path(), &info); + ASSERT_EQ(53, info.size); + +#if defined(__LB_WIIU__) + // If the platform file is already created, GetTestPlatformFile will return + // the platform file, so the flags are irrelevant. + ClosePlatformFile(GetTestPlatformFile(0)); +#endif + + char buffer[53]; + EXPECT_EQ(53, file_util::ReadFile(test_path(), buffer, 53)); + int i = 0; + for (; i < 10; ++i) + EXPECT_EQ(kTestData[i], buffer[i]); + for (; i < 53; ++i) + EXPECT_EQ(0, buffer[i]); +} + +} // namespace base
diff --git a/src/base/file_util_starboard.cc b/src/base/file_util_starboard.cc new file mode 100644 index 0000000..0c6511a --- /dev/null +++ b/src/base/file_util_starboard.cc
@@ -0,0 +1,602 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Adapted from file_util_posix.cc. + +#include "base/file_util.h" + +#include <stack> +#include <string> + +#include "base/base_paths.h" +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/platform_file.h" +#include "base/threading/thread_restrictions.h" +#include "base/time.h" +#include "starboard/directory.h" +#include "starboard/file.h" +#include "starboard/system.h" + +namespace { + +// The list of portable filename characters as per POSIX. This should be the +// lowest-common-denominator of acceptable filename characters. +const char kPortableFilenameCharacters[] = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "._-" +}; +const int kPortableFilenameCharactersLength = + SB_ARRAY_SIZE_INT(kPortableFilenameCharacters) - 1; + +// 8 characters = 65 ^ 8 possible filenames, which gives a nice wide space for +// avoiding collisions. +const char kTempSubstitution[] = "XXXXXXXX"; +const int kTempSubstitutionLength = SB_ARRAY_SIZE_INT(kTempSubstitution) - 1; + +std::string TempFileName() { + return std::string(".com.youtube.Cobalt.XXXXXXXX"); +} + +// Takes the template defined in |in_out_template| and destructively replaces +// any 'X' characters at the end with randomized portable filename characters. +void GenerateTempFileName(FilePath::StringType *in_out_template) { + size_t template_start = in_out_template->find(kTempSubstitution); + if (template_start == FilePath::StringType::npos) { + // No pattern. + return; + } + + for (int i = 0; i < kTempSubstitutionLength; ++i) { + uint64_t random = SbSystemGetRandomUInt64(); + int index = random % kPortableFilenameCharactersLength; + (*in_out_template)[template_start + i] = kPortableFilenameCharacters[index]; + } +} + +// Creates a random filename based on the TempFileName() pattern, creates the +// file, leaving it open, placing the path in |out_path| and returning the open +// SbFile. +SbFile CreateAndOpenTemporaryFile(FilePath directory, FilePath *out_path) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(out_path); + FilePath path = directory.Append(TempFileName()); + FilePath::StringType tmpdir_string = path.value(); + GenerateTempFileName(&tmpdir_string); + *out_path = FilePath(tmpdir_string); + return SbFileOpen(tmpdir_string.c_str(), kSbFileCreateOnly | kSbFileWrite, + NULL, NULL); +} + +// Retries creating a temporary file until it can win the race to create a +// unique one. +SbFile CreateAndOpenTemporaryFileSafely(FilePath directory, + FilePath *out_path) { + SbFile file = kSbFileInvalid; + while (!SbFileIsValid(file)) { + file = CreateAndOpenTemporaryFile(directory, out_path); + } + return file; +} + +bool CreateTemporaryDirInDirImpl(const FilePath &base_dir, + const FilePath::StringType &name_tmpl, + FilePath *new_dir) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(name_tmpl.find(kTempSubstitution) != FilePath::StringType::npos) + << "Directory name template must contain \"XXXXXXXX\"."; + + FilePath dir_template = base_dir.Append(name_tmpl); + std::string dir_template_string = dir_template.value(); + FilePath sub_dir; + while (true) { + std::string sub_dir_string = dir_template_string; + GenerateTempFileName(&sub_dir_string); + sub_dir = FilePath(sub_dir_string); + if (!file_util::DirectoryExists(sub_dir)) { + break; + } + } + + // NOTE: This is not as secure as mkdtemp, because it doesn't atomically + // guarantee that there is no collision. But, with 8 X's it should be good + // enough for our purposes. + if (!file_util::CreateDirectory(sub_dir)) { + DPLOG(ERROR) << "CreateDirectory"; + return false; + } + + *new_dir = sub_dir; + return true; +} + +} // namespace + +namespace file_util { + +bool AbsolutePath(FilePath* path) { + // We don't have cross-platform tools for this, so we will just return true if + // the path is already absolute. + return path->IsAbsolute(); +} + +bool Delete(const FilePath &path, bool recursive) { + base::ThreadRestrictions::AssertIOAllowed(); + const char *path_str = path.value().c_str(); + + bool directory = SbDirectoryCanOpen(path_str); + if (!recursive || !directory) { + return SbFileDelete(path_str); + } + + bool success = true; + std::stack<std::string> directories; + directories.push(path.value()); + + // NOTE: Right now, for Linux, SbFileGetInfo does not follow + // symlinks. This is good for avoiding deleting through symlinks, but makes it + // hard to use symlinks on platforms where they are supported. This seems + // safest for the lowest-common-denominator approach of pretending symlinks + // don't exist. + FileEnumerator traversal( + path, true, FileEnumerator::FILES | FileEnumerator::DIRECTORIES); + + // Delete all files and push all directories in depth order onto the stack. + for (FilePath current = traversal.Next(); + success && !current.empty(); + current = traversal.Next()) { + FileEnumerator::FindInfo info; + traversal.GetFindInfo(&info); + + if (FileEnumerator::IsDirectory(info)) { + directories.push(current.value()); + } else { + success = SbFileDelete(current.value().c_str()); + } + } + + // Delete all directories in reverse-depth order, now that they have no more + // regular files. + while (success && !directories.empty()) { + success = SbFileDelete(directories.top().c_str()); + directories.pop(); + } + + return success; +} + +bool CopyFile(const FilePath &from_path, const FilePath &to_path) { + base::ThreadRestrictions::AssertIOAllowed(); + + base::PlatformFile source_file = base::CreatePlatformFile( + from_path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, NULL, + NULL); + if (source_file == base::kInvalidPlatformFileValue) { + DPLOG(ERROR) << "CopyFile(): Unable to open source file: " + << from_path.value(); + return false; + } + + base::PlatformFile destination_file = base::CreatePlatformFileUnsafe( + to_path, base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, NULL, + NULL); + if (destination_file == base::kInvalidPlatformFileValue) { + DPLOG(ERROR) << "CopyFile(): Unable to open destination file: " + << to_path.value(); + ignore_result(base::ClosePlatformFile(destination_file)); + return false; + } + + const size_t kBufferSize = 32768; + std::vector<char> buffer(kBufferSize); + bool result = true; + + while (result) { + int bytes_read = base::ReadPlatformFileAtCurrentPos(source_file, &buffer[0], + buffer.size()); + if (bytes_read < 0) { + result = false; + break; + } + + if (bytes_read == 0) { + break; + } + + int bytes_written = base::WritePlatformFileAtCurrentPos( + destination_file, &buffer[0], bytes_read); + if (bytes_written < bytes_read) { + DLOG(ERROR) << "CopyFile(): bytes_read (" << bytes_read + << ") > bytes_written (" << bytes_written << ")"; + // Because we use a best-effort write, if we wrote less than what was + // available, something went wrong. + result = false; + break; + } + } + + if (!base::ClosePlatformFile(source_file)) { + result = false; + } + + if (!base::ClosePlatformFile(destination_file)) { + result = false; + } + + return result; +} + +bool PathExists(const FilePath &path) { + base::ThreadRestrictions::AssertIOAllowed(); + return SbFileExists(path.value().c_str()); +} + +bool PathIsWritable(const FilePath &path) { + base::ThreadRestrictions::AssertIOAllowed(); + return SbFileCanOpen(path.value().c_str(), kSbFileOpenAlways | kSbFileWrite); +} + +bool DirectoryExists(const FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + SbFileInfo info; + if (!SbFileGetPathInfo(path.value().c_str(), &info)) { + return false; + } + + return info.is_directory; +} + +bool GetTempDir(FilePath *path) { + char buffer[SB_FILE_MAX_PATH + 1] = {0}; + bool result = SbSystemGetPath(kSbSystemPathTempDirectory, buffer, + SB_ARRAY_SIZE_INT(buffer)); + if (!result) { + return false; + } + + *path = FilePath(buffer); + if (DirectoryExists(*path)) { + return true; + } + + return CreateDirectory(*path); +} + +bool GetShmemTempDir(FilePath *path, bool executable) { + return GetTempDir(path); +} + +FilePath GetHomeDir() { + FilePath path; + bool result = PathService::Get(base::DIR_HOME, &path); + DCHECK(result); + return path; +} + +bool CreateTemporaryFile(FilePath *path) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(path); + + FilePath directory; + if (!GetTempDir(&directory)) { + return false; + } + + return CreateTemporaryFileInDir(directory, path); +} + +bool CreateTemporaryFileInDir(const FilePath &dir, FilePath *temp_file) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(temp_file); + SbFile file = CreateAndOpenTemporaryFileSafely(dir, temp_file); + return (SbFileIsValid(file) && SbFileClose(file)); +} + +bool CreateTemporaryDirInDir(const FilePath &base_dir, + const FilePath::StringType &prefix, + FilePath *new_dir) { + FilePath::StringType mkdtemp_template = prefix + kTempSubstitution; + return CreateTemporaryDirInDirImpl(base_dir, mkdtemp_template, new_dir); +} + +bool CreateNewTempDirectory(const FilePath::StringType &prefix, + FilePath *new_temp_path) { + FilePath tmpdir; + if (!GetTempDir(&tmpdir)) { + return false; + } + + return CreateTemporaryDirInDirImpl(tmpdir, TempFileName(), new_temp_path); +} + +bool CreateDirectory(const FilePath &full_path) { + base::ThreadRestrictions::AssertIOAllowed(); + std::vector<FilePath> subpaths; + + // Collect a list of all parent directories. + FilePath last_path = full_path; + subpaths.push_back(full_path); + for (FilePath path = full_path.DirName(); + path.value() != last_path.value(); + path = path.DirName()) { + subpaths.push_back(path); + last_path = path; + } + + // Iterate through the parents and create the missing ones. + for (std::vector<FilePath>::reverse_iterator i = subpaths.rbegin(); + i != subpaths.rend(); ++i) { + if (DirectoryExists(*i)) { + continue; + } + + if (!SbDirectoryCreate(i->value().c_str())) { + return false; + } + } + + return true; +} + +bool IsLink(const FilePath &file_path) { + base::ThreadRestrictions::AssertIOAllowed(); + SbFileInfo info; + + // If we can't SbfileGetPathInfo on the file, it's safe to assume that the + // file won't at least be a 'followable' link. + if (!SbFileGetPathInfo(file_path.value().c_str(), &info)) { + return false; + } + + return info.is_symbolic_link; +} + +bool GetFileInfo(const FilePath &file_path, base::PlatformFileInfo *results) { + base::ThreadRestrictions::AssertIOAllowed(); + SbFileInfo info; + if (!SbFileGetPathInfo(file_path.value().c_str(), &info)) { + return false; + } + + results->is_directory = info.is_directory; + results->size = info.size; + results->last_modified = base::Time::FromSbTime(info.last_modified); + results->last_accessed = base::Time::FromSbTime(info.last_accessed); + results->creation_time = base::Time::FromSbTime(info.creation_time); + return true; +} + +int ReadFile(const FilePath &filename, char *data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + + base::PlatformFile file = base::CreatePlatformFile( + filename, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, NULL, + NULL); + if (file == base::kInvalidPlatformFileValue) { + DLOG(ERROR) << "ReadFile(" << filename.value() << "): Unable to open."; + return -1; + } + + // We use a best-effort read here. + int bytes_read = base::ReadPlatformFileAtCurrentPos(file, data, size); + if (!base::ClosePlatformFile(file)) { + DLOG(ERROR) << "ReadFile(" << filename.value() << "): Unable to close."; + return -1; + } + + return bytes_read; +} + +int WriteFile(const FilePath &filename, const char *data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + + base::PlatformFile file = base::CreatePlatformFile( + filename, base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE, + NULL, NULL); + if (file == base::kInvalidPlatformFileValue) { + DLOG(ERROR) << "WriteFile(" << filename.value() << "): Unable to open."; + return -1; + } + + // We use a best-effort write here. + int bytes_written = base::WritePlatformFileAtCurrentPos(file, data, size); + if (!base::ClosePlatformFile(file)) { + DLOG(ERROR) << "WriteFile(" << filename.value() << "): Unable to close."; + return -1; + } + + return bytes_written; +} + +int AppendToFile(const FilePath &filename, const char *data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + base::PlatformFile file = base::CreatePlatformFile( + filename, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE, NULL, + NULL); + if (file == base::kInvalidPlatformFileValue) { + DLOG(ERROR) << "AppendToFile(" << filename.value() << "): Unable to open."; + return -1; + } + + if (!base::SeekPlatformFile(file, base::PLATFORM_FILE_FROM_END, 0)) { + DLOG(ERROR) << "AppendToFile(" << filename.value() + << "): Unable to truncate."; + return -1; + } + + int bytes_written = base::WritePlatformFileAtCurrentPos(file, data, size); + if (!base::ClosePlatformFile(file)) { + DLOG(ERROR) << "AppendToFile(" << filename.value() << "): Unable to close."; + return -1; + } + + return bytes_written; +} + +bool HasFileBeenModifiedSince(const FileEnumerator::FindInfo &find_info, + const base::Time &cutoff_time) { + return FileEnumerator::GetLastModifiedTime(find_info) >= cutoff_time; +} + + +/////////////////////////////////////////////// +// FileEnumerator + +FileEnumerator::FileEnumerator(const FilePath &root_path, + bool recursive, + int file_type) + : current_directory_entry_(0), + root_path_(root_path), + recursive_(recursive), + file_type_(file_type) { + // INCLUDE_DOT_DOT must not be specified if recursive. + DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_))); + pending_paths_.push(root_path); +} + +FileEnumerator::FileEnumerator(const FilePath &root_path, + bool recursive, + int file_type, + const FilePath::StringType &pattern) + : current_directory_entry_(0), + root_path_(root_path), + recursive_(recursive), + file_type_(file_type), + pattern_(root_path.Append(pattern).value()) { + // INCLUDE_DOT_DOT must not be specified if recursive. + DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_))); + // The Windows version of this code appends the pattern to the root_path, + // potentially only matching against items in the top-most directory. + // Do the same here. + if (pattern.empty()) { + pattern_ = FilePath::StringType(); + } + pending_paths_.push(root_path); +} + +FileEnumerator::~FileEnumerator() { +} + +FilePath FileEnumerator::Next() { + ++current_directory_entry_; + + // While we've exhausted the entries in the current directory, do the next + while (current_directory_entry_ >= directory_entries_.size()) { + if (pending_paths_.empty()) { + return FilePath(); + } + + root_path_ = pending_paths_.top(); + root_path_ = root_path_.StripTrailingSeparators(); + pending_paths_.pop(); + + std::vector<DirectoryEntryInfo> entries; + if (!ReadDirectory(&entries, root_path_)) { + continue; + } + + directory_entries_.clear(); + current_directory_entry_ = 0; + for (std::vector<DirectoryEntryInfo>::const_iterator i = entries.begin(); + i != entries.end(); ++i) { + FilePath full_path = root_path_.Append(i->filename); + if (ShouldSkip(full_path)) { + continue; + } + + if (pattern_.size()) { + NOTREACHED() << "Patterns not supported in Starboard."; + continue; + } + + if (recursive_ && i->sb_info.is_directory) { + pending_paths_.push(full_path); + } + + if ((i->sb_info.is_directory && (file_type_ & DIRECTORIES)) || + (!i->sb_info.is_directory && (file_type_ & FILES))) { + directory_entries_.push_back(*i); + } + } + } + + return + root_path_.Append(directory_entries_[current_directory_entry_].filename); +} + +void FileEnumerator::GetFindInfo(FindInfo *info) { + DCHECK(info); + + if (current_directory_entry_ >= directory_entries_.size()) { + return; + } + + DirectoryEntryInfo *cur_entry = &directory_entries_[current_directory_entry_]; + memcpy(&(info->sb_info), &(cur_entry->sb_info), sizeof(info->sb_info)); + info->filename.assign(cur_entry->filename.value()); +} + +// static +bool FileEnumerator::IsDirectory(const FindInfo &info) { + return info.sb_info.is_directory; +} + +// static +FilePath FileEnumerator::GetFilename(const FindInfo &find_info) { + return FilePath(find_info.filename); +} + +// static +int64 FileEnumerator::GetFilesize(const FindInfo &find_info) { + return find_info.sb_info.size; +} + +// static +base::Time FileEnumerator::GetLastModifiedTime(const FindInfo &find_info) { + return base::Time::FromSbTime(find_info.sb_info.last_modified); +} + +// static +bool FileEnumerator::ReadDirectory(std::vector<DirectoryEntryInfo> *entries, + const FilePath &source) { + base::ThreadRestrictions::AssertIOAllowed(); + SbDirectory dir = SbDirectoryOpen(source.value().c_str(), NULL); + if (!SbDirectoryIsValid(dir)) { + return false; + } + + SbDirectoryEntry entry; + while (SbDirectoryGetNext(dir, &entry)) { + DirectoryEntryInfo info; + info.filename = FilePath(entry.name); + + FilePath full_name = source.Append(entry.name); + // TODO: Make sure this follows symlinks on relevant platforms. + if (!SbFileGetPathInfo(full_name.value().c_str(), &info.sb_info)) { + DPLOG(ERROR) << "Couldn't SbFileGetInfo on " << full_name.value(); + memset(&info.sb_info, 0, sizeof(info.sb_info)); + } + + entries->push_back(info); + } + + ignore_result(SbDirectoryClose(dir)); + return true; +} + +} // namespace file_util
diff --git a/src/base/file_util_unittest.cc b/src/base/file_util_unittest.cc new file mode 100644 index 0000000..6f4e111 --- /dev/null +++ b/src/base/file_util_unittest.cc
@@ -0,0 +1,2493 @@ +// 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 "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#include <shellapi.h> +#include <shlobj.h> +#include <tchar.h> +#include <winioctl.h> +#endif + +#if !defined(__LB_PS3__) +// These headers are not available on the PS3 +#include <algorithm> +#include <fstream> +#endif +#include <set> + +#include "base/base_paths.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/path_service.h" +#include "base/test/test_file_util.h" +#include "base/threading/platform_thread.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +#if defined(OS_WIN) +#include "base/win/scoped_handle.h" +#endif + +// This macro helps avoid wrapped lines in the test structs. +#define FPL(x) FILE_PATH_LITERAL(x) + +#if defined(COBALT) || defined(OS_STARBOARD) +#define MAYBE_CopyDirectoryRecursivelyNew DISABLED_CopyDirectoryRecursivelyNew +#define MAYBE_CopyDirectoryRecursivelyExists \ + DISABLED_CopyDirectoryRecursivelyExists +#define MAYBE_CopyDirectoryNew DISABLED_CopyDirectoryNew +#define MAYBE_CopyDirectoryExists DISABLED_CopyDirectoryExists +#define MAYBE_CopyFileWithCopyDirectoryRecursiveToNew \ + DISABLED_CopyFileWithCopyDirectoryRecursiveToNew +#define MAYBE_CopyFileWithCopyDirectoryRecursiveToExisting \ + DISABLED_CopyFileWithCopyDirectoryRecursiveToExisting +#define MAYBE_CopyFileWithCopyDirectoryRecursiveToExistingDirectory \ + DISABLED_CopyFileWithCopyDirectoryRecursiveToExistingDirectory +#define MAYBE_CopyDirectoryWithTrailingSeparators \ + DISABLED_CopyDirectoryWithTrailingSeparators +#else +#define MAYBE_CopyDirectoryRecursivelyNew CopyDirectoryRecursivelyNew +#define MAYBE_CopyDirectoryRecursivelyExists CopyDirectoryRecursivelyExists +#define MAYBE_CopyDirectoryNew CopyDirectoryNew +#define MAYBE_CopyDirectoryExists CopyDirectoryExists +#define MAYBE_CopyFileWithCopyDirectoryRecursiveToNew \ + CopyFileWithCopyDirectoryRecursiveToNew +#define MAYBE_CopyFileWithCopyDirectoryRecursiveToExisting \ + CopyFileWithCopyDirectoryRecursiveToExisting +#define MAYBE_CopyFileWithCopyDirectoryRecursiveToExistingDirectory \ + CopyFileWithCopyDirectoryRecursiveToExistingDirectory +#define MAYBE_CopyDirectoryWithTrailingSeparators \ + CopyDirectoryWithTrailingSeparators +#endif + +namespace { + +// To test that file_util::Normalize FilePath() deals with NTFS reparse points +// correctly, we need functions to create and delete reparse points. +#if defined(OS_WIN) +typedef struct _REPARSE_DATA_BUFFER { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + }; +} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; + +// Sets a reparse point. |source| will now point to |target|. Returns true if +// the call succeeds, false otherwise. +bool SetReparsePoint(HANDLE source, const FilePath& target_path) { + std::wstring kPathPrefix = L"\\??\\"; + std::wstring target_str; + // The juction will not work if the target path does not start with \??\ . + if (kPathPrefix != target_path.value().substr(0, kPathPrefix.size())) + target_str += kPathPrefix; + target_str += target_path.value(); + const wchar_t* target = target_str.c_str(); + USHORT size_target = static_cast<USHORT>(wcslen(target)) * sizeof(target[0]); + char buffer[2000] = {0}; + DWORD returned; + + REPARSE_DATA_BUFFER* data = reinterpret_cast<REPARSE_DATA_BUFFER*>(buffer); + + data->ReparseTag = 0xa0000003; + memcpy(data->MountPointReparseBuffer.PathBuffer, target, size_target + 2); + + data->MountPointReparseBuffer.SubstituteNameLength = size_target; + data->MountPointReparseBuffer.PrintNameOffset = size_target + 2; + data->ReparseDataLength = size_target + 4 + 8; + + int data_size = data->ReparseDataLength + 8; + + if (!DeviceIoControl(source, FSCTL_SET_REPARSE_POINT, &buffer, data_size, + NULL, 0, &returned, NULL)) { + return false; + } + return true; +} + +// Delete the reparse point referenced by |source|. Returns true if the call +// succeeds, false otherwise. +bool DeleteReparsePoint(HANDLE source) { + DWORD returned; + REPARSE_DATA_BUFFER data = {0}; + data.ReparseTag = 0xa0000003; + if (!DeviceIoControl(source, FSCTL_DELETE_REPARSE_POINT, &data, 8, NULL, 0, + &returned, NULL)) { + return false; + } + return true; +} +#endif + +#if defined(OS_POSIX) +// Provide a simple way to change the permissions bits on |path| in tests. +// ASSERT failures will return, but not stop the test. Caller should wrap +// calls to this function in ASSERT_NO_FATAL_FAILURE(). +void ChangePosixFilePermissions(const FilePath& path, + int mode_bits_to_set, + int mode_bits_to_clear) { + ASSERT_FALSE(mode_bits_to_set & mode_bits_to_clear) + << "Can't set and clear the same bits."; + + int mode = 0; + ASSERT_TRUE(file_util::GetPosixFilePermissions(path, &mode)); + mode |= mode_bits_to_set; + mode &= ~mode_bits_to_clear; + ASSERT_TRUE(file_util::SetPosixFilePermissions(path, mode)); +} +#endif // defined(OS_POSIX) + +const wchar_t bogus_content[] = L"I'm cannon fodder."; + +const int FILES_AND_DIRECTORIES = + file_util::FileEnumerator::FILES | file_util::FileEnumerator::DIRECTORIES; + +// file_util winds up using autoreleased objects on the Mac, so this needs +// to be a PlatformTest +class FileUtilTest : public PlatformTest { + protected: + virtual void SetUp() OVERRIDE { + PlatformTest::SetUp(); + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + } + + base::ScopedTempDir temp_dir_; +}; + +// Collects all the results from the given file enumerator, and provides an +// interface to query whether a given file is present. +class FindResultCollector { + public: + explicit FindResultCollector(file_util::FileEnumerator& enumerator) { + FilePath cur_file; + while (!(cur_file = enumerator.Next()).value().empty()) { + FilePath::StringType path = cur_file.value(); + // The file should not be returned twice. + EXPECT_TRUE(files_.end() == files_.find(path)) + << "Same file returned twice"; + + // Save for later. + files_.insert(path); + } + } + + // Returns true if the enumerator found the file. + bool HasFile(const FilePath& file) const { + return files_.find(file.value()) != files_.end(); + } + + int size() { + return static_cast<int>(files_.size()); + } + + private: + std::set<FilePath::StringType> files_; +}; + +// Simple function to dump some text into a new file. +void CreateTextFile(const FilePath& filename, + const std::wstring& contents) { +#if defined(__LB_PS3__) + FILE *file = fopen(filename.value().c_str(), "w"); + ASSERT_TRUE(file != NULL); + fputws(contents.c_str(), file); + fclose(file); +#elif defined(__LB_WIIU__) + int fd = open(filename.value().c_str(), O_WRONLY | O_CREAT | O_TRUNC); + ASSERT_TRUE(fd >= 0); + char mb_sequence[64]; + const wchar_t* wc_string = contents.c_str(); + size_t nbytes = wcsrtombs(mb_sequence, &wc_string, arraysize(mb_sequence), NULL); + ssize_t bytes_written = write(fd, mb_sequence, nbytes); + ASSERT_TRUE(bytes_written == nbytes); + ASSERT_TRUE(wc_string == NULL); + close(fd); +#else + std::wofstream file; + file.open(filename.value().c_str()); + ASSERT_TRUE(file.is_open()); + file << contents; + file.close(); +#endif +} + +// Simple function to take out some text from a file. +std::wstring ReadTextFile(const FilePath& filename) { + wchar_t contents[64]; +#if defined(__LB_PS3__) + FILE *file = fopen(filename.value().c_str(), "r"); + EXPECT_TRUE(file != NULL); + fgetws(contents, arraysize(contents), file); + fclose(file); +#elif defined(__LB_WIIU__) + char mb_sequence_buffer[64]; + int fd = open(filename.value().c_str(), O_RDONLY); + EXPECT_TRUE(fd >= 0); + ssize_t bytes_read = read(fd, mb_sequence_buffer, arraysize(mb_sequence_buffer)); + EXPECT_TRUE(bytes_read >= 0); + close(fd); + const char * mb_sequence = mb_sequence_buffer; + size_t nbytes = mbsrtowcs(contents, &mb_sequence, sizeof(contents), NULL); + EXPECT_TRUE(mb_sequence == NULL); +#else + std::wifstream file; + file.open(filename.value().c_str()); + EXPECT_TRUE(file.is_open()); + file.getline(contents, arraysize(contents)); + file.close(); +#endif + return std::wstring(contents); +} + +#if defined(OS_WIN) +uint64 FileTimeAsUint64(const FILETIME& ft) { + ULARGE_INTEGER u; + u.LowPart = ft.dwLowDateTime; + u.HighPart = ft.dwHighDateTime; + return u.QuadPart; +} +#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)); + EXPECT_EQ(20ll, size_f1); + + FilePath subdir_path = temp_dir_.path().Append(FPL("Level2")); + file_util::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)); + EXPECT_EQ(30ll, size_f2); + + FilePath subsubdir_path = subdir_path.Append(FPL("Level3")); + file_util::CreateDirectory(subsubdir_path); + + FilePath file_03 = subsubdir_path.Append(FPL("The file 03.txt")); + CreateTextFile(file_03, L"123"); + + int64 computed_size = file_util::ComputeDirectorySize(temp_dir_.path()); + EXPECT_EQ(size_f1 + size_f2 + 3, computed_size); + +#if !defined(OS_STARBOARD) + // No pattern support in Starboard. + computed_size = + file_util::ComputeFilesSize(temp_dir_.path(), FPL("The file*")); + EXPECT_EQ(size_f1, computed_size); + + computed_size = file_util::ComputeFilesSize(temp_dir_.path(), FPL("bla*")); + EXPECT_EQ(0, computed_size); +#endif +} + +#if !defined(OS_STARBOARD) +TEST_F(FileUtilTest, NormalizeFilePathBasic) { + // Create a directory under the test dir. Because we create it, + // we know it is not a link. + 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); + + FilePath normalized_file_a_path, normalized_file_b_path; + ASSERT_FALSE(file_util::PathExists(file_a_path)); + ASSERT_FALSE(file_util::NormalizeFilePath(file_a_path, + &normalized_file_a_path)) + << "NormalizeFilePath() should fail on nonexistent paths."; + + CreateTextFile(file_a_path, bogus_content); + ASSERT_TRUE(file_util::PathExists(file_a_path)); + ASSERT_TRUE(file_util::NormalizeFilePath(file_a_path, + &normalized_file_a_path)); + + CreateTextFile(file_b_path, bogus_content); + ASSERT_TRUE(file_util::PathExists(file_b_path)); + ASSERT_TRUE(file_util::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 + // must be the parent of the path holding file b. + ASSERT_TRUE(normalized_file_a_path.DirName() + .IsParent(normalized_file_b_path.DirName())); +} +#endif + +#if defined(OS_WIN) + +TEST_F(FileUtilTest, NormalizeFilePathReparsePoints) { + // Build the following directory structure: + // + // temp_dir + // |-> base_a + // | |-> sub_a + // | |-> file.txt + // | |-> long_name___... (Very long name.) + // | |-> sub_long + // | |-> deep.txt + // |-> base_b + // |-> to_sub_a (reparse point to temp_dir\base_a\sub_a) + // |-> to_base_b (reparse point to temp_dir\base_b) + // |-> 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)); + + FilePath sub_a = base_a.Append(FPL("sub_a")); + ASSERT_TRUE(file_util::CreateDirectory(sub_a)); + + FilePath file_txt = sub_a.Append(FPL("file.txt")); + CreateTextFile(file_txt, bogus_content); + + // Want a directory whose name is long enough to make the path to the file + // inside just under MAX_PATH chars. This will be used to test that when + // a junction expands to a path over MAX_PATH chars in length, + // NormalizeFilePath() fails without crashing. + FilePath sub_long_rel(FPL("sub_long")); + FilePath deep_txt(FPL("deep.txt")); + + int target_length = MAX_PATH; + target_length -= (sub_a.value().length() + 1); // +1 for the sepperator '\'. + target_length -= (sub_long_rel.Append(deep_txt).value().length() + 1); + // Without making the path a bit shorter, CreateDirectory() fails. + // the resulting path is still long enough to hit the failing case in + // NormalizePath(). + const int kCreateDirLimit = 4; + target_length -= kCreateDirLimit; + FilePath::StringType long_name_str = FPL("long_name_"); + long_name_str.resize(target_length, '_'); + + FilePath long_name = sub_a.Append(FilePath(long_name_str)); + FilePath deep_file = long_name.Append(sub_long_rel).Append(deep_txt); + ASSERT_EQ(MAX_PATH - kCreateDirLimit, deep_file.value().length()); + + FilePath sub_long = deep_file.DirName(); + ASSERT_TRUE(file_util::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)); + + FilePath to_sub_a = base_b.Append(FPL("to_sub_a")); + ASSERT_TRUE(file_util::CreateDirectory(to_sub_a)); + base::win::ScopedHandle reparse_to_sub_a( + ::CreateFile(to_sub_a.value().c_str(), + FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory. + NULL)); + ASSERT_TRUE(reparse_to_sub_a.IsValid()); + ASSERT_TRUE(SetReparsePoint(reparse_to_sub_a, sub_a)); + + FilePath to_base_b = base_b.Append(FPL("to_base_b")); + ASSERT_TRUE(file_util::CreateDirectory(to_base_b)); + base::win::ScopedHandle reparse_to_base_b( + ::CreateFile(to_base_b.value().c_str(), + FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory. + NULL)); + ASSERT_TRUE(reparse_to_base_b.IsValid()); + ASSERT_TRUE(SetReparsePoint(reparse_to_base_b, base_b)); + + FilePath to_sub_long = base_b.Append(FPL("to_sub_long")); + ASSERT_TRUE(file_util::CreateDirectory(to_sub_long)); + base::win::ScopedHandle reparse_to_sub_long( + ::CreateFile(to_sub_long.value().c_str(), + FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory. + NULL)); + ASSERT_TRUE(reparse_to_sub_long.IsValid()); + ASSERT_TRUE(SetReparsePoint(reparse_to_sub_long, sub_long)); + + // Normalize a junction free path: base_a\sub_a\file.txt . + FilePath normalized_path; + ASSERT_TRUE(file_util::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")), + &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")) + .Append(FPL("to_base_b")) + .Append(FPL("to_sub_a")) + .Append(FPL("file.txt")), + &normalized_path)); + ASSERT_STREQ(file_txt.value().c_str(), normalized_path.value().c_str()); + + // A long enough path will cause NormalizeFilePath() to fail. Make a long + // path using to_base_b many times, and check that paths long enough to fail + // do not cause a crash. + FilePath long_path = base_b; + const int kLengthLimit = MAX_PATH + 200; + while (long_path.value().length() <= kLengthLimit) { + long_path = long_path.Append(FPL("to_base_b")); + } + long_path = long_path.Append(FPL("to_sub_a")) + .Append(FPL("file.txt")); + + ASSERT_FALSE(file_util::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), + &normalized_path)); + + // Delete the reparse points, and see that NormalizeFilePath() fails + // to traverse them. + ASSERT_TRUE(DeleteReparsePoint(reparse_to_sub_a)); + ASSERT_TRUE(DeleteReparsePoint(reparse_to_base_b)); + ASSERT_TRUE(DeleteReparsePoint(reparse_to_sub_long)); + + ASSERT_FALSE(file_util::NormalizeFilePath(to_sub_a.Append(FPL("file.txt")), + &normalized_path)); +} + +TEST_F(FileUtilTest, DevicePathToDriveLetter) { + // Get a drive letter. + std::wstring real_drive_letter = temp_dir_.path().value().substr(0, 2); + if (!isalpha(real_drive_letter[0]) || ':' != real_drive_letter[1]) { + LOG(ERROR) << "Can't get a drive letter to test with."; + return; + } + + // Get the NT style path to that drive. + wchar_t device_path[MAX_PATH] = {'\0'}; + ASSERT_TRUE( + ::QueryDosDevice(real_drive_letter.c_str(), device_path, MAX_PATH)); + FilePath actual_device_path(device_path); + FilePath win32_path; + + // 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_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( + actual_device_path.Append(kRelativePath), + &win32_path)); + EXPECT_EQ(FilePath(real_drive_letter + L"\\").Append(kRelativePath).value(), + win32_path.value()); + + // Deform the real path so that it is invalid by removing the last four + // characters. The way windows names devices that are hard disks + // (\Device\HardDiskVolume${NUMBER}) guarantees that the string is longer + // than three characters. The only way the truncated string could be a + // real drive is if more than 10^3 disks are mounted: + // \Device\HardDiskVolume10000 would be truncated to \Device\HardDiskVolume1 + // Check that DevicePathToDriveLetterPath fails. + int path_length = actual_device_path.value().length(); + int new_length = path_length - 4; + 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(file_util::DevicePathToDriveLetterPath( + prefix_of_real_device_path.Append(kRelativePath), + &win32_path)); + + // Deform the real path so that it is invalid by adding some characters. For + // example, if C: maps to \Device\HardDiskVolume8, then we simulate a + // request for the drive letter whose native path is + // \Device\HardDiskVolume812345 . We assume such a device does not exist, + // because drives are numbered in order and mounting 112345 hard disks will + // never happen. + const FilePath::StringType kExtraChars = FPL("12345"); + + FilePath real_device_path_plus_numbers( + actual_device_path.value() + kExtraChars); + + ASSERT_FALSE(file_util::DevicePathToDriveLetterPath( + real_device_path_plus_numbers, + &win32_path)); + + ASSERT_FALSE(file_util::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( + ::CreateFile(empty_dir.value().c_str(), + FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory. + NULL)); + ASSERT_TRUE(dir.IsValid()); + base::PlatformFileInfo info; + EXPECT_TRUE(base::GetPlatformFileInfo(dir.Get(), &info)); + EXPECT_TRUE(info.is_directory); + EXPECT_FALSE(info.is_symbolic_link); + EXPECT_EQ(0, info.size); +} + +TEST_F(FileUtilTest, CreateTemporaryFileInDirLongPathTest) { + // Test that CreateTemporaryFileInDir() creates a path and returns a long path + // if it is available. This test requires that: + // - the filesystem at |temp_dir_| supports long filenames. + // - the account has FILE_LIST_DIRECTORY permission for all ancestor + // directories of |temp_dir_|. + 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)); + + // kLongDirName is not a 8.3 component. So GetShortName() should give us a + // different short name. + WCHAR path_buffer[MAX_PATH]; + DWORD path_buffer_length = GetShortPathName(long_test_dir.value().c_str(), + path_buffer, MAX_PATH); + ASSERT_LT(path_buffer_length, DWORD(MAX_PATH)); + ASSERT_NE(DWORD(0), path_buffer_length); + FilePath short_test_dir(path_buffer); + ASSERT_STRNE(kLongDirName, short_test_dir.BaseName().value().c_str()); + + FilePath temp_file; + ASSERT_TRUE(file_util::CreateTemporaryFileInDir(short_test_dir, &temp_file)); + EXPECT_STREQ(kLongDirName, temp_file.DirName().BaseName().value().c_str()); + EXPECT_TRUE(file_util::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 + // subdirectory, but we won't be able to determine the long path for it. This + // mimics the environment that some users run where their user profiles reside + // in a location where the don't have full access to the higher level + // 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)); + 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( + short_test_dir.Append(kTestSubDirName), &temp_file)); + EXPECT_TRUE(file_util::PathExists(temp_file)); + EXPECT_TRUE(short_test_dir.IsParent(temp_file.DirName())); + + // Check that the long path can't be determined for |temp_file|. + path_buffer_length = GetLongPathName(temp_file.value().c_str(), + path_buffer, MAX_PATH); + EXPECT_EQ(DWORD(0), path_buffer_length); +} + +#endif // defined(OS_WIN) + +#if defined(OS_POSIX) && !defined(__LB_SHELL__) +// Symbolic links not supported in lbshell + +TEST_F(FileUtilTest, CreateAndReadSymlinks) { + FilePath link_from = temp_dir_.path().Append(FPL("from_file")); + FilePath link_to = temp_dir_.path().Append(FPL("to_file")); + CreateTextFile(link_to, bogus_content); + + ASSERT_TRUE(file_util::CreateSymbolicLink(link_to, link_from)) + << "Failed to create file symlink."; + + // If we created the link properly, we should be able to read the + // contents through it. + std::wstring contents = ReadTextFile(link_from); + ASSERT_EQ(contents, bogus_content); + + FilePath result; + ASSERT_TRUE(file_util::ReadSymbolicLink(link_from, &result)); + ASSERT_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")); + file_util::CreateDirectory(link_to); + + ASSERT_TRUE(file_util::CreateSymbolicLink(link_to, link_from)) + << "Failed to create directory symlink."; + + // Test failures. + ASSERT_FALSE(file_util::CreateSymbolicLink(link_to, link_to)); + ASSERT_FALSE(file_util::ReadSymbolicLink(link_to, &result)); + FilePath missing = temp_dir_.path().Append(FPL("missing")); + ASSERT_FALSE(file_util::ReadSymbolicLink(missing, &result)); +} + +// The following test of NormalizeFilePath() require that we create a symlink. +// This can not be done on Windows before Vista. On Vista, creating a symlink +// requires privilege "SeCreateSymbolicLinkPrivilege". +// TODO(skerner): Investigate the possibility of giving base_unittests the +// privileges required to create a symlink. +TEST_F(FileUtilTest, NormalizeFilePathSymlinks) { + FilePath normalized_path; + + // Link one file to another. + FilePath link_from = temp_dir_.path().Append(FPL("from_file")); + FilePath link_to = temp_dir_.path().Append(FPL("to_file")); + CreateTextFile(link_to, bogus_content); + + ASSERT_TRUE(file_util::CreateSymbolicLink(link_to, link_from)) + << "Failed to create file symlink."; + + // Check that NormalizeFilePath sees the link. + ASSERT_TRUE(file_util::NormalizeFilePath(link_from, &normalized_path)); + ASSERT_TRUE(link_to != link_from); + ASSERT_EQ(link_to.BaseName().value(), normalized_path.BaseName().value()); + ASSERT_EQ(link_to.BaseName().value(), normalized_path.BaseName().value()); + + // Link to a directory. + link_from = temp_dir_.path().Append(FPL("from_dir")); + link_to = temp_dir_.path().Append(FPL("to_dir")); + file_util::CreateDirectory(link_to); + + ASSERT_TRUE(file_util::CreateSymbolicLink(link_to, link_from)) + << "Failed to create directory symlink."; + + ASSERT_FALSE(file_util::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)) + << "Failed to create loop symlink a."; + ASSERT_TRUE(file_util::CreateSymbolicLink(link_from, link_to)) + << "Failed to create loop symlink b."; + + // Infinite loop! + ASSERT_FALSE(file_util::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(file_util::PathExists(non_existent)); + + EXPECT_TRUE(file_util::Delete(non_existent, false)); + ASSERT_FALSE(file_util::PathExists(non_existent)); + EXPECT_TRUE(file_util::Delete(non_existent, true)); + ASSERT_FALSE(file_util::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(file_util::PathExists(file_name)); + + // Make sure it's deleted + EXPECT_TRUE(file_util::Delete(file_name, false)); + EXPECT_FALSE(file_util::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(file_util::PathExists(file_name)); + + // Make sure it's deleted + EXPECT_TRUE(file_util::Delete(file_name, true)); + EXPECT_FALSE(file_util::PathExists(file_name)); +} + +#if defined(OS_POSIX) && !defined(__LB_SHELL__) +// Symbolic links not supported in lbshell + +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(file_util::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)) + << "Failed to create symlink."; + + // Delete the symbolic link. + EXPECT_TRUE(file_util::Delete(file_link, false)); + + // Make sure original file is not deleted. + EXPECT_FALSE(file_util::PathExists(file_link)); + EXPECT_TRUE(file_util::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(file_util::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)) + << "Failed to create symlink."; + + // Make sure the symbolic link is exist. + EXPECT_TRUE(file_util::IsLink(file_link)); + EXPECT_FALSE(file_util::PathExists(file_link)); + + // Delete the symbolic link. + EXPECT_TRUE(file_util::Delete(file_link, false)); + + // Make sure the symbolic link is deleted. + EXPECT_FALSE(file_util::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(file_util::PathExists(file_name)); + + const std::string kData("hello"); + + int buffer_size = kData.length(); + char* buffer = new char[buffer_size]; + + // Write file. + EXPECT_EQ(static_cast<int>(kData.length()), + file_util::WriteFile(file_name, kData.data(), kData.length())); + EXPECT_TRUE(file_util::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); + + // 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); + // Make sure the file can't be read. + EXPECT_EQ(-1, file_util::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); + // Make sure the file can be read. + EXPECT_EQ(static_cast<int>(kData.length()), + file_util::ReadFile(file_name, buffer, buffer_size)); + + // Delete the file. + EXPECT_TRUE(file_util::Delete(file_name, false)); + EXPECT_FALSE(file_util::PathExists(file_name)); + + delete[] buffer; +} + +TEST_F(FileUtilTest, ChangeFilePermissionsAndWrite) { + // Create a file path. + FilePath file_name = temp_dir_.path().Append(FPL("Test Readable File.txt")); + EXPECT_FALSE(file_util::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(file_util::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(file_util::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); + // Make sure the file can't be write. + EXPECT_EQ(-1, + file_util::WriteFile(file_name, kData.data(), kData.length())); + EXPECT_FALSE(file_util::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); + // 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(file_util::PathIsWritable(file_name)); + + // Delete the file. + EXPECT_TRUE(file_util::Delete(file_name, false)); + EXPECT_FALSE(file_util::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(file_util::PathExists(subdir_path)); + + // Create a dummy file to enumerate. + FilePath file_name = subdir_path.Append(FPL("Test Readable File.txt")); + EXPECT_FALSE(file_util::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(file_util::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); + + // 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); + + // Make sure the file in the directory can't be enumerated. + file_util::FileEnumerator f1(subdir_path, true, + file_util::FileEnumerator::FILES); + EXPECT_TRUE(file_util::PathExists(subdir_path)); + FindResultCollector c1(f1); + EXPECT_EQ(c1.size(), 0); + EXPECT_FALSE(file_util::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); + + // Make sure the file in the directory can be enumerated. + file_util::FileEnumerator f2(subdir_path, true, + file_util::FileEnumerator::FILES); + FindResultCollector c2(f2); + EXPECT_TRUE(c2.HasFile(file_name)); + EXPECT_EQ(c2.size(), 1); + + // Delete the file. + EXPECT_TRUE(file_util::Delete(subdir_path, true)); + EXPECT_FALSE(file_util::PathExists(subdir_path)); +} + +#endif // defined(OS_POSIX) + +#if defined(OS_WIN) +// Tests that the Delete function works for wild cards, especially +// with the recursion flag. Also coincidentally tests PathExists. +// TODO(erikkay): see if anyone's actually using this feature of the API +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(file_util::PathExists(file_name)); + + FilePath subdir_path = temp_dir_.path().Append(FPL("DeleteWildCardDir")); + file_util::CreateDirectory(subdir_path); + ASSERT_TRUE(file_util::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(file_util::Delete(directory_contents, false)); + EXPECT_FALSE(file_util::PathExists(file_name)); + EXPECT_TRUE(file_util::PathExists(subdir_path)); + + // Delete recursively and make sure all contents are deleted + EXPECT_TRUE(file_util::Delete(directory_contents, true)); + EXPECT_FALSE(file_util::PathExists(file_name)); + EXPECT_FALSE(file_util::PathExists(subdir_path)); +} + +// TODO(erikkay): see if anyone's actually using this feature of the API +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(file_util::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(file_util::Delete(directory_contents, false)); + EXPECT_TRUE(file_util::PathExists(subdir_path)); + + // Delete recursively and check nothing got deleted + EXPECT_TRUE(file_util::Delete(directory_contents, true)); + EXPECT_TRUE(file_util::PathExists(subdir_path)); +} +#endif + +// Tests non-recursive Delete() for a directory. +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(file_util::PathExists(test_subdir)); + + FilePath file_name = test_subdir.Append(FPL("Test DeleteDir.txt")); + CreateTextFile(file_name, bogus_content); + ASSERT_TRUE(file_util::PathExists(file_name)); + + FilePath subdir_path1 = test_subdir.Append(FPL("TestSubDir1")); + file_util::CreateDirectory(subdir_path1); + ASSERT_TRUE(file_util::PathExists(subdir_path1)); + + FilePath subdir_path2 = test_subdir.Append(FPL("TestSubDir2")); + file_util::CreateDirectory(subdir_path2); + ASSERT_TRUE(file_util::PathExists(subdir_path2)); + + // Delete non-recursively and check that the empty dir got deleted + EXPECT_TRUE(file_util::Delete(subdir_path2, false)); + EXPECT_FALSE(file_util::PathExists(subdir_path2)); + + // Delete non-recursively and check that nothing got deleted + EXPECT_FALSE(file_util::Delete(test_subdir, false)); + EXPECT_TRUE(file_util::PathExists(test_subdir)); + EXPECT_TRUE(file_util::PathExists(file_name)); + EXPECT_TRUE(file_util::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(file_util::PathExists(test_subdir)); + + FilePath file_name = test_subdir.Append(FPL("Test DeleteDirRecursive.txt")); + CreateTextFile(file_name, bogus_content); + ASSERT_TRUE(file_util::PathExists(file_name)); + + FilePath subdir_path1 = test_subdir.Append(FPL("TestSubDir1")); + file_util::CreateDirectory(subdir_path1); + ASSERT_TRUE(file_util::PathExists(subdir_path1)); + + FilePath subdir_path2 = test_subdir.Append(FPL("TestSubDir2")); + file_util::CreateDirectory(subdir_path2); + ASSERT_TRUE(file_util::PathExists(subdir_path2)); + + // Delete recursively and check that the empty dir got deleted + EXPECT_TRUE(file_util::Delete(subdir_path2, true)); + EXPECT_FALSE(file_util::PathExists(subdir_path2)); + + // Delete recursively and check that everything got deleted + EXPECT_TRUE(file_util::Delete(test_subdir, true)); + EXPECT_FALSE(file_util::PathExists(file_name)); + EXPECT_FALSE(file_util::PathExists(subdir_path1)); + EXPECT_FALSE(file_util::PathExists(test_subdir)); +} + +#if !defined(OS_STARBOARD) +TEST_F(FileUtilTest, MoveFileNew) { + // Create a file + FilePath file_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Move_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(file_util::PathExists(file_name_from)); + + // The destination. + FilePath file_name_to = temp_dir_.path().Append( + FILE_PATH_LITERAL("Move_Test_File_Destination.txt")); + ASSERT_FALSE(file_util::PathExists(file_name_to)); + + EXPECT_TRUE(file_util::Move(file_name_from, file_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(file_util::PathExists(file_name_from)); + EXPECT_TRUE(file_util::PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, MoveFileExists) { + // Create a file + FilePath file_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Move_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(file_util::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(file_util::PathExists(file_name_to)); + + EXPECT_TRUE(file_util::Move(file_name_from, file_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(file_util::PathExists(file_name_from)); + EXPECT_TRUE(file_util::PathExists(file_name_to)); + EXPECT_TRUE(L"Gooooooooooooooooooooogle" == ReadTextFile(file_name_to)); +} + +TEST_F(FileUtilTest, MoveFileDirExists) { + // Create a file + FilePath file_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Move_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(file_util::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(file_util::PathExists(dir_name_to)); + + EXPECT_FALSE(file_util::Move(file_name_from, dir_name_to)); +} + + +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(file_util::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(file_util::PathExists(file_name_from)); + + // Move the directory. + FilePath dir_name_to = + temp_dir_.path().Append(FILE_PATH_LITERAL("Move_To_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Move_Test_File.txt")); + + ASSERT_FALSE(file_util::PathExists(dir_name_to)); + + EXPECT_TRUE(file_util::Move(dir_name_from, dir_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(file_util::PathExists(dir_name_from)); + EXPECT_FALSE(file_util::PathExists(file_name_from)); + EXPECT_TRUE(file_util::PathExists(dir_name_to)); + EXPECT_TRUE(file_util::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(file_util::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(file_util::PathExists(file_name_from)); + + // Move the directory + FilePath dir_name_exists = + temp_dir_.path().Append(FILE_PATH_LITERAL("Destination")); + + FilePath dir_name_to = + dir_name_exists.Append(FILE_PATH_LITERAL("Move_To_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Move_Test_File.txt")); + + // Create the destination directory. + file_util::CreateDirectory(dir_name_exists); + ASSERT_TRUE(file_util::PathExists(dir_name_exists)); + + EXPECT_TRUE(file_util::Move(dir_name_from, dir_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(file_util::PathExists(dir_name_from)); + EXPECT_FALSE(file_util::PathExists(file_name_from)); + EXPECT_TRUE(file_util::PathExists(dir_name_to)); + EXPECT_TRUE(file_util::PathExists(file_name_to)); +} +#endif + +#if !defined(OS_STARBOARD) +TEST_F(FileUtilTest, MAYBE_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(file_util::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(file_util::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(file_util::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(file_util::PathExists(file_name2_from)); + + // Copy the directory recursively. + FilePath dir_name_to = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_To_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + FilePath subdir_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Subdir")); + FilePath file_name2_to = + subdir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + + ASSERT_FALSE(file_util::PathExists(dir_name_to)); + + EXPECT_TRUE(file_util::CopyDirectory(dir_name_from, dir_name_to, true)); + + // Check everything has been copied. + EXPECT_TRUE(file_util::PathExists(dir_name_from)); + EXPECT_TRUE(file_util::PathExists(file_name_from)); + EXPECT_TRUE(file_util::PathExists(subdir_name_from)); + EXPECT_TRUE(file_util::PathExists(file_name2_from)); + EXPECT_TRUE(file_util::PathExists(dir_name_to)); + EXPECT_TRUE(file_util::PathExists(file_name_to)); + EXPECT_TRUE(file_util::PathExists(subdir_name_to)); + EXPECT_TRUE(file_util::PathExists(file_name2_to)); +} + +TEST_F(FileUtilTest, MAYBE_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(file_util::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(file_util::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(file_util::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(file_util::PathExists(file_name2_from)); + + // Copy the directory recursively. + FilePath dir_name_exists = + temp_dir_.path().Append(FILE_PATH_LITERAL("Destination")); + + FilePath dir_name_to = + dir_name_exists.Append(FILE_PATH_LITERAL("Copy_From_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + FilePath subdir_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Subdir")); + FilePath file_name2_to = + subdir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + + // Create the destination directory. + file_util::CreateDirectory(dir_name_exists); + ASSERT_TRUE(file_util::PathExists(dir_name_exists)); + + EXPECT_TRUE(file_util::CopyDirectory(dir_name_from, dir_name_exists, true)); + + // Check everything has been copied. + EXPECT_TRUE(file_util::PathExists(dir_name_from)); + EXPECT_TRUE(file_util::PathExists(file_name_from)); + EXPECT_TRUE(file_util::PathExists(subdir_name_from)); + EXPECT_TRUE(file_util::PathExists(file_name2_from)); + EXPECT_TRUE(file_util::PathExists(dir_name_to)); + EXPECT_TRUE(file_util::PathExists(file_name_to)); + EXPECT_TRUE(file_util::PathExists(subdir_name_to)); + EXPECT_TRUE(file_util::PathExists(file_name2_to)); +} + +TEST_F(FileUtilTest, MAYBE_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(file_util::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(file_util::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(file_util::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(file_util::PathExists(file_name2_from)); + + // Copy the directory not recursively. + FilePath dir_name_to = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_To_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + FilePath subdir_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Subdir")); + + ASSERT_FALSE(file_util::PathExists(dir_name_to)); + + EXPECT_TRUE(file_util::CopyDirectory(dir_name_from, dir_name_to, false)); + + // Check everything has been copied. + EXPECT_TRUE(file_util::PathExists(dir_name_from)); + EXPECT_TRUE(file_util::PathExists(file_name_from)); + EXPECT_TRUE(file_util::PathExists(subdir_name_from)); + EXPECT_TRUE(file_util::PathExists(file_name2_from)); + EXPECT_TRUE(file_util::PathExists(dir_name_to)); + EXPECT_TRUE(file_util::PathExists(file_name_to)); + EXPECT_FALSE(file_util::PathExists(subdir_name_to)); +} + +TEST_F(FileUtilTest, MAYBE_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(file_util::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(file_util::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(file_util::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(file_util::PathExists(file_name2_from)); + + // Copy the directory not recursively. + FilePath dir_name_to = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_To_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + FilePath subdir_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Subdir")); + + // Create the destination directory. + file_util::CreateDirectory(dir_name_to); + ASSERT_TRUE(file_util::PathExists(dir_name_to)); + + EXPECT_TRUE(file_util::CopyDirectory(dir_name_from, dir_name_to, false)); + + // Check everything has been copied. + EXPECT_TRUE(file_util::PathExists(dir_name_from)); + EXPECT_TRUE(file_util::PathExists(file_name_from)); + EXPECT_TRUE(file_util::PathExists(subdir_name_from)); + EXPECT_TRUE(file_util::PathExists(file_name2_from)); + EXPECT_TRUE(file_util::PathExists(dir_name_to)); + EXPECT_TRUE(file_util::PathExists(file_name_to)); + EXPECT_FALSE(file_util::PathExists(subdir_name_to)); +} + +TEST_F(FileUtilTest, MAYBE_CopyFileWithCopyDirectoryRecursiveToNew) { + // Create a file + FilePath file_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(file_util::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(file_util::PathExists(file_name_to)); + + EXPECT_TRUE(file_util::CopyDirectory(file_name_from, file_name_to, true)); + + // Check the has been copied + EXPECT_TRUE(file_util::PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, MAYBE_CopyFileWithCopyDirectoryRecursiveToExisting) { + // Create a file + FilePath file_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(file_util::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(file_util::PathExists(file_name_to)); + + EXPECT_TRUE(file_util::CopyDirectory(file_name_from, file_name_to, true)); + + // Check the has been copied + EXPECT_TRUE(file_util::PathExists(file_name_to)); + EXPECT_TRUE(L"Gooooooooooooooooooooogle" == ReadTextFile(file_name_to)); +} + +TEST_F(FileUtilTest, + MAYBE_CopyFileWithCopyDirectoryRecursiveToExistingDirectory) { + // Create a file + FilePath file_name_from = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); + ASSERT_TRUE(file_util::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(file_util::PathExists(dir_name_to)); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + + EXPECT_TRUE(file_util::CopyDirectory(file_name_from, dir_name_to, true)); + + // Check the has been copied + EXPECT_TRUE(file_util::PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, MAYBE_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(file_util::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(file_util::PathExists(file_name_from)); + + // Copy the directory recursively. + FilePath dir_name_to = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_To_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); + + // Create from path with trailing separators. +#if defined(OS_WIN) + FilePath from_path = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir\\\\\\")); +#elif defined (OS_POSIX) + FilePath from_path = + temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir///")); +#endif + + EXPECT_TRUE(file_util::CopyDirectory(from_path, dir_name_to, true)); + + // Check everything has been copied. + EXPECT_TRUE(file_util::PathExists(dir_name_from)); + EXPECT_TRUE(file_util::PathExists(file_name_from)); + EXPECT_TRUE(file_util::PathExists(dir_name_to)); + EXPECT_TRUE(file_util::PathExists(file_name_to)); +} +#endif // defined(OS_STARBOARD) + +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(file_util::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(file_util::PathExists(file_name_from)); + + // Copy the file. + FilePath dest_file = dir_name_from.Append(FILE_PATH_LITERAL("DestFile.txt")); + ASSERT_TRUE(file_util::CopyFile(file_name_from, dest_file)); + +#if defined(OS_STARBOARD) + // Why should we ensure CopyFile is unsafe? On Starboard, it won't open files + // with a ".." in the path. + // Copy the file to another location using '..' in the path. + FilePath dest_file2(temp_dir_.path()); + dest_file2 = dest_file2.AppendASCII("DestFile.txt"); + ASSERT_TRUE(file_util::CopyFile(file_name_from, dest_file2)); +#else + // 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_TRUE(file_util::CopyFile(file_name_from, dest_file2)); +#endif + + 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(file_util::PathExists(file_name_from)); + EXPECT_TRUE(file_util::PathExists(dest_file)); + const std::wstring read_contents = ReadTextFile(dest_file); + EXPECT_EQ(file_contents, read_contents); + EXPECT_TRUE(file_util::PathExists(dest_file2_test)); + EXPECT_TRUE(file_util::PathExists(dest_file2)); +} + +// TODO(erikkay): implement +#if defined(OS_WIN) +TEST_F(FileUtilTest, GetFileCreationLocalTime) { + FilePath file_name = temp_dir_.path().Append(L"Test File.txt"); + + SYSTEMTIME start_time; + GetLocalTime(&start_time); + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); + CreateTextFile(file_name, L"New file!"); + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); + SYSTEMTIME end_time; + GetLocalTime(&end_time); + + SYSTEMTIME file_creation_time; + file_util::GetFileCreationLocalTime(file_name.value(), &file_creation_time); + + FILETIME start_filetime; + SystemTimeToFileTime(&start_time, &start_filetime); + FILETIME end_filetime; + SystemTimeToFileTime(&end_time, &end_filetime); + FILETIME file_creation_filetime; + SystemTimeToFileTime(&file_creation_time, &file_creation_filetime); + + EXPECT_EQ(-1, CompareFileTime(&start_filetime, &file_creation_filetime)) << + "start time: " << FileTimeAsUint64(start_filetime) << ", " << + "creation time: " << FileTimeAsUint64(file_creation_filetime); + + EXPECT_EQ(-1, CompareFileTime(&file_creation_filetime, &end_filetime)) << + "creation time: " << FileTimeAsUint64(file_creation_filetime) << ", " << + "end time: " << FileTimeAsUint64(end_filetime); + + ASSERT_TRUE(DeleteFile(file_name.value().c_str())); +} +#endif + +// file_util winds up using autoreleased objects on the Mac, so this needs +// to be a PlatformTest. +typedef PlatformTest ReadOnlyFileUtilTest; + +#if !defined(__LB_WIIU__) && !defined(OS_STARBOARD) +TEST_F(ReadOnlyFileUtilTest, ContentsEqual) { + FilePath data_dir; + ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &data_dir)); + data_dir = data_dir.Append(FILE_PATH_LITERAL("base")) + .Append(FILE_PATH_LITERAL("data")) + .Append(FILE_PATH_LITERAL("file_util_unittest")); + ASSERT_TRUE(file_util::PathExists(data_dir)); + + FilePath original_file = + data_dir.Append(FILE_PATH_LITERAL("original.txt")); + FilePath same_file = + data_dir.Append(FILE_PATH_LITERAL("same.txt")); + FilePath same_length_file = + data_dir.Append(FILE_PATH_LITERAL("same_length.txt")); + FilePath different_file = + data_dir.Append(FILE_PATH_LITERAL("different.txt")); + FilePath different_first_file = + data_dir.Append(FILE_PATH_LITERAL("different_first.txt")); + FilePath different_last_file = + data_dir.Append(FILE_PATH_LITERAL("different_last.txt")); + FilePath empty1_file = + data_dir.Append(FILE_PATH_LITERAL("empty1.txt")); + FilePath empty2_file = + data_dir.Append(FILE_PATH_LITERAL("empty2.txt")); + FilePath shortened_file = + data_dir.Append(FILE_PATH_LITERAL("shortened.txt")); + FilePath binary_file = + data_dir.Append(FILE_PATH_LITERAL("binary_file.bin")); + FilePath binary_file_same = + data_dir.Append(FILE_PATH_LITERAL("binary_file_same.bin")); + FilePath binary_file_diff = + data_dir.Append(FILE_PATH_LITERAL("binary_file_diff.bin")); + + EXPECT_TRUE(file_util::ContentsEqual(original_file, original_file)); + EXPECT_TRUE(file_util::ContentsEqual(original_file, same_file)); + EXPECT_FALSE(file_util::ContentsEqual(original_file, same_length_file)); + EXPECT_FALSE(file_util::ContentsEqual(original_file, different_file)); + EXPECT_FALSE(file_util::ContentsEqual( + FilePath(FILE_PATH_LITERAL("bogusname")), + FilePath(FILE_PATH_LITERAL("bogusname")))); + EXPECT_FALSE(file_util::ContentsEqual(original_file, different_first_file)); + EXPECT_FALSE(file_util::ContentsEqual(original_file, different_last_file)); + EXPECT_TRUE(file_util::ContentsEqual(empty1_file, empty2_file)); + EXPECT_FALSE(file_util::ContentsEqual(original_file, shortened_file)); + EXPECT_FALSE(file_util::ContentsEqual(shortened_file, original_file)); + EXPECT_TRUE(file_util::ContentsEqual(binary_file, binary_file_same)); + EXPECT_FALSE(file_util::ContentsEqual(binary_file, binary_file_diff)); +} +#endif + +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) +TEST_F(ReadOnlyFileUtilTest, TextContentsEqual) { + FilePath data_dir; + ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &data_dir)); + data_dir = data_dir.Append(FILE_PATH_LITERAL("base")) + .Append(FILE_PATH_LITERAL("data")) + .Append(FILE_PATH_LITERAL("file_util_unittest")); + ASSERT_TRUE(file_util::PathExists(data_dir)); + + FilePath original_file = + data_dir.Append(FILE_PATH_LITERAL("original.txt")); + FilePath same_file = + data_dir.Append(FILE_PATH_LITERAL("same.txt")); + FilePath crlf_file = + data_dir.Append(FILE_PATH_LITERAL("crlf.txt")); + FilePath shortened_file = + data_dir.Append(FILE_PATH_LITERAL("shortened.txt")); + FilePath different_file = + data_dir.Append(FILE_PATH_LITERAL("different.txt")); + FilePath different_first_file = + data_dir.Append(FILE_PATH_LITERAL("different_first.txt")); + FilePath different_last_file = + data_dir.Append(FILE_PATH_LITERAL("different_last.txt")); + FilePath first1_file = + data_dir.Append(FILE_PATH_LITERAL("first1.txt")); + FilePath first2_file = + data_dir.Append(FILE_PATH_LITERAL("first2.txt")); + FilePath empty1_file = + data_dir.Append(FILE_PATH_LITERAL("empty1.txt")); + FilePath empty2_file = + data_dir.Append(FILE_PATH_LITERAL("empty2.txt")); + FilePath blank_line_file = + data_dir.Append(FILE_PATH_LITERAL("blank_line.txt")); + FilePath blank_line_crlf_file = + data_dir.Append(FILE_PATH_LITERAL("blank_line_crlf.txt")); + + EXPECT_TRUE(file_util::TextContentsEqual(original_file, same_file)); + EXPECT_TRUE(file_util::TextContentsEqual(original_file, crlf_file)); + EXPECT_FALSE(file_util::TextContentsEqual(original_file, shortened_file)); + EXPECT_FALSE(file_util::TextContentsEqual(original_file, different_file)); + EXPECT_FALSE(file_util::TextContentsEqual(original_file, + different_first_file)); + EXPECT_FALSE(file_util::TextContentsEqual(original_file, + different_last_file)); + EXPECT_FALSE(file_util::TextContentsEqual(first1_file, first2_file)); + EXPECT_TRUE(file_util::TextContentsEqual(empty1_file, empty2_file)); + EXPECT_FALSE(file_util::TextContentsEqual(original_file, empty1_file)); + EXPECT_TRUE(file_util::TextContentsEqual(blank_line_file, + blank_line_crlf_file)); +} +#endif // __LB_SHELL__ + +// We don't need equivalent functionality outside of Windows. +#if defined(OS_WIN) +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(file_util::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(file_util::PathExists(file_name_from)); + + // Move the directory by using CopyAndDeleteDirectory + FilePath dir_name_to = temp_dir_.path().Append( + FILE_PATH_LITERAL("CopyAndDelete_To_Subdir")); + FilePath file_name_to = + dir_name_to.Append(FILE_PATH_LITERAL("CopyAndDelete_Test_File.txt")); + + ASSERT_FALSE(file_util::PathExists(dir_name_to)); + + EXPECT_TRUE(file_util::CopyAndDeleteDirectory(dir_name_from, dir_name_to)); + + // Check everything has been moved. + EXPECT_FALSE(file_util::PathExists(dir_name_from)); + EXPECT_FALSE(file_util::PathExists(file_name_from)); + EXPECT_TRUE(file_util::PathExists(dir_name_to)); + EXPECT_TRUE(file_util::PathExists(file_name_to)); +} + +TEST_F(FileUtilTest, GetTempDirTest) { + static const TCHAR* kTmpKey = _T("TMP"); + static const TCHAR* kTmpValues[] = { + _T(""), _T("C:"), _T("C:\\"), _T("C:\\tmp"), _T("C:\\tmp\\") + }; + // Save the original $TMP. + size_t original_tmp_size; + TCHAR* original_tmp; + ASSERT_EQ(0, ::_tdupenv_s(&original_tmp, &original_tmp_size, kTmpKey)); + // original_tmp may be NULL. + + for (unsigned int i = 0; i < arraysize(kTmpValues); ++i) { + FilePath path; + ::_tputenv_s(kTmpKey, kTmpValues[i]); + file_util::GetTempDir(&path); + EXPECT_TRUE(path.IsAbsolute()) << "$TMP=" << kTmpValues[i] << + " result=" << path.value(); + } + + // Restore the original $TMP. + if (original_tmp) { + ::_tputenv_s(kTmpKey, original_tmp); + free(original_tmp); + } else { + ::_tputenv_s(kTmpKey, _T("")); + } +} +#endif // OS_WIN + +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(file_util::PathExists(temp_files[i])); + EXPECT_FALSE(file_util::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(file_util::Delete(temp_files[i], false)); +} + +#if !defined(OS_STARBOARD) +TEST_F(FileUtilTest, CreateAndOpenTemporaryFileTest) { + FilePath names[3]; + FILE* fps[3]; + int i; + + // Create; make sure they are open and exist. + for (i = 0; i < 3; ++i) { + fps[i] = file_util::CreateAndOpenTemporaryFile(&(names[i])); + ASSERT_TRUE(fps[i]); + EXPECT_TRUE(file_util::PathExists(names[i])); + } + + // Make sure all names are unique. + for (i = 0; i < 3; ++i) { + EXPECT_FALSE(names[i] == names[(i+1)%3]); + } + + // Close and delete. + for (i = 0; i < 3; ++i) { + EXPECT_TRUE(file_util::CloseFile(fps[i])); + EXPECT_TRUE(file_util::Delete(names[i], false)); + } +} +#endif + +TEST_F(FileUtilTest, CreateNewTempDirectoryTest) { + FilePath temp_dir; + ASSERT_TRUE(file_util::CreateNewTempDirectory(FilePath::StringType(), + &temp_dir)); + EXPECT_TRUE(file_util::PathExists(temp_dir)); + EXPECT_TRUE(file_util::Delete(temp_dir, false)); +} + +TEST_F(FileUtilTest, CreateNewTemporaryDirInDirTest) { + FilePath new_dir; + ASSERT_TRUE(file_util::CreateTemporaryDirInDir( + temp_dir_.path(), + FILE_PATH_LITERAL("CreateNewTemporaryDirInDirTest"), + &new_dir)); + EXPECT_TRUE(file_util::PathExists(new_dir)); + EXPECT_TRUE(temp_dir_.path().IsParent(new_dir)); + EXPECT_TRUE(file_util::Delete(new_dir, false)); +} + +#if !defined(__LB_SHELL__) +TEST_F(FileUtilTest, GetShmemTempDirTest) { + FilePath dir; + EXPECT_TRUE(file_util::GetShmemTempDir(&dir, false)); + EXPECT_TRUE(file_util::DirectoryExists(dir)); +} +#endif + +TEST_F(FileUtilTest, CreateDirectoryTest) { + FilePath test_root = + temp_dir_.path().Append(FILE_PATH_LITERAL("create_directory_test")); +#if defined(OS_WIN) + FilePath test_path = + test_root.Append(FILE_PATH_LITERAL("dir\\tree\\likely\\doesnt\\exist\\")); +#elif defined(OS_POSIX) + FilePath test_path = + test_root.Append(FILE_PATH_LITERAL("dir/tree/likely/doesnt/exist/")); +#elif defined(OS_STARBOARD) + // Directory depth is limited on certain platforms. + FilePath test_path = + test_root.Append(FILE_PATH_LITERAL("tree/likely/doesnt/exist/")); +#endif + + EXPECT_FALSE(file_util::PathExists(test_path)); + EXPECT_TRUE(file_util::CreateDirectory(test_path)); + EXPECT_TRUE(file_util::PathExists(test_path)); + // CreateDirectory returns true if the DirectoryExists returns true. + EXPECT_TRUE(file_util::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(file_util::PathExists(test_path)); + CreateTextFile(test_path, L"test file"); + EXPECT_TRUE(file_util::PathExists(test_path)); + EXPECT_FALSE(file_util::CreateDirectory(test_path)); + + EXPECT_TRUE(file_util::Delete(test_root, true)); + EXPECT_FALSE(file_util::PathExists(test_root)); + EXPECT_FALSE(file_util::PathExists(test_path)); + +#if !defined(__LB_PS3__) && !defined(OS_STARBOARD) + // Starboard doesn't support relative paths. + + // Verify assumptions made by the Windows implementation: + // 1. The current directory always exists. + // 2. The root directory always exists. + ASSERT_TRUE(file_util::DirectoryExists( + FilePath(FilePath::kCurrentDirectory))); +#endif + + FilePath top_level = test_root; + while (top_level != top_level.DirName()) { + top_level = top_level.DirName(); + } + ASSERT_TRUE(file_util::DirectoryExists(top_level)); + + // Given these assumptions hold, it should be safe to + // test that "creating" these directories succeeds. +#if !defined(OS_STARBOARD) + EXPECT_TRUE(file_util::CreateDirectory( + FilePath(FilePath::kCurrentDirectory))); +#endif + + EXPECT_TRUE(file_util::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 (!file_util::PathExists(invalid_drive)) { + EXPECT_FALSE(file_util::CreateDirectory(invalid_path)); + } +#endif +} + +TEST_F(FileUtilTest, DetectDirectoryTest) { + // Check a directory + FilePath test_root = + temp_dir_.path().Append(FILE_PATH_LITERAL("detect_directory_test")); + EXPECT_FALSE(file_util::PathExists(test_root)); + EXPECT_TRUE(file_util::CreateDirectory(test_root)); + EXPECT_TRUE(file_util::PathExists(test_root)); + EXPECT_TRUE(file_util::DirectoryExists(test_root)); + // Check a file + FilePath test_path = + test_root.Append(FILE_PATH_LITERAL("foobar.txt")); + EXPECT_FALSE(file_util::PathExists(test_path)); + CreateTextFile(test_path, L"test file"); + EXPECT_TRUE(file_util::PathExists(test_path)); + EXPECT_FALSE(file_util::DirectoryExists(test_path)); + EXPECT_TRUE(file_util::Delete(test_path, false)); + + EXPECT_TRUE(file_util::Delete(test_root, true)); +} + +TEST_F(FileUtilTest, FileEnumeratorTest) { + // Test an empty directory. + file_util::FileEnumerator f0(temp_dir_.path(), true, FILES_AND_DIRECTORIES); + EXPECT_EQ(f0.Next().value(), FILE_PATH_LITERAL("")); + EXPECT_EQ(f0.Next().value(), FILE_PATH_LITERAL("")); + + // Test an empty directory, non-recursively, including "..". +#if !defined(OS_STARBOARD) + // Starboard doesn't support relative paths. + file_util::FileEnumerator f0_dotdot(temp_dir_.path(), false, + FILES_AND_DIRECTORIES | file_util::FileEnumerator::INCLUDE_DOT_DOT); + EXPECT_EQ(temp_dir_.path().Append(FILE_PATH_LITERAL("..")).value(), + f0_dotdot.Next().value()); + EXPECT_EQ(FILE_PATH_LITERAL(""), + f0_dotdot.Next().value()); +#endif + + // create the directories + FilePath dir1 = temp_dir_.path().Append(FILE_PATH_LITERAL("dir1")); + EXPECT_TRUE(file_util::CreateDirectory(dir1)); + FilePath dir2 = temp_dir_.path().Append(FILE_PATH_LITERAL("dir2")); + EXPECT_TRUE(file_util::CreateDirectory(dir2)); + FilePath dir2inner = dir2.Append(FILE_PATH_LITERAL("inner")); + EXPECT_TRUE(file_util::CreateDirectory(dir2inner)); + + // create the files + FilePath dir2file = dir2.Append(FILE_PATH_LITERAL("dir2file.txt")); + CreateTextFile(dir2file, L""); + FilePath dir2innerfile = dir2inner.Append(FILE_PATH_LITERAL("innerfile.txt")); + CreateTextFile(dir2innerfile, L""); + FilePath file1 = temp_dir_.path().Append(FILE_PATH_LITERAL("file1.txt")); + CreateTextFile(file1, L""); + FilePath file2_rel = + dir2.Append(FilePath::kParentDirectory) + .Append(FILE_PATH_LITERAL("file2.txt")); + CreateTextFile(file2_rel, L""); + FilePath file2_abs = temp_dir_.path().Append(FILE_PATH_LITERAL("file2.txt")); + + // Only enumerate files. + file_util::FileEnumerator f1(temp_dir_.path(), true, + file_util::FileEnumerator::FILES); + FindResultCollector c1(f1); + EXPECT_TRUE(c1.HasFile(file1)); + EXPECT_TRUE(c1.HasFile(file2_abs)); + EXPECT_TRUE(c1.HasFile(dir2file)); + EXPECT_TRUE(c1.HasFile(dir2innerfile)); + EXPECT_EQ(c1.size(), 4); + + // Only enumerate directories. + file_util::FileEnumerator f2(temp_dir_.path(), true, + file_util::FileEnumerator::DIRECTORIES); + FindResultCollector c2(f2); + EXPECT_TRUE(c2.HasFile(dir1)); + EXPECT_TRUE(c2.HasFile(dir2)); + EXPECT_TRUE(c2.HasFile(dir2inner)); + EXPECT_EQ(c2.size(), 3); + + // Only enumerate directories non-recursively. + file_util::FileEnumerator f2_non_recursive( + temp_dir_.path(), false, file_util::FileEnumerator::DIRECTORIES); + FindResultCollector c2_non_recursive(f2_non_recursive); + EXPECT_TRUE(c2_non_recursive.HasFile(dir1)); + EXPECT_TRUE(c2_non_recursive.HasFile(dir2)); + EXPECT_EQ(c2_non_recursive.size(), 2); + +#if !defined(__LB_XB1__) && !defined(OS_STARBOARD) +// As all file access is absolute, this is not an issue + // Only enumerate directories, non-recursively, including "..". + file_util::FileEnumerator f2_dotdot(temp_dir_.path(), false, + file_util::FileEnumerator::DIRECTORIES | + file_util::FileEnumerator::INCLUDE_DOT_DOT); + FindResultCollector c2_dotdot(f2_dotdot); + EXPECT_TRUE(c2_dotdot.HasFile(dir1)); + EXPECT_TRUE(c2_dotdot.HasFile(dir2)); + EXPECT_TRUE(c2_dotdot.HasFile( + temp_dir_.path().Append(FILE_PATH_LITERAL("..")))); + EXPECT_EQ(c2_dotdot.size(), 3); +#endif + + // Enumerate files and directories. + file_util::FileEnumerator f3(temp_dir_.path(), true, FILES_AND_DIRECTORIES); + FindResultCollector c3(f3); + EXPECT_TRUE(c3.HasFile(dir1)); + EXPECT_TRUE(c3.HasFile(dir2)); + EXPECT_TRUE(c3.HasFile(file1)); + EXPECT_TRUE(c3.HasFile(file2_abs)); + EXPECT_TRUE(c3.HasFile(dir2file)); + EXPECT_TRUE(c3.HasFile(dir2inner)); + EXPECT_TRUE(c3.HasFile(dir2innerfile)); + EXPECT_EQ(c3.size(), 7); + + // Non-recursive operation. + file_util::FileEnumerator f4(temp_dir_.path(), false, FILES_AND_DIRECTORIES); + FindResultCollector c4(f4); + EXPECT_TRUE(c4.HasFile(dir2)); + EXPECT_TRUE(c4.HasFile(dir2)); + EXPECT_TRUE(c4.HasFile(file1)); + EXPECT_TRUE(c4.HasFile(file2_abs)); + EXPECT_EQ(c4.size(), 4); + +#if !defined(OS_STARBOARD) + // Enumerate with a pattern. + file_util::FileEnumerator f5(temp_dir_.path(), true, FILES_AND_DIRECTORIES, + FILE_PATH_LITERAL("dir*")); + FindResultCollector c5(f5); + EXPECT_TRUE(c5.HasFile(dir1)); + EXPECT_TRUE(c5.HasFile(dir2)); + EXPECT_TRUE(c5.HasFile(dir2file)); + EXPECT_TRUE(c5.HasFile(dir2inner)); + EXPECT_TRUE(c5.HasFile(dir2innerfile)); + EXPECT_EQ(c5.size(), 5); +#endif + + // Make sure the destructor closes the find handle while in the middle of a + // query to allow TearDown to delete the directory. + file_util::FileEnumerator f6(temp_dir_.path(), true, FILES_AND_DIRECTORIES); + EXPECT_FALSE(f6.Next().value().empty()); // Should have found something + // (we don't care what). +} + +TEST_F(FileUtilTest, AppendToFile) { + FilePath data_dir = + temp_dir_.path().Append(FILE_PATH_LITERAL("FilePathTest")); + + // Create a fresh, empty copy of this directory. + if (file_util::PathExists(data_dir)) { + ASSERT_TRUE(file_util::Delete(data_dir, true)); + } + ASSERT_TRUE(file_util::CreateDirectory(data_dir)); + + // Create a fresh, empty copy of this directory. + if (file_util::PathExists(data_dir)) { + ASSERT_TRUE(file_util::Delete(data_dir, true)); + } + ASSERT_TRUE(file_util::CreateDirectory(data_dir)); + FilePath foobar(data_dir.Append(FILE_PATH_LITERAL("foobar.txt"))); + + std::string data("hello"); + EXPECT_EQ(-1, file_util::AppendToFile(foobar, data.c_str(), data.length())); + EXPECT_EQ(static_cast<int>(data.length()), + file_util::WriteFile(foobar, data.c_str(), data.length())); + EXPECT_EQ(static_cast<int>(data.length()), + file_util::AppendToFile(foobar, data.c_str(), data.length())); + + const std::wstring read_content = ReadTextFile(foobar); + EXPECT_EQ(L"hellohello", read_content); +} + +TEST_F(FileUtilTest, Contains) { + FilePath data_dir = + temp_dir_.path().Append(FILE_PATH_LITERAL("FilePathTest")); + + // Create a fresh, empty copy of this directory. + if (file_util::PathExists(data_dir)) { + ASSERT_TRUE(file_util::Delete(data_dir, true)); + } + ASSERT_TRUE(file_util::CreateDirectory(data_dir)); + + FilePath foo(data_dir.Append(FILE_PATH_LITERAL("foo"))); + FilePath bar(foo.Append(FILE_PATH_LITERAL("bar.txt"))); + FilePath baz(data_dir.Append(FILE_PATH_LITERAL("baz.txt"))); + FilePath foobar(data_dir.Append(FILE_PATH_LITERAL("foobar.txt"))); + + // Annoyingly, the directories must actually exist in order for realpath(), + // which Contains() relies on in posix, to work. + ASSERT_TRUE(file_util::CreateDirectory(foo)); + std::string data("hello"); + ASSERT_TRUE(file_util::WriteFile(bar, data.c_str(), data.length())); + ASSERT_TRUE(file_util::WriteFile(baz, data.c_str(), data.length())); + ASSERT_TRUE(file_util::WriteFile(foobar, data.c_str(), data.length())); + + EXPECT_TRUE(file_util::ContainsPath(foo, bar)); + EXPECT_FALSE(file_util::ContainsPath(foo, baz)); + EXPECT_FALSE(file_util::ContainsPath(foo, foobar)); + EXPECT_FALSE(file_util::ContainsPath(foo, foo)); + + // Platform-specific concerns. + FilePath foo_caps(data_dir.Append(FILE_PATH_LITERAL("FOO"))); +#if defined(OS_WIN) + EXPECT_TRUE(file_util::ContainsPath(foo, + foo_caps.Append(FILE_PATH_LITERAL("bar.txt")))); + EXPECT_TRUE(file_util::ContainsPath(foo, + FilePath(foo.value() + FILE_PATH_LITERAL("/bar.txt")))); +#elif defined(OS_MACOSX) + // We can't really do this test on OS X since the case-sensitivity of the + // filesystem is configurable. +#elif defined(OS_POSIX) + EXPECT_FALSE(file_util::ContainsPath(foo, + foo_caps.Append(FILE_PATH_LITERAL("bar.txt")))); +#endif +} + +#if !defined(OS_STARBOARD) +TEST_F(FileUtilTest, TouchFile) { + FilePath data_dir = + temp_dir_.path().Append(FILE_PATH_LITERAL("FilePathTest")); + + // Create a fresh, empty copy of this directory. + if (file_util::PathExists(data_dir)) { + ASSERT_TRUE(file_util::Delete(data_dir, true)); + } + ASSERT_TRUE(file_util::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; + // 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", + &access_time)); + + base::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", + &modification_time)); + +#if !defined(__LB_PS3__) + // file_util::TouchFile creates a platform file and passes its descriptor to + // base::TouchPlatformFile(). + // Unfortunately, PS3 only implements utime, not futime, which means it + // can only touch a file path, not a file descriptor. + + ASSERT_TRUE(file_util::TouchFile(foobar, access_time, modification_time)); + base::PlatformFileInfo file_info; + ASSERT_TRUE(file_util::GetFileInfo(foobar, &file_info)); + EXPECT_EQ(file_info.last_accessed.ToInternalValue(), + access_time.ToInternalValue()); + EXPECT_EQ(file_info.last_modified.ToInternalValue(), + modification_time.ToInternalValue()); +#endif +} +#endif + +TEST_F(FileUtilTest, IsDirectoryEmpty) { + FilePath empty_dir = temp_dir_.path().Append(FILE_PATH_LITERAL("EmptyDir")); + + ASSERT_FALSE(file_util::PathExists(empty_dir)); + + ASSERT_TRUE(file_util::CreateDirectory(empty_dir)); + + EXPECT_TRUE(file_util::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)); +} + +#if defined(OS_POSIX) && !defined(__LB_SHELL__) +// We don't support uids or symlinks in lbshell + +// Testing VerifyPathControlledByAdmin() is hard, because there is no +// way a test can make a file owned by root, or change file paths +// at the root of the file system. VerifyPathControlledByAdmin() +// is implemented as a call to VerifyPathControlledByUser, which gives +// us the ability to test with paths under the test's temp directory, +// using a user id we control. +// Pull tests of VerifyPathControlledByUserTest() into a separate test class +// with a common SetUp() method. +class VerifyPathControlledByUserTest : public FileUtilTest { + protected: + virtual void SetUp() OVERRIDE { + FileUtilTest::SetUp(); + + // Create a basic structure used by each test. + // base_dir_ + // |-> sub_dir_ + // |-> text_file_ + + base_dir_ = temp_dir_.path().AppendASCII("base_dir"); + ASSERT_TRUE(file_util::CreateDirectory(base_dir_)); + + sub_dir_ = base_dir_.AppendASCII("sub_dir"); + ASSERT_TRUE(file_util::CreateDirectory(sub_dir_)); + + text_file_ = sub_dir_.AppendASCII("file.txt"); + CreateTextFile(text_file_, L"This text file has some text in it."); + + // Get the user and group files are created with from |base_dir_|. + struct stat stat_buf; + ASSERT_EQ(0, stat(base_dir_.value().c_str(), &stat_buf)); + uid_ = stat_buf.st_uid; + ok_gids_.insert(stat_buf.st_gid); + bad_gids_.insert(stat_buf.st_gid + 1); + + ASSERT_EQ(uid_, getuid()); // This process should be the owner. + + // To ensure that umask settings do not cause the initial state + // of permissions to be different from what we expect, explicitly + // set permissions on the directories we create. + // Make all files and directories non-world-writable. + + // Users and group can read, write, traverse + int enabled_permissions = + file_util::FILE_PERMISSION_USER_MASK | + file_util::FILE_PERMISSION_GROUP_MASK; + // Other users can't read, write, traverse + int disabled_permissions = + file_util::FILE_PERMISSION_OTHERS_MASK; + + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions( + base_dir_, enabled_permissions, disabled_permissions)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions( + sub_dir_, enabled_permissions, disabled_permissions)); + } + + FilePath base_dir_; + FilePath sub_dir_; + FilePath text_file_; + uid_t uid_; + + std::set<gid_t> ok_gids_; + std::set<gid_t> bad_gids_; +}; + +TEST_F(VerifyPathControlledByUserTest, BadPaths) { + // File does not exist. + FilePath does_not_exist = base_dir_.AppendASCII("does") + .AppendASCII("not") + .AppendASCII("exist"); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, does_not_exist, uid_, ok_gids_)); + + // |base| not a subpath of |path|. + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + sub_dir_, base_dir_, uid_, ok_gids_)); + + // An empty base path will fail to be a prefix for any path. + FilePath empty; + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + empty, base_dir_, uid_, ok_gids_)); + + // Finding that a bad call fails proves nothing unless a good call succeeds. + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); +} + +#if !defined(__LB_SHELL__) +// We don't support uids or symlinks in lbshell + +TEST_F(VerifyPathControlledByUserTest, Symlinks) { + // Symlinks in the path should cause failure. + + // 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)) + << "Failed to create symlink."; + + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, file_link, uid_, ok_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + file_link, file_link, uid_, ok_gids_)); + + // 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)) + << "Failed to create symlink."; + + FilePath file_path_with_link = link_to_sub_dir.AppendASCII("file.txt"); + ASSERT_TRUE(file_util::PathExists(file_path_with_link)); + + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, file_path_with_link, uid_, ok_gids_)); + + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + link_to_sub_dir, file_path_with_link, uid_, ok_gids_)); + + // Symlinks in parents of base path are allowed. + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + file_path_with_link, file_path_with_link, uid_, ok_gids_)); +} + +TEST_F(VerifyPathControlledByUserTest, OwnershipChecks) { + // Get a uid that is not the uid of files we create. + uid_t bad_uid = uid_ + 1; + + // Make all files and directories non-world-writable. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(base_dir_, 0u, S_IWOTH)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(sub_dir_, 0u, S_IWOTH)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(text_file_, 0u, S_IWOTH)); + + // We control these paths. + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + // Another user does not control these paths. + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, bad_uid, ok_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, bad_uid, ok_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, bad_uid, ok_gids_)); + + // Another group does not control the paths. + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, bad_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, bad_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, bad_gids_)); +} + +TEST_F(VerifyPathControlledByUserTest, GroupWriteTest) { + // Make all files and directories writable only by their owner. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(base_dir_, 0u, S_IWOTH|S_IWGRP)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(sub_dir_, 0u, S_IWOTH|S_IWGRP)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(text_file_, 0u, S_IWOTH|S_IWGRP)); + + // Any group is okay because the path is not group-writable. + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, bad_gids_)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, bad_gids_)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, bad_gids_)); + + // No group is okay, because we don't check the group + // if no group can write. + std::set<gid_t> no_gids; // Empty set of gids. + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, no_gids)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, no_gids)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, no_gids)); + + + // Make all files and directories writable by their group. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(base_dir_, S_IWGRP, 0u)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(sub_dir_, S_IWGRP, 0u)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(text_file_, S_IWGRP, 0u)); + + // Now |ok_gids_| works, but |bad_gids_| fails. + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, bad_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, bad_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, bad_gids_)); + + // Because any group in the group set is allowed, + // the union of good and bad gids passes. + + std::set<gid_t> multiple_gids; + std::set_union( + ok_gids_.begin(), ok_gids_.end(), + bad_gids_.begin(), bad_gids_.end(), + std::inserter(multiple_gids, multiple_gids.begin())); + + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, multiple_gids)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, multiple_gids)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, multiple_gids)); +} + +TEST_F(VerifyPathControlledByUserTest, WriteBitChecks) { + // Make all files and directories non-world-writable. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(base_dir_, 0u, S_IWOTH)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(sub_dir_, 0u, S_IWOTH)); + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(text_file_, 0u, S_IWOTH)); + + // Initialy, we control all parts of the path. + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + // Make base_dir_ world-writable. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(base_dir_, S_IWOTH, 0u)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + // Make sub_dir_ world writable. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(sub_dir_, S_IWOTH, 0u)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + // Make text_file_ world writable. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(text_file_, S_IWOTH, 0u)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + // Make sub_dir_ non-world writable. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(sub_dir_, 0u, S_IWOTH)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + // Make base_dir_ non-world-writable. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(base_dir_, 0u, S_IWOTH)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_FALSE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); + + // Back to the initial state: Nothing is writable, so every path + // should pass. + ASSERT_NO_FATAL_FAILURE( + ChangePosixFilePermissions(text_file_, 0u, S_IWOTH)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, sub_dir_, uid_, ok_gids_)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + base_dir_, text_file_, uid_, ok_gids_)); + EXPECT_TRUE( + file_util::VerifyPathControlledByUser( + sub_dir_, text_file_, uid_, ok_gids_)); +} +#endif + +#endif // defined(OS_POSIX) + +} // namespace
diff --git a/src/base/file_util_win.cc b/src/base/file_util_win.cc new file mode 100644 index 0000000..ef51594 --- /dev/null +++ b/src/base/file_util_win.cc
@@ -0,0 +1,977 @@ +// 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 <windows.h> +#include <psapi.h> +#include <shellapi.h> +#include <shlobj.h> +#include <time.h> + +#include <limits> +#include <string> + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/process_util.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/threading/thread_restrictions.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" + +namespace file_util { + +namespace { + +const DWORD kFileShareAll = + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + +} // namespace + +bool AbsolutePath(FilePath* path) { + base::ThreadRestrictions::AssertIOAllowed(); + wchar_t file_path_buf[MAX_PATH]; + if (!_wfullpath(file_path_buf, path->value().c_str(), MAX_PATH)) + return false; + *path = FilePath(file_path_buf); + return true; +} + +int CountFilesCreatedAfter(const FilePath& path, + const base::Time& comparison_time) { + base::ThreadRestrictions::AssertIOAllowed(); + + int file_count = 0; + FILETIME comparison_filetime(comparison_time.ToFileTime()); + + WIN32_FIND_DATA find_file_data; + // All files in given dir + std::wstring filename_spec = path.Append(L"*").value(); + HANDLE find_handle = FindFirstFile(filename_spec.c_str(), &find_file_data); + if (find_handle != INVALID_HANDLE_VALUE) { + do { + // Don't count current or parent directories. + if ((wcscmp(find_file_data.cFileName, L"..") == 0) || + (wcscmp(find_file_data.cFileName, L".") == 0)) + continue; + + long result = CompareFileTime(&find_file_data.ftCreationTime, // NOLINT + &comparison_filetime); + // File was created after or on comparison time + if ((result == 1) || (result == 0)) + ++file_count; + } while (FindNextFile(find_handle, &find_file_data)); + FindClose(find_handle); + } + + return file_count; +} + +bool Delete(const FilePath& path, bool recursive) { + base::ThreadRestrictions::AssertIOAllowed(); + + if (path.value().length() >= MAX_PATH) + return false; + + if (!recursive) { + // If not recursing, then first check to see if |path| is a directory. + // If it is, then remove it with RemoveDirectory. + base::PlatformFileInfo file_info; + 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 + // because it should be faster. If DeleteFile fails, then we fall through + // to SHFileOperation, which will do the right thing. + if (DeleteFile(path.value().c_str()) != 0) + return true; + } + + // SHFILEOPSTRUCT wants the path to be terminated with two NULLs, + // so we have to use wcscpy because wcscpy_s writes non-NULLs + // 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}; + file_operation.wFunc = FO_DELETE; + file_operation.pFrom = double_terminated_path; + 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 + // being set (although MSDN doesn't give any scenarios for how this can + // happen). See MSDN for SHFileOperation and SHFILEOPTSTRUCT. + if (file_operation.fAnyOperationsAborted) + return false; + + // Some versions of Windows return ERROR_FILE_NOT_FOUND (0x2) when deleting + // an empty directory and some return 0x402 when they should be returning + // ERROR_FILE_NOT_FOUND. MSDN says Vista and up won't return 0x402. + return (err == 0 || err == ERROR_FILE_NOT_FOUND || err == 0x402); +} + +bool DeleteAfterReboot(const FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + + if (path.value().length() >= MAX_PATH) + return false; + + return MoveFileEx(path.value().c_str(), NULL, + MOVEFILE_DELAY_UNTIL_REBOOT | + MOVEFILE_REPLACE_EXISTING) != FALSE; +} + +bool Move(const FilePath& from_path, const FilePath& to_path) { + base::ThreadRestrictions::AssertIOAllowed(); + + // NOTE: I suspect we could support longer paths, but that would involve + // analyzing all our usage of files. + if (from_path.value().length() >= MAX_PATH || + to_path.value().length() >= MAX_PATH) { + return false; + } + if (MoveFileEx(from_path.value().c_str(), to_path.value().c_str(), + MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING) != 0) + return true; + + // Keep the last error value from MoveFileEx around in case the below + // fails. + bool ret = false; + DWORD last_error = ::GetLastError(); + + if (DirectoryExists(from_path)) { + // MoveFileEx fails if moving directory across volumes. We will simulate + // the move by using Copy and Delete. Ideally we could check whether + // from_path and to_path are indeed in different volumes. + ret = CopyAndDeleteDirectory(from_path, to_path); + } + + if (!ret) { + // Leave a clue about what went wrong so that it can be (at least) picked + // up by a PLOG entry. + ::SetLastError(last_error); + } + + return ret; +} + +bool ReplaceFile(const FilePath& from_path, const FilePath& to_path) { + base::ThreadRestrictions::AssertIOAllowed(); + // Try a simple move first. It will only succeed when |to_path| doesn't + // already exist. + if (::MoveFile(from_path.value().c_str(), to_path.value().c_str())) + return true; + // Try the full-blown replace if the move fails, as ReplaceFile will only + // succeed when |to_path| does exist. When writing to a network share, we may + // not be able to change the ACLs. Ignore ACL errors then + // (REPLACEFILE_IGNORE_MERGE_ERRORS). + if (::ReplaceFile(to_path.value().c_str(), from_path.value().c_str(), NULL, + REPLACEFILE_IGNORE_MERGE_ERRORS, NULL, NULL)) { + return true; + } + return false; +} + +bool CopyFile(const FilePath& from_path, const FilePath& to_path) { + base::ThreadRestrictions::AssertIOAllowed(); + + // NOTE: I suspect we could support longer paths, but that would involve + // analyzing all our usage of files. + if (from_path.value().length() >= MAX_PATH || + to_path.value().length() >= MAX_PATH) { + return false; + } + return (::CopyFile(from_path.value().c_str(), to_path.value().c_str(), + false) != 0); +} + +bool ShellCopy(const FilePath& from_path, const FilePath& to_path, + bool recursive) { + // WinXP SHFileOperation doesn't like trailing separators. + FilePath stripped_from = from_path.StripTrailingSeparators(); + FilePath stripped_to = to_path.StripTrailingSeparators(); + + base::ThreadRestrictions::AssertIOAllowed(); + + // NOTE: I suspect we could support longer paths, but that would involve + // analyzing all our usage of files. + if (stripped_from.value().length() >= MAX_PATH || + stripped_to.value().length() >= MAX_PATH) { + return false; + } + + // SHFILEOPSTRUCT wants the path to be terminated with two NULLs, + // so we have to use wcscpy because wcscpy_s writes non-NULLs + // into the rest of the buffer. + wchar_t double_terminated_path_from[MAX_PATH + 1] = {0}; + wchar_t double_terminated_path_to[MAX_PATH + 1] = {0}; +#pragma warning(suppress:4996) // don't complain about wcscpy deprecation + wcscpy(double_terminated_path_from, stripped_from.value().c_str()); +#pragma warning(suppress:4996) // don't complain about wcscpy deprecation + wcscpy(double_terminated_path_to, stripped_to.value().c_str()); + + SHFILEOPSTRUCT file_operation = {0}; + file_operation.wFunc = FO_COPY; + file_operation.pFrom = double_terminated_path_from; + file_operation.pTo = double_terminated_path_to; + file_operation.fFlags = FOF_NOERRORUI | FOF_SILENT | FOF_NOCONFIRMATION | + FOF_NOCONFIRMMKDIR; + if (!recursive) + file_operation.fFlags |= FOF_NORECURSION | FOF_FILESONLY; + + return (SHFileOperation(&file_operation) == 0); +} + +bool CopyDirectory(const FilePath& from_path, const FilePath& to_path, + bool recursive) { + base::ThreadRestrictions::AssertIOAllowed(); + + if (recursive) + return ShellCopy(from_path, to_path, true); + + // The following code assumes that from path is a directory. + DCHECK(DirectoryExists(from_path)); + + // Instead of creating a new directory, we copy the old one to include the + // security information of the folder as part of the copy. + if (!PathExists(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) + CreateDirectory(to_path); + else + ShellCopy(from_path, to_path, false); + } + + FilePath directory = from_path.Append(L"*.*"); + return ShellCopy(directory, to_path, false); +} + +bool CopyAndDeleteDirectory(const FilePath& from_path, + const FilePath& to_path) { + base::ThreadRestrictions::AssertIOAllowed(); + if (CopyDirectory(from_path, to_path, true)) { + if (Delete(from_path, true)) { + return true; + } + // Like Move, this function is not transactional, so we just + // leave the copied bits behind if deleting from_path fails. + // If to_path exists previously then we have already overwritten + // it by now, we don't get better off by deleting the new bits. + } + return false; +} + + +bool PathExists(const FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + return (GetFileAttributes(path.value().c_str()) != INVALID_FILE_ATTRIBUTES); +} + +bool PathIsWritable(const FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + HANDLE dir = + CreateFile(path.value().c_str(), FILE_ADD_FILE, kFileShareAll, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (dir == INVALID_HANDLE_VALUE) + return false; + + CloseHandle(dir); + return true; +} + +bool DirectoryExists(const FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + DWORD fileattr = GetFileAttributes(path.value().c_str()); + if (fileattr != INVALID_FILE_ATTRIBUTES) + return (fileattr & FILE_ATTRIBUTE_DIRECTORY) != 0; + return false; +} + +bool GetFileCreationLocalTimeFromHandle(HANDLE file_handle, + LPSYSTEMTIME creation_time) { + base::ThreadRestrictions::AssertIOAllowed(); + if (!file_handle) + return false; + + FILETIME utc_filetime; + if (!GetFileTime(file_handle, &utc_filetime, NULL, NULL)) + return false; + + FILETIME local_filetime; + if (!FileTimeToLocalFileTime(&utc_filetime, &local_filetime)) + return false; + + return !!FileTimeToSystemTime(&local_filetime, creation_time); +} + +bool GetFileCreationLocalTime(const std::wstring& filename, + LPSYSTEMTIME creation_time) { + base::ThreadRestrictions::AssertIOAllowed(); + base::win::ScopedHandle file_handle( + CreateFile(filename.c_str(), GENERIC_READ, kFileShareAll, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); + return GetFileCreationLocalTimeFromHandle(file_handle.Get(), creation_time); +} + +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) + return false; + // TODO(evanm): the old behavior of this function was to always strip the + // trailing slash. We duplicate this here, but it shouldn't be necessary + // when everyone is using the appropriate FilePath APIs. + *path = FilePath(temp_path).StripTrailingSeparators(); + return true; +} + +bool GetShmemTempDir(FilePath* path, bool executable) { + return GetTempDir(path); +} + +bool CreateTemporaryFile(FilePath* path) { + base::ThreadRestrictions::AssertIOAllowed(); + + FilePath temp_file; + + if (!GetTempDir(path)) + return false; + + if (CreateTemporaryFileInDir(*path, &temp_file)) { + *path = temp_file; + return true; + } + + return false; +} + +FILE* CreateAndOpenTemporaryShmemFile(FilePath* path, bool executable) { + base::ThreadRestrictions::AssertIOAllowed(); + return CreateAndOpenTemporaryFile(path); +} + +// On POSIX we have semantics to create and open a temporary file +// atomically. +// 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(); + if (!CreateTemporaryFileInDir(dir, path)) { + return NULL; + } + // Open file in binary mode, to avoid problems with fwrite. On Windows + // it replaces \n's with \r\n's, which may surprise you. + // Reference: http://msdn.microsoft.com/en-us/library/h9t88zwz(VS.71).aspx + return OpenFile(*path, "wb+"); +} + +bool CreateTemporaryFileInDir(const FilePath& dir, + FilePath* temp_file) { + base::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(); + return false; + } + + wchar_t long_temp_name[MAX_PATH + 1]; + DWORD long_name_len = GetLongPathName(temp_name, long_temp_name, MAX_PATH); + if (long_name_len > MAX_PATH || long_name_len == 0) { + // GetLongPathName() failed, but we still have a temporary file. + *temp_file = FilePath(temp_name); + return true; + } + + FilePath::StringType long_temp_name_str; + long_temp_name_str.assign(long_temp_name, long_name_len); + *temp_file = FilePath(long_temp_name_str); + return true; +} + +bool CreateTemporaryDirInDir(const FilePath& base_dir, + const FilePath::StringType& prefix, + FilePath* new_dir) { + base::ThreadRestrictions::AssertIOAllowed(); + + FilePath path_to_create; + srand(static_cast<uint32>(time(NULL))); + + for (int count = 0; count < 50; ++count) { + // Try create a new temporary directory with random generated name. If + // 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.push_back('_'); + new_dir_name.append(base::IntToString16(rand() % kint16max)); + + path_to_create = base_dir.Append(new_dir_name); + if (::CreateDirectory(path_to_create.value().c_str(), NULL)) { + *new_dir = path_to_create; + return true; + } + } + + return false; +} + +bool CreateNewTempDirectory(const FilePath::StringType& prefix, + FilePath* new_temp_path) { + base::ThreadRestrictions::AssertIOAllowed(); + + FilePath system_temp_dir; + if (!GetTempDir(&system_temp_dir)) + return false; + + return CreateTemporaryDirInDir(system_temp_dir, prefix, new_temp_path); +} + +bool CreateDirectory(const FilePath& full_path) { + base::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(); + DWORD fileattr = ::GetFileAttributes(full_path_str); + if (fileattr != INVALID_FILE_ATTRIBUTES) { + if ((fileattr & FILE_ATTRIBUTE_DIRECTORY) != 0) { + DVLOG(1) << "CreateDirectory(" << full_path_str << "), " + << "directory already exists."; + return true; + } + DLOG(WARNING) << "CreateDirectory(" << full_path_str << "), " + << "conflicts with existing file."; + return false; + } + + // Invariant: Path does not exist as file or directory. + + // Attempt to create the parent recursively. This will immediately return + // true if it already exists, otherwise will create all required parent + // directories starting with the highest-level missing parent. + FilePath parent_path(full_path.DirName()); + if (parent_path.value() == full_path.value()) { + return false; + } + if (!CreateDirectory(parent_path)) { + DLOG(WARNING) << "Failed to create one of the parent directories."; + return false; + } + + if (!::CreateDirectory(full_path_str, NULL)) { + DWORD error_code = ::GetLastError(); + if (error_code == ERROR_ALREADY_EXISTS && DirectoryExists(full_path)) { + // This error code ERROR_ALREADY_EXISTS doesn't indicate whether we + // were racing with someone creating the same directory, or a file + // with the same path. If DirectoryExists() returns true, we lost the + // race to create the same directory. + return true; + } else { + DLOG(WARNING) << "Failed to create directory " << full_path_str + << ", last error is " << error_code << "."; + return false; + } + } else { + return true; + } +} + +// 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(); + + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!GetFileAttributesEx(file_path.value().c_str(), + GetFileExInfoStandard, &attr)) { + return false; + } + + ULARGE_INTEGER size; + size.HighPart = attr.nFileSizeHigh; + size.LowPart = attr.nFileSizeLow; + results->size = size.QuadPart; + + 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); + + return true; +} + +FILE* OpenFile(const FilePath& filename, const char* mode) { + base::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(); + base::win::ScopedHandle file(CreateFile(filename.value().c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, + NULL)); + if (!file) + return -1; + + DWORD read; + if (::ReadFile(file, data, size, &read, NULL) && + static_cast<int>(read) == size) + return read; + return -1; +} + +int WriteFile(const FilePath& filename, const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + base::win::ScopedHandle file(CreateFile(filename.value().c_str(), + GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + 0, + NULL)); + if (!file) { + DLOG(WARNING) << "CreateFile failed for path " << filename.value() + << " error code=" << GetLastError(); + return -1; + } + + DWORD written; + BOOL result = ::WriteFile(file, data, size, &written, NULL); + if (result && static_cast<int>(written) == size) + return written; + + if (!result) { + // WriteFile failed. + DLOG(WARNING) << "writing file " << filename.value() + << " failed, error code=" << GetLastError(); + } else { + // Didn't write all the bytes. + DLOG(WARNING) << "wrote" << written << " bytes to " + << filename.value() << " expected " << size; + } + return -1; +} + +int AppendToFile(const FilePath& filename, const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + base::win::ScopedHandle file(CreateFile(filename.value().c_str(), + FILE_APPEND_DATA, + 0, + NULL, + OPEN_EXISTING, + 0, + NULL)); + if (!file) { + DLOG(WARNING) << "CreateFile failed for path " << filename.value() + << " error code=" << GetLastError(); + return -1; + } + + DWORD written; + BOOL result = ::WriteFile(file, data, size, &written, NULL); + if (result && static_cast<int>(written) == size) + return written; + + if (!result) { + // WriteFile failed. + DLOG(WARNING) << "writing file " << filename.value() + << " failed, error code=" << GetLastError(); + } else { + // Didn't write all the bytes. + DLOG(WARNING) << "wrote" << written << " bytes to " + << filename.value() << " expected " << size; + } + return -1; +} + +// Gets the current working directory for the process. +bool GetCurrentDirectory(FilePath* dir) { + base::ThreadRestrictions::AssertIOAllowed(); + + wchar_t system_buffer[MAX_PATH]; + system_buffer[0] = 0; + DWORD len = ::GetCurrentDirectory(MAX_PATH, system_buffer); + if (len == 0 || len > MAX_PATH) + return false; + // TODO(evanm): the old behavior of this function was to always strip the + // trailing slash. We duplicate this here, but it shouldn't be necessary + // when everyone is using the appropriate FilePath APIs. + std::wstring dir_str(system_buffer); + *dir = FilePath(dir_str).StripTrailingSeparators(); + return true; +} + +// Sets the current working directory for the process. +bool SetCurrentDirectory(const FilePath& directory) { + base::ThreadRestrictions::AssertIOAllowed(); + BOOL ret = ::SetCurrentDirectory(directory.value().c_str()); + return ret != 0; +} + +/////////////////////////////////////////////// +// FileEnumerator + +FileEnumerator::FileEnumerator(const FilePath& root_path, + bool recursive, + int file_type) + : recursive_(recursive), + file_type_(file_type), + has_find_data_(false), + find_handle_(INVALID_HANDLE_VALUE) { + // INCLUDE_DOT_DOT must not be specified if recursive. + DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_))); + memset(&find_data_, 0, sizeof(find_data_)); + pending_paths_.push(root_path); +} + +FileEnumerator::FileEnumerator(const FilePath& root_path, + bool recursive, + int file_type, + const FilePath::StringType& pattern) + : recursive_(recursive), + file_type_(file_type), + has_find_data_(false), + pattern_(pattern), + find_handle_(INVALID_HANDLE_VALUE) { + // INCLUDE_DOT_DOT must not be specified if recursive. + DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_))); + memset(&find_data_, 0, sizeof(find_data_)); + pending_paths_.push(root_path); +} + +FileEnumerator::~FileEnumerator() { + if (find_handle_ != INVALID_HANDLE_VALUE) + FindClose(find_handle_); +} + +void FileEnumerator::GetFindInfo(FindInfo* info) { + DCHECK(info); + + if (!has_find_data_) + return; + + memcpy(info, &find_data_, sizeof(*info)); +} + +// static +bool FileEnumerator::IsDirectory(const FindInfo& info) { + return (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; +} + +// static +FilePath FileEnumerator::GetFilename(const FindInfo& find_info) { + return FilePath(find_info.cFileName); +} + +// static +int64 FileEnumerator::GetFilesize(const FindInfo& find_info) { + ULARGE_INTEGER size; + size.HighPart = find_info.nFileSizeHigh; + size.LowPart = find_info.nFileSizeLow; + DCHECK_LE(size.QuadPart, std::numeric_limits<int64>::max()); + return static_cast<int64>(size.QuadPart); +} + +// static +base::Time FileEnumerator::GetLastModifiedTime(const FindInfo& find_info) { + return base::Time::FromFileTime(find_info.ftLastWriteTime); +} + +FilePath FileEnumerator::Next() { + base::ThreadRestrictions::AssertIOAllowed(); + + while (has_find_data_ || !pending_paths_.empty()) { + if (!has_find_data_) { + // The last find FindFirstFile operation is done, prepare a new one. + root_path_ = pending_paths_.top(); + pending_paths_.pop(); + + // Start a new find operation. + FilePath src = root_path_; + + if (pattern_.empty()) + src = src.Append(L"*"); // No pattern = match everything. + else + src = src.Append(pattern_); + + find_handle_ = FindFirstFile(src.value().c_str(), &find_data_); + has_find_data_ = true; + } else { + // Search for the next file/directory. + if (!FindNextFile(find_handle_, &find_data_)) { + FindClose(find_handle_); + find_handle_ = INVALID_HANDLE_VALUE; + } + } + + if (INVALID_HANDLE_VALUE == find_handle_) { + has_find_data_ = false; + + // This is reached when we have finished a directory and are advancing to + // the next one in the queue. We applied the pattern (if any) to the files + // in the root search directory, but for those directories which were + // matched, we want to enumerate all files inside them. This will happen + // when the handle is empty. + pattern_ = FilePath::StringType(); + + continue; + } + + FilePath cur_file(find_data_.cFileName); + if (ShouldSkip(cur_file)) + continue; + + // Construct the absolute filename. + cur_file = root_path_.Append(find_data_.cFileName); + + if (find_data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (recursive_) { + // If |cur_file| is a directory, and we are doing recursive searching, + // add it to pending_paths_ so we scan it after we finish scanning this + // directory. + pending_paths_.push(cur_file); + } + if (file_type_ & FileEnumerator::DIRECTORIES) + return cur_file; + } else if (file_type_ & FileEnumerator::FILES) { + return cur_file; + } + } + + return FilePath(); +} + +/////////////////////////////////////////////// +// MemoryMappedFile + +MemoryMappedFile::MemoryMappedFile() + : file_(INVALID_HANDLE_VALUE), + file_mapping_(INVALID_HANDLE_VALUE), + data_(NULL), + length_(INVALID_FILE_SIZE) { +} + +bool MemoryMappedFile::InitializeAsImageSection(const FilePath& file_name) { + if (IsValid()) + return false; + file_ = base::CreatePlatformFile( + file_name, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, + NULL, NULL); + + if (file_ == base::kInvalidPlatformFileValue) { + DLOG(ERROR) << "Couldn't open " << file_name.value(); + return false; + } + + if (!MapFileToMemoryInternalEx(SEC_IMAGE)) { + CloseHandles(); + return false; + } + + return true; +} + +bool MemoryMappedFile::MapFileToMemoryInternal() { + return MapFileToMemoryInternalEx(0); +} + +bool MemoryMappedFile::MapFileToMemoryInternalEx(int flags) { + base::ThreadRestrictions::AssertIOAllowed(); + + if (file_ == INVALID_HANDLE_VALUE) + return false; + + length_ = ::GetFileSize(file_, NULL); + if (length_ == INVALID_FILE_SIZE) + return false; + + file_mapping_ = ::CreateFileMapping(file_, NULL, PAGE_READONLY | flags, + 0, 0, NULL); + if (!file_mapping_) { + // According to msdn, system error codes are only reserved up to 15999. + // http://msdn.microsoft.com/en-us/library/ms681381(v=VS.85).aspx. + UMA_HISTOGRAM_ENUMERATION("MemoryMappedFile.CreateFileMapping", + logging::GetLastSystemErrorCode(), 16000); + return false; + } + + data_ = static_cast<uint8*>( + ::MapViewOfFile(file_mapping_, FILE_MAP_READ, 0, 0, 0)); + if (!data_) { + UMA_HISTOGRAM_ENUMERATION("MemoryMappedFile.MapViewOfFile", + logging::GetLastSystemErrorCode(), 16000); + } + return data_ != NULL; +} + +void MemoryMappedFile::CloseHandles() { + if (data_) + ::UnmapViewOfFile(data_); + if (file_mapping_ != INVALID_HANDLE_VALUE) + ::CloseHandle(file_mapping_); + if (file_ != INVALID_HANDLE_VALUE) + ::CloseHandle(file_); + + data_ = NULL; + file_mapping_ = file_ = INVALID_HANDLE_VALUE; + length_ = INVALID_FILE_SIZE; +} + +bool HasFileBeenModifiedSince(const FileEnumerator::FindInfo& find_info, + const base::Time& cutoff_time) { + base::ThreadRestrictions::AssertIOAllowed(); + FILETIME file_time = cutoff_time.ToFileTime(); + long result = CompareFileTime(&find_info.ftLastWriteTime, // NOLINT + &file_time); + return result == 1 || result == 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; +} + +} // namespace file_util
diff --git a/src/base/file_version_info.h b/src/base/file_version_info.h new file mode 100644 index 0000000..84eec41 --- /dev/null +++ b/src/base/file_version_info.h
@@ -0,0 +1,85 @@ +// 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. + +#ifndef BASE_FILE_VERSION_INFO_H__ +#define BASE_FILE_VERSION_INFO_H__ + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +// http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx +extern "C" IMAGE_DOS_HEADER __ImageBase; +#endif // OS_WIN + +#include <string> + +#include "base/base_export.h" +#include "base/string16.h" + +class FilePath; + +// Provides an interface for accessing the version information for a file. This +// is the information you access when you select a file in the Windows Explorer, +// right-click select Properties, then click the Version tab, and on the Mac +// when you select a file in the Finder and do a Get Info. +// +// This list of properties is straight out of Win32's VerQueryValue +// <http://msdn.microsoft.com/en-us/library/ms647464.aspx> and the Mac +// version returns values from the Info.plist as appropriate. TODO(avi): make +// this a less-obvious Windows-ism. + +class FileVersionInfo { + public: + virtual ~FileVersionInfo() {} +#if defined(OS_WIN) || defined(OS_MACOSX) + // Creates a FileVersionInfo for the specified path. Returns NULL if something + // goes wrong (typically the file does not exit or cannot be opened). The + // returned object should be deleted when you are done with it. + BASE_EXPORT static FileVersionInfo* CreateFileVersionInfo( + const FilePath& file_path); +#endif // OS_WIN || OS_MACOSX + +#if defined(OS_WIN) + // Creates a FileVersionInfo for the specified module. Returns NULL in case + // of error. The returned object should be deleted when you are done with it. + BASE_EXPORT static FileVersionInfo* CreateFileVersionInfoForModule( + HMODULE module); + + // Creates a FileVersionInfo for the current module. Returns NULL in case + // of error. The returned object should be deleted when you are done with it. + // This function should be inlined so that the "current module" is evaluated + // correctly, instead of being the module that contains base. + __forceinline static FileVersionInfo* + CreateFileVersionInfoForCurrentModule() { + HMODULE module = reinterpret_cast<HMODULE>(&__ImageBase); + return CreateFileVersionInfoForModule(module); + } +#else + // Creates a FileVersionInfo for the current module. Returns NULL in case + // of error. The returned object should be deleted when you are done with it. + BASE_EXPORT static FileVersionInfo* CreateFileVersionInfoForCurrentModule(); +#endif // OS_WIN + + // 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 bool is_official_build() = 0; +}; + +#endif // BASE_FILE_VERSION_INFO_H__
diff --git a/src/base/file_version_info_mac.h b/src/base/file_version_info_mac.h new file mode 100644 index 0000000..0ab3296 --- /dev/null +++ b/src/base/file_version_info_mac.h
@@ -0,0 +1,53 @@ +// 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. + +#ifndef BASE_FILE_VERSION_INFO_MAC_H_ +#define BASE_FILE_VERSION_INFO_MAC_H_ + +#include <string> + +#include "base/file_version_info.h" +#include "base/memory/scoped_nsobject.h" + +#ifdef __OBJC__ +@class NSBundle; +#else +class NSBundle; +#endif + +class FileVersionInfoMac : public FileVersionInfo { + public: + explicit FileVersionInfoMac(NSBundle *bundle); + virtual ~FileVersionInfoMac(); + + // Accessors to the different version properties. + // Returns an empty string if the property is not found. + virtual string16 company_name() OVERRIDE; + virtual string16 company_short_name() OVERRIDE; + virtual string16 product_name() OVERRIDE; + virtual string16 product_short_name() OVERRIDE; + virtual string16 internal_name() OVERRIDE; + virtual string16 product_version() OVERRIDE; + virtual string16 private_build() OVERRIDE; + virtual string16 special_build() OVERRIDE; + virtual string16 comments() OVERRIDE; + virtual string16 original_filename() OVERRIDE; + virtual string16 file_description() OVERRIDE; + virtual string16 file_version() OVERRIDE; + virtual string16 legal_copyright() OVERRIDE; + virtual string16 legal_trademarks() OVERRIDE; + virtual string16 last_change() OVERRIDE; + virtual bool is_official_build() OVERRIDE; + + private: + // Returns a string16 value for a property name. + // Returns the empty string if the property does not exist. + string16 GetString16Value(CFStringRef name); + + scoped_nsobject<NSBundle> bundle_; + + DISALLOW_COPY_AND_ASSIGN(FileVersionInfoMac); +}; + +#endif // BASE_FILE_VERSION_INFO_MAC_H_
diff --git a/src/base/file_version_info_mac.mm b/src/base/file_version_info_mac.mm new file mode 100644 index 0000000..3a6df81 --- /dev/null +++ b/src/base/file_version_info_mac.mm
@@ -0,0 +1,120 @@ +// 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_version_info_mac.h" + +#import <Foundation/Foundation.h> + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/mac/bundle_locations.h" +#include "base/mac/foundation_util.h" +#include "base/sys_string_conversions.h" + +FileVersionInfoMac::FileVersionInfoMac(NSBundle *bundle) + : bundle_([bundle retain]) { +} + +FileVersionInfoMac::~FileVersionInfoMac() {} + +// static +FileVersionInfo* FileVersionInfo::CreateFileVersionInfoForCurrentModule() { + return CreateFileVersionInfo(base::mac::FrameworkBundlePath()); +} + +// static +FileVersionInfo* FileVersionInfo::CreateFileVersionInfo( + const FilePath& file_path) { + NSString* path = base::SysUTF8ToNSString(file_path.value()); + NSBundle* bundle = [NSBundle bundleWithPath:path]; + return new FileVersionInfoMac(bundle); +} + +string16 FileVersionInfoMac::company_name() { + return string16(); +} + +string16 FileVersionInfoMac::company_short_name() { + return string16(); +} + +string16 FileVersionInfoMac::internal_name() { + return string16(); +} + +string16 FileVersionInfoMac::product_name() { + return GetString16Value(kCFBundleNameKey); +} + +string16 FileVersionInfoMac::product_short_name() { + return GetString16Value(kCFBundleNameKey); +} + +string16 FileVersionInfoMac::comments() { + return string16(); +} + +string16 FileVersionInfoMac::legal_copyright() { + return GetString16Value(CFSTR("CFBundleGetInfoString")); +} + +string16 FileVersionInfoMac::product_version() { + // On OS X, CFBundleVersion is used by LaunchServices, and must follow + // specific formatting rules, so the four-part Chrome version is in + // CFBundleShortVersionString. On iOS, however, CFBundleVersion can be the + // full version, but CFBundleShortVersionString has a policy-enfoced limit + // of three version components. +#if defined(OS_IOS) + return GetString16Value(CFSTR("CFBundleVersion")); +#else + return GetString16Value(CFSTR("CFBundleShortVersionString")); +#endif // defined(OS_IOS) +} + +string16 FileVersionInfoMac::file_description() { + return string16(); +} + +string16 FileVersionInfoMac::legal_trademarks() { + return string16(); +} + +string16 FileVersionInfoMac::private_build() { + return string16(); +} + +string16 FileVersionInfoMac::file_version() { + return product_version(); +} + +string16 FileVersionInfoMac::original_filename() { + return GetString16Value(kCFBundleNameKey); +} + +string16 FileVersionInfoMac::special_build() { + return string16(); +} + +string16 FileVersionInfoMac::last_change() { + return GetString16Value(CFSTR("SCMRevision")); +} + +bool FileVersionInfoMac::is_official_build() { +#if defined (GOOGLE_CHROME_BUILD) + return true; +#else + return false; +#endif +} + +string16 FileVersionInfoMac::GetString16Value(CFStringRef name) { + if (bundle_) { + NSString *ns_name = base::mac::CFToNSCast(name); + NSString* value = [bundle_ objectForInfoDictionaryKey:ns_name]; + if (value) { + return base::SysNSStringToUTF16(value); + } + } + return string16(); +}
diff --git a/src/base/file_version_info_unittest.cc b/src/base/file_version_info_unittest.cc new file mode 100644 index 0000000..916e7bc --- /dev/null +++ b/src/base/file_version_info_unittest.cc
@@ -0,0 +1,138 @@ +// 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/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/file_version_info.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include "base/file_version_info_win.h" +#endif + +namespace { + +#if defined(OS_WIN) +FilePath GetTestDataPath() { + FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + path = path.AppendASCII("base"); + path = path.AppendASCII("data"); + path = path.AppendASCII("file_version_info_unittest"); + return path; +} +#endif + +} + +#if defined(OS_WIN) +TEST(FileVersionInfoTest, HardCodedProperties) { + const wchar_t* kDLLNames[] = { + L"FileVersionInfoTest1.dll" + }; + + const wchar_t* kExpectedValues[1][15] = { + // FileVersionInfoTest.dll + L"Goooooogle", // company_name + L"Google", // company_short_name + L"This is the product name", // product_name + L"This is the product short name", // product_short_name + L"The Internal Name", // internal_name + L"4.3.2.1", // product_version + L"Private build property", // private_build + L"Special build property", // special_build + L"This is a particularly interesting comment", // comments + L"This is the original filename", // original_filename + L"This is my file description", // file_description + L"1.2.3.4", // file_version + L"This is the legal copyright", // legal_copyright + L"This is the legal trademarks", // legal_trademarks + L"This is the last change", // last_change + }; + + for (int i = 0; i < arraysize(kDLLNames); ++i) { + FilePath dll_path = GetTestDataPath(); + dll_path = dll_path.Append(kDLLNames[i]); + + scoped_ptr<FileVersionInfo> version_info( + FileVersionInfo::CreateFileVersionInfo(dll_path)); + + int j = 0; + EXPECT_EQ(kExpectedValues[i][j++], version_info->company_name()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->company_short_name()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->product_name()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->product_short_name()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->internal_name()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->product_version()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->private_build()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->special_build()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->comments()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->original_filename()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->file_description()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->file_version()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->legal_copyright()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->legal_trademarks()); + EXPECT_EQ(kExpectedValues[i][j++], version_info->last_change()); + } +} +#endif + +#if defined(OS_WIN) +TEST(FileVersionInfoTest, IsOfficialBuild) { + const wchar_t* kDLLNames[] = { + L"FileVersionInfoTest1.dll", + L"FileVersionInfoTest2.dll" + }; + + const bool kExpected[] = { + true, + false, + }; + + // Test consistency check. + ASSERT_EQ(arraysize(kDLLNames), arraysize(kExpected)); + + for (int i = 0; i < arraysize(kDLLNames); ++i) { + FilePath dll_path = GetTestDataPath(); + dll_path = dll_path.Append(kDLLNames[i]); + + scoped_ptr<FileVersionInfo> version_info( + FileVersionInfo::CreateFileVersionInfo(dll_path)); + + EXPECT_EQ(kExpected[i], version_info->is_official_build()); + } +} +#endif + +#if defined(OS_WIN) +TEST(FileVersionInfoTest, CustomProperties) { + FilePath dll_path = GetTestDataPath(); + dll_path = dll_path.AppendASCII("FileVersionInfoTest1.dll"); + + scoped_ptr<FileVersionInfo> version_info( + FileVersionInfo::CreateFileVersionInfo(dll_path)); + + // Test few existing properties. + std::wstring str; + FileVersionInfoWin* version_info_win = + static_cast<FileVersionInfoWin*>(version_info.get()); + EXPECT_TRUE(version_info_win->GetValue(L"Custom prop 1", &str)); + EXPECT_EQ(L"Un", str); + EXPECT_EQ(L"Un", version_info_win->GetStringValue(L"Custom prop 1")); + + EXPECT_TRUE(version_info_win->GetValue(L"Custom prop 2", &str)); + EXPECT_EQ(L"Deux", str); + EXPECT_EQ(L"Deux", version_info_win->GetStringValue(L"Custom prop 2")); + + EXPECT_TRUE(version_info_win->GetValue(L"Custom prop 3", &str)); + EXPECT_EQ(L"1600 Amphitheatre Parkway Mountain View, CA 94043", str); + EXPECT_EQ(L"1600 Amphitheatre Parkway Mountain View, CA 94043", + version_info_win->GetStringValue(L"Custom prop 3")); + + // Test an non-existing property. + EXPECT_FALSE(version_info_win->GetValue(L"Unknown property", &str)); + EXPECT_EQ(L"", version_info_win->GetStringValue(L"Unknown property")); +} +#endif
diff --git a/src/base/file_version_info_win.cc b/src/base/file_version_info_win.cc new file mode 100644 index 0000000..6528ca3 --- /dev/null +++ b/src/base/file_version_info_win.cc
@@ -0,0 +1,187 @@ +// 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/file_version_info_win.h" + +#include <windows.h> + +#include "base/file_path.h" +#include "base/file_version_info.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/threading/thread_restrictions.h" + +FileVersionInfoWin::FileVersionInfoWin(void* data, int language, int code_page) + : language_(language), code_page_(code_page) { + base::ThreadRestrictions::AssertIOAllowed(); + data_.reset((char*) data); + fixed_file_info_ = NULL; + UINT size; + ::VerQueryValue(data_.get(), L"\\", (LPVOID*)&fixed_file_info_, &size); +} + +FileVersionInfoWin::~FileVersionInfoWin() { + DCHECK(data_.get()); +} + +typedef struct { + WORD language; + WORD code_page; +} LanguageAndCodePage; + +// static +FileVersionInfo* FileVersionInfo::CreateFileVersionInfoForModule( + HMODULE module) { + // Note that the use of MAX_PATH is basically in line with what we do for + // all registered paths (PathProviderWin). + wchar_t system_buffer[MAX_PATH]; + system_buffer[0] = 0; + if (!GetModuleFileName(module, system_buffer, MAX_PATH)) + return NULL; + + FilePath app_path(system_buffer); + return CreateFileVersionInfo(app_path); +} + +// static +FileVersionInfo* FileVersionInfo::CreateFileVersionInfo( + const FilePath& file_path) { + base::ThreadRestrictions::AssertIOAllowed(); + + DWORD dummy; + const wchar_t* path = file_path.value().c_str(); + DWORD length = ::GetFileVersionInfoSize(path, &dummy); + if (length == 0) + return NULL; + + void* data = calloc(length, 1); + if (!data) + return NULL; + + if (!::GetFileVersionInfo(path, dummy, length, data)) { + free(data); + return NULL; + } + + LanguageAndCodePage* translate = NULL; + uint32 page_count; + BOOL query_result = VerQueryValue(data, L"\\VarFileInfo\\Translation", + (void**) &translate, &page_count); + + if (query_result && translate) { + return new FileVersionInfoWin(data, translate->language, + translate->code_page); + + } else { + free(data); + return NULL; + } +} + +string16 FileVersionInfoWin::company_name() { + return GetStringValue(L"CompanyName"); +} + +string16 FileVersionInfoWin::company_short_name() { + return GetStringValue(L"CompanyShortName"); +} + +string16 FileVersionInfoWin::internal_name() { + return GetStringValue(L"InternalName"); +} + +string16 FileVersionInfoWin::product_name() { + return GetStringValue(L"ProductName"); +} + +string16 FileVersionInfoWin::product_short_name() { + return GetStringValue(L"ProductShortName"); +} + +string16 FileVersionInfoWin::comments() { + return GetStringValue(L"Comments"); +} + +string16 FileVersionInfoWin::legal_copyright() { + return GetStringValue(L"LegalCopyright"); +} + +string16 FileVersionInfoWin::product_version() { + return GetStringValue(L"ProductVersion"); +} + +string16 FileVersionInfoWin::file_description() { + return GetStringValue(L"FileDescription"); +} + +string16 FileVersionInfoWin::legal_trademarks() { + return GetStringValue(L"LegalTrademarks"); +} + +string16 FileVersionInfoWin::private_build() { + return GetStringValue(L"PrivateBuild"); +} + +string16 FileVersionInfoWin::file_version() { + return GetStringValue(L"FileVersion"); +} + +string16 FileVersionInfoWin::original_filename() { + return GetStringValue(L"OriginalFilename"); +} + +string16 FileVersionInfoWin::special_build() { + return GetStringValue(L"SpecialBuild"); +} + +string16 FileVersionInfoWin::last_change() { + return GetStringValue(L"LastChange"); +} + +bool FileVersionInfoWin::is_official_build() { + return (GetStringValue(L"Official Build").compare(L"1") == 0); +} + +bool FileVersionInfoWin::GetValue(const wchar_t* name, + std::wstring* value_str) { + WORD lang_codepage[8]; + int i = 0; + // Use the language and codepage from the DLL. + lang_codepage[i++] = language_; + lang_codepage[i++] = code_page_; + // Use the default language and codepage from the DLL. + lang_codepage[i++] = ::GetUserDefaultLangID(); + lang_codepage[i++] = code_page_; + // Use the language from the DLL and Latin codepage (most common). + lang_codepage[i++] = language_; + lang_codepage[i++] = 1252; + // Use the default language and Latin codepage (most common). + lang_codepage[i++] = ::GetUserDefaultLangID(); + lang_codepage[i++] = 1252; + + i = 0; + while (i < arraysize(lang_codepage)) { + wchar_t sub_block[MAX_PATH]; + WORD language = lang_codepage[i++]; + WORD code_page = lang_codepage[i++]; + _snwprintf_s(sub_block, MAX_PATH, MAX_PATH, + L"\\StringFileInfo\\%04x%04x\\%ls", language, code_page, name); + LPVOID value = NULL; + uint32 size; + BOOL r = ::VerQueryValue(data_.get(), sub_block, &value, &size); + if (r && value) { + value_str->assign(static_cast<wchar_t*>(value)); + return true; + } + } + return false; +} + +std::wstring FileVersionInfoWin::GetStringValue(const wchar_t* name) { + std::wstring str; + if (GetValue(name, &str)) + return str; + else + return L""; +}
diff --git a/src/base/file_version_info_win.h b/src/base/file_version_info_win.h new file mode 100644 index 0000000..a378577 --- /dev/null +++ b/src/base/file_version_info_win.h
@@ -0,0 +1,62 @@ +// 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. + +#ifndef BASE_FILE_VERSION_INFO_WIN_H_ +#define BASE_FILE_VERSION_INFO_WIN_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/file_version_info.h" +#include "base/memory/scoped_ptr.h" + +struct tagVS_FIXEDFILEINFO; +typedef tagVS_FIXEDFILEINFO VS_FIXEDFILEINFO; + +class FileVersionInfoWin : public FileVersionInfo { + public: + BASE_EXPORT FileVersionInfoWin(void* data, int language, int code_page); + BASE_EXPORT ~FileVersionInfoWin(); + + // Accessors to the different version properties. + // Returns an empty string if the property is not found. + virtual string16 company_name() OVERRIDE; + virtual string16 company_short_name() OVERRIDE; + virtual string16 product_name() OVERRIDE; + virtual string16 product_short_name() OVERRIDE; + virtual string16 internal_name() OVERRIDE; + virtual string16 product_version() OVERRIDE; + virtual string16 private_build() OVERRIDE; + virtual string16 special_build() OVERRIDE; + virtual string16 comments() OVERRIDE; + virtual string16 original_filename() OVERRIDE; + virtual string16 file_description() OVERRIDE; + virtual string16 file_version() OVERRIDE; + virtual string16 legal_copyright() OVERRIDE; + virtual string16 legal_trademarks() OVERRIDE; + virtual string16 last_change() OVERRIDE; + virtual bool is_official_build() OVERRIDE; + + // Lets you access other properties not covered above. + BASE_EXPORT bool GetValue(const wchar_t* name, std::wstring* value); + + // Similar to GetValue but returns a wstring (empty string if the property + // does not exist). + BASE_EXPORT std::wstring GetStringValue(const wchar_t* name); + + // Get the fixed file info if it exists. Otherwise NULL + VS_FIXEDFILEINFO* fixed_file_info() { return fixed_file_info_; } + + private: + scoped_ptr_malloc<char> data_; + int language_; + int code_page_; + // This is a pointer into the data_ if it exists. Otherwise NULL. + VS_FIXEDFILEINFO* fixed_file_info_; + + DISALLOW_COPY_AND_ASSIGN(FileVersionInfoWin); +}; + +#endif // BASE_FILE_VERSION_INFO_WIN_H_
diff --git a/src/base/files/dir_reader_dirent.h b/src/base/files/dir_reader_dirent.h new file mode 100644 index 0000000..c2d88d5 --- /dev/null +++ b/src/base/files/dir_reader_dirent.h
@@ -0,0 +1,90 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Follows the interface defined in dir_reader_posix.h/dir_reader_fallback.h +// but isn't directly included as the memory usage of this object may be higher +// than that offered by the dir_reader_linux implementation. Therefore the +// programmer must ask for this DirReaderDirent implementation by name. Also +// we add a few methods to this object to make file enumeration more convenient. + +#ifndef BASE_DIR_READER_DIRENT_H_ +#define BASE_DIR_READER_DIRENT_H_ +#pragma once + +#include <dirent.h> + +#include "base/logging.h" + +namespace base { + +class DirReaderDirent { + public: + explicit DirReaderDirent(const char* directory_path) + : current_(NULL) { + dir_ = opendir(directory_path); + } + + ~DirReaderDirent() { + if (dir_) { + closedir(dir_); + } + } + + bool IsValid() const { + return dir_ != NULL; + } + + bool Next() { + if (!IsValid()) return false; + current_ = readdir(dir_); + return current_ != NULL; + } + + const char* name() const { + if (!IsValid()) return NULL; + if (!current_) return NULL; + return current_->d_name; + } + +#if !defined(__LB_SHELL__) + // reading the linux implementation this is clear it is a call for the + // opened directory fd + int fd() const { + if (!IsValid()) return -1; + return dir_->fd; + } +#endif + + static bool IsFallback() { + return false; + } + + //**************** extra convenience methods + + // returns true if the current enumerated object is a file + bool IsFile() const { + if (!IsValid()) return false; + if (!current_) return false; + return (current_->d_type == DT_REG); + } + + private: + DIR* dir_; + dirent* current_; +}; + +} +#endif // BASE_DIR_READER_DIRENT_H_
diff --git a/src/base/files/dir_reader_fallback.h b/src/base/files/dir_reader_fallback.h new file mode 100644 index 0000000..a435f25 --- /dev/null +++ b/src/base/files/dir_reader_fallback.h
@@ -0,0 +1,35 @@ +// 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_DIR_READER_FALLBACK_H_ +#define BASE_FILES_DIR_READER_FALLBACK_H_ + +namespace base { + +class DirReaderFallback { + public: + // Open a directory. If |IsValid| is true, then |Next| can be called to start + // the iteration at the beginning of the directory. + explicit DirReaderFallback(const char* directory_path) {} + + // After construction, IsValid returns true iff the directory was + // successfully opened. + bool IsValid() const { return false; } + + // Move to the next entry returning false if the iteration is complete. + bool Next() { return false; } + + // Return the name of the current directory entry. + const char* name() { return 0;} + + // Return the file descriptor which is being used. + int fd() const { return -1; } + + // Returns true if this is a no-op fallback class (for testing). + static bool IsFallback() { return true; } +}; + +} // namespace base + +#endif // BASE_FILES_DIR_READER_FALLBACK_H_
diff --git a/src/base/files/dir_reader_linux.h b/src/base/files/dir_reader_linux.h new file mode 100644 index 0000000..fda955f --- /dev/null +++ b/src/base/files/dir_reader_linux.h
@@ -0,0 +1,109 @@ +// 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_DIR_READER_LINUX_H_ +#define BASE_FILES_DIR_READER_LINUX_H_ + +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <sys/syscall.h> +#include <unistd.h> +#include <dirent.h> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +// See the comments in dir_reader_posix.h about this. + +namespace base { + +struct linux_dirent { + uint64_t d_ino; + int64_t d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[0]; +}; + +class DirReaderLinux { + public: + explicit DirReaderLinux(const char* directory_path) + : fd_(open(directory_path, O_RDONLY | O_DIRECTORY)), + offset_(0), + size_(0) { + memset(buf_, 0, sizeof(buf_)); + } + + ~DirReaderLinux() { + if (fd_ >= 0) { + if (HANDLE_EINTR(close(fd_))) + RAW_LOG(ERROR, "Failed to close directory handle"); + } + } + + bool IsValid() const { + return fd_ >= 0; + } + + // Move to the next entry returning false if the iteration is complete. + bool Next() { + if (size_) { + linux_dirent* dirent = reinterpret_cast<linux_dirent*>(&buf_[offset_]); + offset_ += dirent->d_reclen; + } + + if (offset_ != size_) + return true; + + const int r = syscall(__NR_getdents64, fd_, buf_, sizeof(buf_)); + if (r == 0) + return false; + if (r == -1) { + DPLOG(FATAL) << "getdents64 returned an error: " << errno; + return false; + } + size_ = r; + offset_ = 0; + return true; + } + + const char* name() const { + if (!size_) + return NULL; + + const linux_dirent* dirent = + reinterpret_cast<const linux_dirent*>(&buf_[offset_]); + return dirent->d_name; + } + + int fd() const { + return fd_; + } + + static bool IsFallback() { + return false; + } + + //**************** extra convenience methods + + // returns true if the current enumerated object is a file + bool IsFile() const { + if (!IsValid()) return false; + const linux_dirent* dirent = + reinterpret_cast<const linux_dirent*>(&buf_[offset_]); + return (dirent->d_type == DT_REG); + } + + private: + const int fd_; + unsigned char buf_[512]; + size_t offset_, size_; + + DISALLOW_COPY_AND_ASSIGN(DirReaderLinux); +}; + +} // namespace base + +#endif // BASE_FILES_DIR_READER_LINUX_H_
diff --git a/src/base/files/dir_reader_posix.h b/src/base/files/dir_reader_posix.h new file mode 100644 index 0000000..6a20ced --- /dev/null +++ b/src/base/files/dir_reader_posix.h
@@ -0,0 +1,36 @@ +// 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_DIR_READER_POSIX_H_ +#define BASE_FILES_DIR_READER_POSIX_H_ + +#include "build/build_config.h" + +// This header provides a class, DirReaderPosix, which allows one to open and +// read from directories without allocating memory. For the interface, see +// the generic fallback in dir_reader_fallback.h. + +// Mac note: OS X has getdirentries, but it only works if we restrict Chrome to +// 32-bit inodes. There is a getdirentries64 syscall in 10.6, but it's not +// wrapped and the direct syscall interface is unstable. Using an unstable API +// seems worse than falling back to enumerating all file descriptors so we will +// probably never implement this on the Mac. + +#if defined(OS_LINUX) +#include "base/files/dir_reader_linux.h" +#else +#include "base/files/dir_reader_fallback.h" +#endif + +namespace base { + +#if defined(OS_LINUX) +typedef DirReaderLinux DirReaderPosix; +#else +typedef DirReaderFallback DirReaderPosix; +#endif + +} // namespace base + +#endif // BASE_FILES_DIR_READER_POSIX_H_
diff --git a/src/base/files/dir_reader_posix_unittest.cc b/src/base/files/dir_reader_posix_unittest.cc new file mode 100644 index 0000000..0685031 --- /dev/null +++ b/src/base/files/dir_reader_posix_unittest.cc
@@ -0,0 +1,92 @@ +// 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/dir_reader_posix.h" + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_ANDROID) +#include "base/os_compat_android.h" +#endif + +namespace base { + +TEST(DirReaderPosixUnittest, Read) { + static const unsigned kNumFiles = 100; + + if (DirReaderPosix::IsFallback()) + return; + + char kDirTemplate[] = "/tmp/org.chromium.dir-reader-posix-XXXXXX"; + const char* dir = mkdtemp(kDirTemplate); + ASSERT_TRUE(dir); + + const int prev_wd = open(".", O_RDONLY | O_DIRECTORY); + DCHECK_GE(prev_wd, 0); + + PCHECK(chdir(dir) == 0); + + for (unsigned i = 0; i < kNumFiles; i++) { + char buf[16]; + snprintf(buf, sizeof(buf), "%d", i); + const int fd = open(buf, O_CREAT | O_RDONLY | O_EXCL, 0600); + PCHECK(fd >= 0); + PCHECK(close(fd) == 0); + } + + std::set<unsigned> seen; + + DirReaderPosix reader(dir); + EXPECT_TRUE(reader.IsValid()); + + if (!reader.IsValid()) + return; + + bool seen_dot = false, seen_dotdot = false; + + for (; reader.Next(); ) { + if (strcmp(reader.name(), ".") == 0) { + seen_dot = true; + continue; + } + if (strcmp(reader.name(), "..") == 0) { + seen_dotdot = true; + continue; + } + + SCOPED_TRACE(testing::Message() << "reader.name(): " << reader.name()); + + char *endptr; + const unsigned long value = strtoul(reader.name(), &endptr, 10); + + EXPECT_FALSE(*endptr); + EXPECT_LT(value, kNumFiles); + EXPECT_EQ(0u, seen.count(value)); + seen.insert(value); + } + + for (unsigned i = 0; i < kNumFiles; i++) { + char buf[16]; + snprintf(buf, sizeof(buf), "%d", i); + PCHECK(unlink(buf) == 0); + } + + PCHECK(rmdir(dir) == 0); + + PCHECK(fchdir(prev_wd) == 0); + PCHECK(close(prev_wd) == 0); + + EXPECT_TRUE(seen_dot); + EXPECT_TRUE(seen_dotdot); + EXPECT_EQ(kNumFiles, seen.size()); +} + +} // namespace base
diff --git a/src/base/files/file_path_watcher.cc b/src/base/files/file_path_watcher.cc new file mode 100644 index 0000000..bc8db37 --- /dev/null +++ b/src/base/files/file_path_watcher.cc
@@ -0,0 +1,73 @@ +// 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. + +// Cross platform methods for FilePathWatcher. See the various platform +// specific implementation files, too. + +#include "base/files/file_path_watcher.h" + +#include "base/logging.h" +#include "base/message_loop.h" + +namespace base { +namespace files { + +namespace { + +// A delegate implementation for the callback interface. +class FilePathWatcherDelegate : public base::files::FilePathWatcher::Delegate { + public: + explicit FilePathWatcherDelegate(const FilePathWatcher::Callback& callback) + : callback_(callback) {} + + // FilePathWatcher::Delegate implementation. + virtual void OnFilePathChanged(const FilePath& path) OVERRIDE { + callback_.Run(path, false); + } + + virtual void OnFilePathError(const FilePath& path) OVERRIDE { + callback_.Run(path, true); + } + + private: + virtual ~FilePathWatcherDelegate() {} + + FilePathWatcher::Callback callback_; + + DISALLOW_COPY_AND_ASSIGN(FilePathWatcherDelegate); +}; + +} // namespace + +FilePathWatcher::~FilePathWatcher() { + impl_->Cancel(); +} + +// static +void FilePathWatcher::CancelWatch( + const scoped_refptr<PlatformDelegate>& delegate) { + delegate->CancelOnMessageLoopThread(); +} + +bool FilePathWatcher::Watch(const FilePath& path, Delegate* delegate) { + DCHECK(path.IsAbsolute()); + return impl_->Watch(path, false, delegate); +} + +FilePathWatcher::PlatformDelegate::PlatformDelegate(): cancelled_(false) { +} + +FilePathWatcher::PlatformDelegate::~PlatformDelegate() { + DCHECK(is_cancelled()); +} + +bool FilePathWatcher::Watch(const FilePath& path, + bool recursive, + const Callback& callback) { + DCHECK(path.IsAbsolute()); + return impl_->Watch(path, recursive, new FilePathWatcherDelegate(callback)); +} + +} // namespace files +} // namespace base
diff --git a/src/base/files/file_path_watcher.h b/src/base/files/file_path_watcher.h new file mode 100644 index 0000000..94a3f9a --- /dev/null +++ b/src/base/files/file_path_watcher.h
@@ -0,0 +1,140 @@ +// 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. + +// This module provides a way to monitor a file or directory for changes. + +#ifndef BASE_FILES_FILE_PATH_WATCHER_H_ +#define BASE_FILES_FILE_PATH_WATCHER_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop_proxy.h" + +namespace base { +namespace files { + +// This class lets you register interest in changes on a FilePath. +// The delegate will get called whenever the file or directory referenced by the +// FilePath is changed, including created or deleted. Due to limitations in the +// underlying OS APIs, FilePathWatcher has slightly different semantics on OS X +// than on Windows or Linux. FilePathWatcher on Linux and Windows will detect +// modifications to files in a watched directory. FilePathWatcher on Mac will +// detect the creation and deletion of files in a watched directory, but will +// not detect modifications to those files. See file_path_watcher_kqueue.cc for +// details. +class BASE_EXPORT FilePathWatcher { + public: + // Callback type for Watch(). |path| points to the file that was updated, + // and |error| is true if the platform specific code detected an error. In + // that case, the callback won't be invoked again. + typedef base::Callback<void(const FilePath& path, bool error)> Callback; + + // Declares the callback client code implements to receive notifications. Note + // that implementations of this interface should not keep a reference to the + // corresponding FileWatcher object to prevent a reference cycle. + // + // Deprecated: see comment on Watch() below. + class Delegate : public base::RefCountedThreadSafe<Delegate> { + public: + virtual void OnFilePathChanged(const FilePath& path) = 0; + // Called when platform specific code detected an error. The watcher will + // not call OnFilePathChanged for future changes. + virtual void OnFilePathError(const FilePath& path) {} + + protected: + friend class base::RefCountedThreadSafe<Delegate>; + virtual ~Delegate() {} + }; + + // Used internally to encapsulate different members on different platforms. + // TODO(jhawkins): Move this into its own file. Also fix the confusing naming + // wrt Delegate vs PlatformDelegate. + class PlatformDelegate : public base::RefCountedThreadSafe<PlatformDelegate> { + public: + PlatformDelegate(); + + // Start watching for the given |path| and notify |delegate| about changes. + virtual bool Watch(const FilePath& path, + bool recursive, + Delegate* delegate) WARN_UNUSED_RESULT = 0; + + // Stop watching. This is called from FilePathWatcher's dtor in order to + // allow to shut down properly while the object is still alive. + // It can be called from any thread. + virtual void Cancel() = 0; + + protected: + friend class base::RefCountedThreadSafe<PlatformDelegate>; + friend class FilePathWatcher; + + virtual ~PlatformDelegate(); + + // Stop watching. This is only called on the thread of the appropriate + // message loop. Since it can also be called more than once, it should + // check |is_cancelled()| to avoid duplicate work. + virtual void CancelOnMessageLoopThread() = 0; + + scoped_refptr<base::MessageLoopProxy> message_loop() const { + return message_loop_; + } + + void set_message_loop(base::MessageLoopProxy* loop) { + message_loop_ = loop; + } + + // Must be called before the PlatformDelegate is deleted. + void set_cancelled() { + cancelled_ = true; + } + + bool is_cancelled() const { + return cancelled_; + } + + private: + scoped_refptr<base::MessageLoopProxy> message_loop_; + bool cancelled_; + }; + + FilePathWatcher(); + virtual ~FilePathWatcher(); + + // A callback that always cleans up the PlatformDelegate, either when executed + // or when deleted without having been executed at all, as can happen during + // shutdown. + static void CancelWatch(const scoped_refptr<PlatformDelegate>& delegate); + + // Register interest in any changes on |path|. OnPathChanged will be called + // back for each change. Returns true on success. + // OnFilePathChanged() will be called on the same thread as Watch() is called, + // which should have a MessageLoop of TYPE_IO. + // + // Deprecated: new code should use the callback interface, declared below. + // The FilePathWatcher::Delegate interface will be removed once all client + // code has been updated. http://crbug.com/130980 + virtual bool Watch(const FilePath& path, Delegate* delegate) + WARN_UNUSED_RESULT; + + // Invokes |callback| whenever updates to |path| are detected. This should be + // called at most once, and from a MessageLoop of TYPE_IO. Set |recursive| to + // true, to watch |path| and its children. The callback will be invoked on + // the same loop. Returns true on success. + // + // NOTE: Recursive watch is not supported on all platforms and file systems. + // Watch() will return false in the case of failure. + bool Watch(const FilePath& path, bool recursive, const Callback& callback); + + private: + scoped_refptr<PlatformDelegate> impl_; + + DISALLOW_COPY_AND_ASSIGN(FilePathWatcher); +}; + +} // namespace files +} // namespace base + +#endif // BASE_FILES_FILE_PATH_WATCHER_H_
diff --git a/src/base/files/file_path_watcher_browsertest.cc b/src/base/files/file_path_watcher_browsertest.cc new file mode 100644 index 0000000..9a7c6c4 --- /dev/null +++ b/src/base/files/file_path_watcher_browsertest.cc
@@ -0,0 +1,916 @@ +// 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_path_watcher.h" + +#if defined(OS_WIN) +#include <windows.h> +#include <aclapi.h> +#elif defined(OS_POSIX) +#include <sys/stat.h> +#endif + +#include <set> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/stl_util.h" +#include "base/stringprintf.h" +#include "base/synchronization/waitable_event.h" +#include "base/test/test_file_util.h" +#include "base/test/test_timeouts.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace files { + +namespace { + +class TestDelegate; + +// Aggregates notifications from the test delegates and breaks the message loop +// the test thread is waiting on once they all came in. +class NotificationCollector + : public base::RefCountedThreadSafe<NotificationCollector> { + public: + NotificationCollector() + : loop_(base::MessageLoopProxy::current()) {} + + // Called from the file thread by the delegates. + void OnChange(TestDelegate* delegate) { + loop_->PostTask(FROM_HERE, + base::Bind(&NotificationCollector::RecordChange, this, + base::Unretained(delegate))); + } + + void Register(TestDelegate* delegate) { + delegates_.insert(delegate); + } + + void Reset() { + signaled_.clear(); + } + + bool Success() { + return signaled_ == delegates_; + } + + private: + friend class base::RefCountedThreadSafe<NotificationCollector>; + ~NotificationCollector() {} + + void RecordChange(TestDelegate* delegate) { + // Warning: |delegate| is Unretained. Do not dereference. + ASSERT_TRUE(loop_->BelongsToCurrentThread()); + ASSERT_TRUE(delegates_.count(delegate)); + signaled_.insert(delegate); + + // Check whether all delegates have been signaled. + if (signaled_ == delegates_) + loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure()); + } + + // Set of registered delegates. + std::set<TestDelegate*> delegates_; + + // Set of signaled delegates. + std::set<TestDelegate*> signaled_; + + // The loop we should break after all delegates signaled. + scoped_refptr<base::MessageLoopProxy> loop_; +}; + +class TestDelegateBase : public SupportsWeakPtr<TestDelegateBase> { + public: + TestDelegateBase() {} + virtual ~TestDelegateBase() {} + + virtual void OnFileChanged(const FilePath& path, bool error) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(TestDelegateBase); +}; + +// A mock class for testing. Gmock is not appropriate because it is not +// thread-safe for setting expectations. Thus the test code cannot safely +// reset expectations while the file watcher is running. +// Instead, TestDelegate gets the notifications from FilePathWatcher and uses +// NotificationCollector to aggregate the results. +class TestDelegate : public TestDelegateBase { + public: + explicit TestDelegate(NotificationCollector* collector) + : collector_(collector) { + collector_->Register(this); + } + ~TestDelegate() {} + + virtual void OnFileChanged(const FilePath& path, bool error) OVERRIDE { + if (error) + ADD_FAILURE() << "Error " << path.value(); + else + collector_->OnChange(this); + } + + private: + scoped_refptr<NotificationCollector> collector_; + + DISALLOW_COPY_AND_ASSIGN(TestDelegate); +}; + +void SetupWatchCallback(const FilePath& target, + FilePathWatcher* watcher, + TestDelegateBase* delegate, + bool recursive_watch, + bool* result, + base::WaitableEvent* completion) { + *result = watcher->Watch(target, recursive_watch, + base::Bind(&TestDelegateBase::OnFileChanged, + delegate->AsWeakPtr())); + 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->QuitClosure()); +} + +class FilePathWatcherTest : public testing::Test { + public: + FilePathWatcherTest() + : file_thread_("FilePathWatcherTest") {} + + virtual ~FilePathWatcherTest() {} + + protected: + virtual void SetUp() OVERRIDE { + // Create a separate file thread in order to test proper thread usage. + base::Thread::Options options(MessageLoop::TYPE_IO, 0); + ASSERT_TRUE(file_thread_.StartWithOptions(options)); + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + collector_ = new NotificationCollector(); + } + + virtual void TearDown() OVERRIDE { + loop_.RunUntilIdle(); + } + + void DeleteDelegateOnFileThread(TestDelegate* delegate) { + file_thread_.message_loop_proxy()->DeleteSoon(FROM_HERE, delegate); + } + + FilePath test_file() { + return temp_dir_.path().AppendASCII("FilePathWatcherTest"); + } + + FilePath test_link() { + return temp_dir_.path().AppendASCII("FilePathWatcherTest.lnk"); + } + + // Write |content| to |file|. Returns true on success. + bool WriteFile(const FilePath& file, const std::string& content) { + int write_size = file_util::WriteFile(file, content.c_str(), + content.length()); + return write_size == static_cast<int>(content.length()); + } + + bool SetupWatch(const FilePath& target, + FilePathWatcher* watcher, + TestDelegateBase* delegate, + bool recursive_watch) WARN_UNUSED_RESULT; + + bool WaitForEvents() WARN_UNUSED_RESULT { + collector_->Reset(); + loop_.Run(); + return collector_->Success(); + } + + NotificationCollector* collector() { return collector_.get(); } + + MessageLoop loop_; + base::Thread file_thread_; + ScopedTempDir temp_dir_; + scoped_refptr<NotificationCollector> collector_; + + DISALLOW_COPY_AND_ASSIGN(FilePathWatcherTest); +}; + +bool FilePathWatcherTest::SetupWatch(const FilePath& target, + FilePathWatcher* watcher, + TestDelegateBase* delegate, + bool recursive_watch) { + base::WaitableEvent completion(false, false); + bool result; + file_thread_.message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(SetupWatchCallback, + target, watcher, delegate, recursive_watch, &result, + &completion)); + completion.Wait(); + return result; +} + +// Basic test: Create the file and verify that we notice. +TEST_F(FilePathWatcherTest, NewFile) { + FilePathWatcher watcher; + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); + + ASSERT_TRUE(WriteFile(test_file(), "content")); + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +// Verify that modifying the file is caught. +TEST_F(FilePathWatcherTest, ModifiedFile) { + ASSERT_TRUE(WriteFile(test_file(), "content")); + + FilePathWatcher watcher; + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); + + // Now make sure we get notified if the file is modified. + ASSERT_TRUE(WriteFile(test_file(), "new content")); + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +// Verify that moving the file into place is caught. +TEST_F(FilePathWatcherTest, MovedFile) { + FilePath source_file(temp_dir_.path().AppendASCII("source")); + ASSERT_TRUE(WriteFile(source_file, "content")); + + FilePathWatcher watcher; + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); + + // Now make sure we get notified if the file is modified. + ASSERT_TRUE(file_util::Move(source_file, test_file())); + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +TEST_F(FilePathWatcherTest, DeletedFile) { + ASSERT_TRUE(WriteFile(test_file(), "content")); + + FilePathWatcher watcher; + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); + + // Now make sure we get notified if the file is deleted. + file_util::Delete(test_file(), false); + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +// Used by the DeleteDuringNotify test below. +// Deletes the FilePathWatcher when it's notified. +class Deleter : public TestDelegateBase { + public: + Deleter(FilePathWatcher* watcher, MessageLoop* loop) + : watcher_(watcher), + loop_(loop) { + } + ~Deleter() {} + + virtual void OnFileChanged(const FilePath&, bool) OVERRIDE { + watcher_.reset(); + loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure()); + } + + FilePathWatcher* watcher() const { return watcher_.get(); } + + private: + scoped_ptr<FilePathWatcher> watcher_; + MessageLoop* loop_; + + DISALLOW_COPY_AND_ASSIGN(Deleter); +}; + +// Verify that deleting a watcher during the callback doesn't crash. +TEST_F(FilePathWatcherTest, DeleteDuringNotify) { + FilePathWatcher* watcher = new FilePathWatcher; + // Takes ownership of watcher. + scoped_ptr<Deleter> deleter(new Deleter(watcher, &loop_)); + ASSERT_TRUE(SetupWatch(test_file(), watcher, deleter.get(), false)); + + ASSERT_TRUE(WriteFile(test_file(), "content")); + ASSERT_TRUE(WaitForEvents()); + + // We win if we haven't crashed yet. + // Might as well double-check it got deleted, too. + ASSERT_TRUE(deleter->watcher() == NULL); +} + +// Verify that deleting the watcher works even if there is a pending +// notification. +// Flaky on MacOS. http://crbug.com/85930 +#if defined(OS_MACOSX) +#define MAYBE_DestroyWithPendingNotification \ + DISABLED_DestroyWithPendingNotification +#else +#define MAYBE_DestroyWithPendingNotification DestroyWithPendingNotification +#endif +TEST_F(FilePathWatcherTest, MAYBE_DestroyWithPendingNotification) { + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + FilePathWatcher* watcher = new FilePathWatcher; + ASSERT_TRUE(SetupWatch(test_file(), watcher, delegate.get(), false)); + ASSERT_TRUE(WriteFile(test_file(), "content")); + file_thread_.message_loop_proxy()->DeleteSoon(FROM_HERE, watcher); + DeleteDelegateOnFileThread(delegate.release()); +} + +TEST_F(FilePathWatcherTest, MultipleWatchersSingleFile) { + FilePathWatcher watcher1, watcher2; + scoped_ptr<TestDelegate> delegate1(new TestDelegate(collector())); + scoped_ptr<TestDelegate> delegate2(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(test_file(), &watcher1, delegate1.get(), false)); + ASSERT_TRUE(SetupWatch(test_file(), &watcher2, delegate2.get(), false)); + + ASSERT_TRUE(WriteFile(test_file(), "content")); + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate1.release()); + DeleteDelegateOnFileThread(delegate2.release()); +} + +// Verify that watching a file whose parent directory doesn't exist yet works if +// the directory and file are created eventually. +TEST_F(FilePathWatcherTest, NonExistentDirectory) { + FilePathWatcher watcher; + FilePath dir(temp_dir_.path().AppendASCII("dir")); + FilePath file(dir.AppendASCII("file")); + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false)); + + ASSERT_TRUE(file_util::CreateDirectory(dir)); + + ASSERT_TRUE(WriteFile(file, "content")); + + VLOG(1) << "Waiting for file creation"; + ASSERT_TRUE(WaitForEvents()); + + ASSERT_TRUE(WriteFile(file, "content v2")); + VLOG(1) << "Waiting for file change"; + ASSERT_TRUE(WaitForEvents()); + + ASSERT_TRUE(file_util::Delete(file, false)); + VLOG(1) << "Waiting for file deletion"; + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +// Exercises watch reconfiguration for the case that directories on the path +// are rapidly created. +TEST_F(FilePathWatcherTest, DirectoryChain) { + FilePath path(temp_dir_.path()); + std::vector<std::string> dir_names; + for (int i = 0; i < 20; i++) { + std::string dir(base::StringPrintf("d%d", i)); + dir_names.push_back(dir); + path = path.AppendASCII(dir); + } + + FilePathWatcher watcher; + FilePath file(path.AppendASCII("file")); + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false)); + + FilePath sub_path(temp_dir_.path()); + 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)); + } + VLOG(1) << "Create File"; + ASSERT_TRUE(WriteFile(file, "content")); + VLOG(1) << "Waiting for file creation"; + ASSERT_TRUE(WaitForEvents()); + + ASSERT_TRUE(WriteFile(file, "content v2")); + VLOG(1) << "Waiting for file modification"; + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +#if defined(OS_MACOSX) +// http://crbug.com/85930 +#define DisappearingDirectory DISABLED_DisappearingDirectory +#endif +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(WriteFile(file, "content")); + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false)); + + ASSERT_TRUE(file_util::Delete(dir, true)); + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +// Tests that a file that is deleted and reappears is tracked correctly. +TEST_F(FilePathWatcherTest, DeleteAndRecreate) { + ASSERT_TRUE(WriteFile(test_file(), "content")); + FilePathWatcher watcher; + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); + + ASSERT_TRUE(file_util::Delete(test_file(), false)); + VLOG(1) << "Waiting for file deletion"; + ASSERT_TRUE(WaitForEvents()); + + ASSERT_TRUE(WriteFile(test_file(), "content")); + VLOG(1) << "Waiting for file creation"; + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +TEST_F(FilePathWatcherTest, WatchDirectory) { + FilePathWatcher watcher; + FilePath dir(temp_dir_.path().AppendASCII("dir")); + FilePath file1(dir.AppendASCII("file1")); + FilePath file2(dir.AppendASCII("file2")); + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(dir, &watcher, delegate.get(), false)); + + ASSERT_TRUE(file_util::CreateDirectory(dir)); + VLOG(1) << "Waiting for directory creation"; + ASSERT_TRUE(WaitForEvents()); + + ASSERT_TRUE(WriteFile(file1, "content")); + VLOG(1) << "Waiting for file1 creation"; + ASSERT_TRUE(WaitForEvents()); + +#if !defined(OS_MACOSX) + // Mac implementation does not detect files modified in a directory. + ASSERT_TRUE(WriteFile(file1, "content v2")); + VLOG(1) << "Waiting for file1 modification"; + ASSERT_TRUE(WaitForEvents()); +#endif // !OS_MACOSX + + ASSERT_TRUE(file_util::Delete(file1, false)); + VLOG(1) << "Waiting for file1 deletion"; + ASSERT_TRUE(WaitForEvents()); + + ASSERT_TRUE(WriteFile(file2, "content")); + VLOG(1) << "Waiting for file2 creation"; + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +TEST_F(FilePathWatcherTest, MoveParent) { + FilePathWatcher file_watcher; + FilePathWatcher subdir_watcher; + FilePath dir(temp_dir_.path().AppendASCII("dir")); + FilePath dest(temp_dir_.path().AppendASCII("dest")); + FilePath subdir(dir.AppendASCII("subdir")); + FilePath file(subdir.AppendASCII("file")); + scoped_ptr<TestDelegate> file_delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(file, &file_watcher, file_delegate.get(), false)); + scoped_ptr<TestDelegate> subdir_delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(subdir, &subdir_watcher, subdir_delegate.get(), + false)); + + // Setup a directory hierarchy. + ASSERT_TRUE(file_util::CreateDirectory(subdir)); + ASSERT_TRUE(WriteFile(file, "content")); + VLOG(1) << "Waiting for file creation"; + ASSERT_TRUE(WaitForEvents()); + + // Move the parent directory. + file_util::Move(dir, dest); + VLOG(1) << "Waiting for directory move"; + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(file_delegate.release()); + DeleteDelegateOnFileThread(subdir_delegate.release()); +} + +#if defined(OS_WIN) +TEST_F(FilePathWatcherTest, RecursiveWatch) { + FilePathWatcher watcher; + FilePath dir(temp_dir_.path().AppendASCII("dir")); + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(dir, &watcher, delegate.get(), true)); + + // Main directory("dir") creation. + ASSERT_TRUE(file_util::CreateDirectory(dir)); + ASSERT_TRUE(WaitForEvents()); + + // Create "$dir/file1". + FilePath file1(dir.AppendASCII("file1")); + ASSERT_TRUE(WriteFile(file1, "content")); + ASSERT_TRUE(WaitForEvents()); + + // Create "$dir/subdir". + FilePath subdir(dir.AppendASCII("subdir")); + ASSERT_TRUE(file_util::CreateDirectory(subdir)); + ASSERT_TRUE(WaitForEvents()); + + // Create "$dir/subdir/subdir_file1". + FilePath subdir_file1(subdir.AppendASCII("subdir_file1")); + ASSERT_TRUE(WriteFile(subdir_file1, "content")); + ASSERT_TRUE(WaitForEvents()); + + // 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(WaitForEvents()); + + // Create "$dir/subdir/subdir_child_dir/child_dir_file1". + FilePath child_dir_file1(subdir_child_dir.AppendASCII("child_dir_file1")); + ASSERT_TRUE(WriteFile(child_dir_file1, "content v2")); + ASSERT_TRUE(WaitForEvents()); + + // Write into "$dir/subdir/subdir_child_dir/child_dir_file1". + ASSERT_TRUE(WriteFile(child_dir_file1, "content")); + ASSERT_TRUE(WaitForEvents()); + + // Modify "$dir/subdir/subdir_child_dir/child_dir_file1" attributes. + ASSERT_TRUE(file_util::MakeFileUnreadable(child_dir_file1)); + ASSERT_TRUE(WaitForEvents()); + + // Delete "$dir/subdir/subdir_file1". + ASSERT_TRUE(file_util::Delete(subdir_file1, false)); + ASSERT_TRUE(WaitForEvents()); + + // Delete "$dir/subdir/subdir_child_dir/child_dir_file1". + ASSERT_TRUE(file_util::Delete(child_dir_file1, false)); + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} +#else +TEST_F(FilePathWatcherTest, RecursiveWatch) { + FilePathWatcher watcher; + FilePath dir(temp_dir_.path().AppendASCII("dir")); + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + // Non-Windows implementaion does not support recursive watching. + ASSERT_FALSE(SetupWatch(dir, &watcher, delegate.get(), true)); + DeleteDelegateOnFileThread(delegate.release()); +} +#endif + +TEST_F(FilePathWatcherTest, MoveChild) { + FilePathWatcher file_watcher; + FilePathWatcher subdir_watcher; + FilePath source_dir(temp_dir_.path().AppendASCII("source")); + FilePath source_subdir(source_dir.AppendASCII("subdir")); + FilePath source_file(source_subdir.AppendASCII("file")); + FilePath dest_dir(temp_dir_.path().AppendASCII("dest")); + FilePath dest_subdir(dest_dir.AppendASCII("subdir")); + FilePath dest_file(dest_subdir.AppendASCII("file")); + + // Setup a directory hierarchy. + ASSERT_TRUE(file_util::CreateDirectory(source_subdir)); + ASSERT_TRUE(WriteFile(source_file, "content")); + + scoped_ptr<TestDelegate> file_delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(dest_file, &file_watcher, file_delegate.get(), false)); + scoped_ptr<TestDelegate> subdir_delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(dest_subdir, &subdir_watcher, subdir_delegate.get(), + false)); + + // Move the directory into place, s.t. the watched file appears. + ASSERT_TRUE(file_util::Move(source_dir, dest_dir)); + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(file_delegate.release()); + DeleteDelegateOnFileThread(subdir_delegate.release()); +} + +#if !defined(OS_LINUX) +// Linux implementation of FilePathWatcher doesn't catch attribute changes. +// http://crbug.com/78043 + +// Verify that changing attributes on a file is caught +TEST_F(FilePathWatcherTest, FileAttributesChanged) { + ASSERT_TRUE(WriteFile(test_file(), "content")); + FilePathWatcher watcher; + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(test_file(), &watcher, delegate.get(), false)); + + // Now make sure we get notified if the file is modified. + ASSERT_TRUE(file_util::MakeFileUnreadable(test_file())); + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +#endif // !OS_LINUX + +#if defined(OS_LINUX) + +// Verify that creating a symlink is caught. +TEST_F(FilePathWatcherTest, CreateLink) { + FilePathWatcher watcher; + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + // Note that we are watching the symlink + ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); + + // 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(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +// Verify that deleting a symlink is caught. +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())); + FilePathWatcher watcher; + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); + + // Now make sure we get notified if the link is deleted. + ASSERT_TRUE(file_util::Delete(test_link(), false)); + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +// Verify that modifying a target file that a link is pointing to +// 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())); + FilePathWatcher watcher; + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + // Note that we are watching the symlink. + ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); + + // Now make sure we get notified if the file is modified. + ASSERT_TRUE(WriteFile(test_file(), "new content")); + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +// 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())); + FilePathWatcher watcher; + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + // Note that we are watching the symlink. + ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); + + // Now make sure we get notified if the target file is created. + ASSERT_TRUE(WriteFile(test_file(), "content")); + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +// Verify that deleting a target file that a link is pointing to +// 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())); + FilePathWatcher watcher; + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + // Note that we are watching the symlink. + ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); + + // Now make sure we get notified if the target file is deleted. + ASSERT_TRUE(file_util::Delete(test_file(), false)); + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +// Verify that watching a file whose parent directory is a link that +// doesn't exist yet works if the symlink is created eventually. +TEST_F(FilePathWatcherTest, LinkedDirectoryPart1) { + FilePathWatcher watcher; + FilePath dir(temp_dir_.path().AppendASCII("dir")); + FilePath link_dir(temp_dir_.path().AppendASCII("dir.lnk")); + FilePath file(dir.AppendASCII("file")); + 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(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)); + VLOG(1) << "Waiting for link creation"; + ASSERT_TRUE(WaitForEvents()); + + ASSERT_TRUE(WriteFile(file, "content v2")); + VLOG(1) << "Waiting for file change"; + ASSERT_TRUE(WaitForEvents()); + + ASSERT_TRUE(file_util::Delete(file, false)); + VLOG(1) << "Waiting for file deletion"; + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +// Verify that watching a file whose parent directory is a +// dangling symlink works if the directory is created eventually. +TEST_F(FilePathWatcherTest, LinkedDirectoryPart2) { + FilePathWatcher watcher; + FilePath dir(temp_dir_.path().AppendASCII("dir")); + FilePath link_dir(temp_dir_.path().AppendASCII("dir.lnk")); + FilePath file(dir.AppendASCII("file")); + FilePath linkfile(link_dir.AppendASCII("file")); + 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)); + // Note that we are watching dir.lnk/file. + ASSERT_TRUE(SetupWatch(linkfile, &watcher, delegate.get(), false)); + + ASSERT_TRUE(file_util::CreateDirectory(dir)); + ASSERT_TRUE(WriteFile(file, "content")); + VLOG(1) << "Waiting for dir/file creation"; + ASSERT_TRUE(WaitForEvents()); + + ASSERT_TRUE(WriteFile(file, "content v2")); + VLOG(1) << "Waiting for file change"; + ASSERT_TRUE(WaitForEvents()); + + ASSERT_TRUE(file_util::Delete(file, false)); + VLOG(1) << "Waiting for file deletion"; + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +// Verify that watching a file with a symlink on the path +// to the file works. +TEST_F(FilePathWatcherTest, LinkedDirectoryPart3) { + FilePathWatcher watcher; + FilePath dir(temp_dir_.path().AppendASCII("dir")); + FilePath link_dir(temp_dir_.path().AppendASCII("dir.lnk")); + 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)); + // Note that we are watching dir.lnk/file but the file doesn't exist yet. + ASSERT_TRUE(SetupWatch(linkfile, &watcher, delegate.get(), false)); + + ASSERT_TRUE(WriteFile(file, "content")); + VLOG(1) << "Waiting for file creation"; + ASSERT_TRUE(WaitForEvents()); + + ASSERT_TRUE(WriteFile(file, "content v2")); + VLOG(1) << "Waiting for file change"; + ASSERT_TRUE(WaitForEvents()); + + ASSERT_TRUE(file_util::Delete(file, false)); + VLOG(1) << "Waiting for file deletion"; + ASSERT_TRUE(WaitForEvents()); + DeleteDelegateOnFileThread(delegate.release()); +} + +#endif // OS_LINUX + +enum Permission { + Read, + Write, + Execute +}; + +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) + return false; + + mode_t mode = 0; + switch (perm) { + case Read: + mode = S_IRUSR | S_IRGRP | S_IROTH; + break; + case Write: + mode = S_IWUSR | S_IWGRP | S_IWOTH; + break; + case Execute: + mode = S_IXUSR | S_IXGRP | S_IXOTH; + break; + default: + ADD_FAILURE() << "unknown perm " << perm; + return false; + } + if (allow) { + stat_buf.st_mode |= mode; + } else { + 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 +} + +#if defined(OS_MACOSX) +// Linux implementation of FilePathWatcher doesn't catch attribute changes. +// http://crbug.com/78043 +// Windows implementation of FilePathWatcher catches attribute changes that +// don't affect the path being watched. +// http://crbug.com/78045 + +// Verify that changing attributes on a directory works. +TEST_F(FilePathWatcherTest, DirAttributesChanged) { + FilePath test_dir1(temp_dir_.path().AppendASCII("DirAttributesChangedDir1")); + 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(WriteFile(test_file, "content")); + + FilePathWatcher watcher; + scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); + ASSERT_TRUE(SetupWatch(test_file, &watcher, delegate.get(), false)); + + // We should not get notified in this case as it hasn't affected our ability + // to access the file. + ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, false)); + loop_.PostDelayedTask(FROM_HERE, + MessageLoop::QuitClosure(), + TestTimeouts::tiny_timeout()); + ASSERT_FALSE(WaitForEvents()); + ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, true)); + + // We should get notified in this case because filepathwatcher can no + // longer access the file + ASSERT_TRUE(ChangeFilePermissions(test_dir1, Execute, false)); + ASSERT_TRUE(WaitForEvents()); + ASSERT_TRUE(ChangeFilePermissions(test_dir1, Execute, true)); + DeleteDelegateOnFileThread(delegate.release()); +} + +#endif // OS_MACOSX +} // namespace + +} // namespace files +} // namespace base
diff --git a/src/base/files/file_path_watcher_kqueue.cc b/src/base/files/file_path_watcher_kqueue.cc new file mode 100644 index 0000000..aebe15a --- /dev/null +++ b/src/base/files/file_path_watcher_kqueue.cc
@@ -0,0 +1,513 @@ +// 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_path_watcher.h" + +#include <fcntl.h> +#include <sys/event.h> +#include <sys/param.h> + +#include <vector> + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/stringprintf.h" + +// On some platforms these are not defined. +#if !defined(EV_RECEIPT) +#define EV_RECEIPT 0 +#endif +#if !defined(O_EVTONLY) +#define O_EVTONLY O_RDONLY +#endif + +namespace base { +namespace files { + +namespace { + +// Mac-specific file watcher implementation based on kqueue. +// Originally it was based on FSEvents so that the semantics were equivalent +// on Linux, OSX and Windows where it was able to detect: +// - file creation/deletion/modification in a watched directory +// - file creation/deletion/modification for a watched file +// - modifications to the paths to a watched object that would affect the +// object such as renaming/attibute changes etc. +// The FSEvents version did all of the above except handling attribute changes +// to path components. Unfortunately FSEvents appears to have an issue where the +// current implementation (Mac OS X 10.6.7) sometimes drops events and doesn't +// send notifications. See +// http://code.google.com/p/chromium/issues/detail?id=54822#c31 for source that +// will reproduce the problem. FSEvents also required having a CFRunLoop +// backing the thread that it was running on, that caused added complexity +// in the interfaces. +// The kqueue implementation will handle all of the items in the list above +// except for detecting modifications to files in a watched directory. It will +// detect the creation and deletion of files, just not the modification of +// files. It does however detect the attribute changes that the FSEvents impl +// would miss. +class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate, + public MessageLoopForIO::Watcher, + public MessageLoop::DestructionObserver { + public: + FilePathWatcherImpl() : kqueue_(-1) {} + + // MessageLoopForIO::Watcher overrides. + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + // MessageLoop::DestructionObserver overrides. + virtual void WillDestroyCurrentMessageLoop() OVERRIDE; + + // FilePathWatcher::PlatformDelegate overrides. + virtual bool Watch(const FilePath& path, + bool recursive, + FilePathWatcher::Delegate* delegate) OVERRIDE; + virtual void Cancel() OVERRIDE; + + protected: + virtual ~FilePathWatcherImpl() {} + + private: + class EventData { + public: + EventData(const FilePath& path, const FilePath::StringType& subdir) + : path_(path), subdir_(subdir) { } + FilePath path_; // Full path to this item. + FilePath::StringType subdir_; // Path to any sub item. + }; + typedef std::vector<struct kevent> EventVector; + + // Can only be called on |io_message_loop_|'s thread. + virtual void CancelOnMessageLoopThread() OVERRIDE; + + // Returns true if the kevent values are error free. + bool AreKeventValuesValid(struct kevent* kevents, int count); + + // Respond to a change of attributes of the path component represented by + // |event|. Sets |target_file_affected| to true if |target_| is affected. + // Sets |update_watches| to true if |events_| need to be updated. + void HandleAttributesChange(const EventVector::iterator& event, + bool* target_file_affected, + bool* update_watches); + + // Respond to a move of deletion of the path component represented by + // |event|. Sets |target_file_affected| to true if |target_| is affected. + // Sets |update_watches| to true if |events_| need to be updated. + void HandleDeleteOrMoveChange(const EventVector::iterator& event, + bool* target_file_affected, + bool* update_watches); + + // Respond to a creation of an item in the path component represented by + // |event|. Sets |target_file_affected| to true if |target_| is affected. + // Sets |update_watches| to true if |events_| need to be updated. + void HandleCreateItemChange(const EventVector::iterator& event, + bool* target_file_affected, + bool* update_watches); + + // Update |events_| with the current status of the system. + // Sets |target_file_affected| to true if |target_| is affected. + // Returns false if an error occurs. + bool UpdateWatches(bool* target_file_affected); + + // Fills |events| with one kevent per component in |path|. + // Returns the number of valid events created where a valid event is + // defined as one that has a ident (file descriptor) field != -1. + static int EventsForPath(FilePath path, EventVector *events); + + // Release a kevent generated by EventsForPath. + static void ReleaseEvent(struct kevent& event); + + // Returns a file descriptor that will not block the system from deleting + // the file it references. + static int FileDescriptorForPath(const FilePath& path); + + // Closes |*fd| and sets |*fd| to -1. + static void CloseFileDescriptor(int* fd); + + // Returns true if kevent has open file descriptor. + static bool IsKeventFileDescriptorOpen(const struct kevent& event) { + return event.ident != static_cast<uintptr_t>(-1); + } + + static EventData* EventDataForKevent(const struct kevent& event) { + return reinterpret_cast<EventData*>(event.udata); + } + + EventVector events_; + scoped_refptr<base::MessageLoopProxy> io_message_loop_; + MessageLoopForIO::FileDescriptorWatcher kqueue_watcher_; + scoped_refptr<FilePathWatcher::Delegate> delegate_; + FilePath target_; + int kqueue_; + + DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl); +}; + +void FilePathWatcherImpl::ReleaseEvent(struct kevent& event) { + CloseFileDescriptor(reinterpret_cast<int*>(&event.ident)); + EventData* entry = EventDataForKevent(event); + delete entry; + event.udata = NULL; +} + +int FilePathWatcherImpl::EventsForPath(FilePath path, EventVector* events) { + DCHECK(MessageLoopForIO::current()); + // Make sure that we are working with a clean slate. + DCHECK(events->empty()); + + std::vector<FilePath::StringType> components; + path.GetComponents(&components); + + if (components.size() < 1) { + return -1; + } + + int last_existing_entry = 0; + FilePath built_path; + bool path_still_exists = true; + for(std::vector<FilePath::StringType>::iterator i = components.begin(); + i != components.end(); ++i) { + if (i == components.begin()) { + built_path = FilePath(*i); + } else { + built_path = built_path.Append(*i); + } + int fd = -1; + if (path_still_exists) { + fd = FileDescriptorForPath(built_path); + if (fd == -1) { + path_still_exists = false; + } else { + ++last_existing_entry; + } + } + FilePath::StringType subdir = (i != (components.end() - 1)) ? *(i + 1) : ""; + EventData* data = new EventData(built_path, subdir); + struct kevent event; + EV_SET(&event, fd, EVFILT_VNODE, (EV_ADD | EV_CLEAR | EV_RECEIPT), + (NOTE_DELETE | NOTE_WRITE | NOTE_ATTRIB | + NOTE_RENAME | NOTE_REVOKE | NOTE_EXTEND), 0, data); + events->push_back(event); + } + return last_existing_entry; +} + +int FilePathWatcherImpl::FileDescriptorForPath(const FilePath& path) { + return HANDLE_EINTR(open(path.value().c_str(), O_EVTONLY)); +} + +void FilePathWatcherImpl::CloseFileDescriptor(int *fd) { + if (*fd == -1) { + return; + } + + if (HANDLE_EINTR(close(*fd)) != 0) { + DPLOG(ERROR) << "close"; + } + *fd = -1; +} + +bool FilePathWatcherImpl::AreKeventValuesValid(struct kevent* kevents, + int count) { + if (count < 0) { + DPLOG(ERROR) << "kevent"; + return false; + } + bool valid = true; + for (int i = 0; i < count; ++i) { + if (kevents[i].flags & EV_ERROR && kevents[i].data) { + // Find the kevent in |events_| that matches the kevent with the error. + EventVector::iterator event = events_.begin(); + for (; event != events_.end(); ++event) { + if (event->ident == kevents[i].ident) { + break; + } + } + std::string path_name; + if (event != events_.end()) { + EventData* event_data = EventDataForKevent(*event); + if (event_data != NULL) { + path_name = event_data->path_.value(); + } + } + if (path_name.empty()) { + path_name = base::StringPrintf( + "fd %d", *reinterpret_cast<int*>(&kevents[i].ident)); + } + DLOG(ERROR) << "Error: " << kevents[i].data << " for " << path_name; + valid = false; + } + } + return valid; +} + +void FilePathWatcherImpl::HandleAttributesChange( + const EventVector::iterator& event, + bool* target_file_affected, + bool* update_watches) { + EventVector::iterator next_event = event + 1; + EventData* next_event_data = EventDataForKevent(*next_event); + // Check to see if the next item in path is still accessible. + int have_access = FileDescriptorForPath(next_event_data->path_); + if (have_access == -1) { + *target_file_affected = true; + *update_watches = true; + EventVector::iterator local_event(event); + for (; local_event != events_.end(); ++local_event) { + // Close all nodes from the event down. This has the side effect of + // potentially rendering other events in |updates| invalid. + // There is no need to remove the events from |kqueue_| because this + // happens as a side effect of closing the file descriptor. + CloseFileDescriptor(reinterpret_cast<int*>(&local_event->ident)); + } + } else { + CloseFileDescriptor(&have_access); + } +} + +void FilePathWatcherImpl::HandleDeleteOrMoveChange( + const EventVector::iterator& event, + bool* target_file_affected, + bool* update_watches) { + *target_file_affected = true; + *update_watches = true; + EventVector::iterator local_event(event); + for (; local_event != events_.end(); ++local_event) { + // Close all nodes from the event down. This has the side effect of + // potentially rendering other events in |updates| invalid. + // There is no need to remove the events from |kqueue_| because this + // happens as a side effect of closing the file descriptor. + CloseFileDescriptor(reinterpret_cast<int*>(&local_event->ident)); + } +} + +void FilePathWatcherImpl::HandleCreateItemChange( + const EventVector::iterator& event, + bool* target_file_affected, + bool* update_watches) { + // Get the next item in the path. + EventVector::iterator next_event = event + 1; + EventData* next_event_data = EventDataForKevent(*next_event); + + // Check to see if it already has a valid file descriptor. + if (!IsKeventFileDescriptorOpen(*next_event)) { + // If not, attempt to open a file descriptor for it. + next_event->ident = FileDescriptorForPath(next_event_data->path_); + if (IsKeventFileDescriptorOpen(*next_event)) { + *update_watches = true; + if (next_event_data->subdir_.empty()) { + *target_file_affected = true; + } + } + } +} + +bool FilePathWatcherImpl::UpdateWatches(bool* target_file_affected) { + // Iterate over events adding kevents for items that exist to the kqueue. + // Then check to see if new components in the path have been created. + // Repeat until no new components in the path are detected. + // This is to get around races in directory creation in a watched path. + bool update_watches = true; + while (update_watches) { + size_t valid; + for (valid = 0; valid < events_.size(); ++valid) { + if (!IsKeventFileDescriptorOpen(events_[valid])) { + break; + } + } + if (valid == 0) { + // The root of the file path is inaccessible? + return false; + } + + EventVector updates(valid); + int count = HANDLE_EINTR(kevent(kqueue_, &events_[0], valid, &updates[0], + valid, NULL)); + if (!AreKeventValuesValid(&updates[0], count)) { + return false; + } + update_watches = false; + for (; valid < events_.size(); ++valid) { + EventData* event_data = EventDataForKevent(events_[valid]); + events_[valid].ident = FileDescriptorForPath(event_data->path_); + if (IsKeventFileDescriptorOpen(events_[valid])) { + update_watches = true; + if (event_data->subdir_.empty()) { + *target_file_affected = true; + } + } else { + break; + } + } + } + return true; +} + +void FilePathWatcherImpl::OnFileCanReadWithoutBlocking(int fd) { + DCHECK(MessageLoopForIO::current()); + DCHECK_EQ(fd, kqueue_); + DCHECK(events_.size()); + + // Request the file system update notifications that have occurred and return + // them in |updates|. |count| will contain the number of updates that have + // occurred. + EventVector updates(events_.size()); + struct timespec timeout = {0, 0}; + int count = HANDLE_EINTR(kevent(kqueue_, NULL, 0, &updates[0], updates.size(), + &timeout)); + + // Error values are stored within updates, so check to make sure that no + // errors occurred. + if (!AreKeventValuesValid(&updates[0], count)) { + delegate_->OnFilePathError(target_); + Cancel(); + return; + } + + bool update_watches = false; + bool send_notification = false; + + // Iterate through each of the updates and react to them. + for (int i = 0; i < count; ++i) { + // Find our kevent record that matches the update notification. + EventVector::iterator event = events_.begin(); + for (; event != events_.end(); ++event) { + if (!IsKeventFileDescriptorOpen(*event) || + event->ident == updates[i].ident) { + break; + } + } + if (!IsKeventFileDescriptorOpen(*event) || event == events_.end()) { + // The event may no longer exist in |events_| because another event + // modified |events_| in such a way to make it invalid. For example if + // the path is /foo/bar/bam and foo is deleted, NOTE_DELETE events for + // foo, bar and bam will be sent. If foo is processed first, then + // the file descriptors for bar and bam will already be closed and set + // to -1 before they get a chance to be processed. + continue; + } + + EventData* event_data = EventDataForKevent(*event); + + // If the subdir is empty, this is the last item on the path and is the + // target file. + bool target_file_affected = event_data->subdir_.empty(); + if ((updates[i].fflags & NOTE_ATTRIB) && !target_file_affected) { + HandleAttributesChange(event, &target_file_affected, &update_watches); + } + if (updates[i].fflags & (NOTE_DELETE | NOTE_REVOKE | NOTE_RENAME)) { + HandleDeleteOrMoveChange(event, &target_file_affected, &update_watches); + } + if ((updates[i].fflags & NOTE_WRITE) && !target_file_affected) { + HandleCreateItemChange(event, &target_file_affected, &update_watches); + } + send_notification |= target_file_affected; + } + + if (update_watches) { + if (!UpdateWatches(&send_notification)) { + delegate_->OnFilePathError(target_); + Cancel(); + } + } + + if (send_notification) { + delegate_->OnFilePathChanged(target_); + } +} + +void FilePathWatcherImpl::OnFileCanWriteWithoutBlocking(int fd) { + NOTREACHED(); +} + +void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() { + CancelOnMessageLoopThread(); +} + +bool FilePathWatcherImpl::Watch(const FilePath& path, + bool recursive, + FilePathWatcher::Delegate* delegate) { + DCHECK(MessageLoopForIO::current()); + DCHECK(target_.value().empty()); // Can only watch one path. + DCHECK(delegate); + DCHECK_EQ(kqueue_, -1); + + if (recursive) { + // Recursive watch is not supported on this platform. + NOTIMPLEMENTED(); + return false; + } + + delegate_ = delegate; + target_ = path; + + MessageLoop::current()->AddDestructionObserver(this); + io_message_loop_ = base::MessageLoopProxy::current(); + + kqueue_ = kqueue(); + if (kqueue_ == -1) { + DPLOG(ERROR) << "kqueue"; + return false; + } + + int last_entry = EventsForPath(target_, &events_); + DCHECK_NE(last_entry, 0); + + EventVector responses(last_entry); + + int count = HANDLE_EINTR(kevent(kqueue_, &events_[0], last_entry, + &responses[0], last_entry, NULL)); + if (!AreKeventValuesValid(&responses[0], count)) { + // Calling Cancel() here to close any file descriptors that were opened. + // This would happen in the destructor anyways, but FilePathWatchers tend to + // be long lived, and if an error has occurred, there is no reason to waste + // the file descriptors. + Cancel(); + return false; + } + + return MessageLoopForIO::current()->WatchFileDescriptor( + kqueue_, true, MessageLoopForIO::WATCH_READ, &kqueue_watcher_, this); +} + +void FilePathWatcherImpl::Cancel() { + base::MessageLoopProxy* proxy = io_message_loop_.get(); + if (!proxy) { + set_cancelled(); + return; + } + if (!proxy->BelongsToCurrentThread()) { + proxy->PostTask(FROM_HERE, + base::Bind(&FilePathWatcherImpl::Cancel, this)); + return; + } + CancelOnMessageLoopThread(); +} + +void FilePathWatcherImpl::CancelOnMessageLoopThread() { + DCHECK(MessageLoopForIO::current()); + if (!is_cancelled()) { + set_cancelled(); + kqueue_watcher_.StopWatchingFileDescriptor(); + CloseFileDescriptor(&kqueue_); + std::for_each(events_.begin(), events_.end(), ReleaseEvent); + events_.clear(); + io_message_loop_ = NULL; + MessageLoop::current()->RemoveDestructionObserver(this); + delegate_ = NULL; + } +} + +} // namespace + +FilePathWatcher::FilePathWatcher() { + impl_ = new FilePathWatcherImpl(); +} + +} // namespace files +} // namespace base
diff --git a/src/base/files/file_path_watcher_linux.cc b/src/base/files/file_path_watcher_linux.cc new file mode 100644 index 0000000..9e55022 --- /dev/null +++ b/src/base/files/file_path_watcher_linux.cc
@@ -0,0 +1,490 @@ +// 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_path_watcher.h" + +#include <errno.h> +#include <string.h> +#include <sys/inotify.h> +#include <sys/ioctl.h> +#include <sys/select.h> +#include <unistd.h> + +#include <algorithm> +#include <set> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/hash_tables.h" +#include "base/lazy_instance.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/posix/eintr_wrapper.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread.h" + +namespace base { +namespace files { + +namespace { + +class FilePathWatcherImpl; + +// Singleton to manage all inotify watches. +// TODO(tony): It would be nice if this wasn't a singleton. +// http://crbug.com/38174 +class InotifyReader { + public: + typedef int Watch; // Watch descriptor used by AddWatch and RemoveWatch. + static const Watch kInvalidWatch = -1; + + // Watch directory |path| for changes. |watcher| will be notified on each + // change. Returns kInvalidWatch on failure. + Watch AddWatch(const FilePath& path, FilePathWatcherImpl* watcher); + + // Remove |watch|. Returns true on success. + bool RemoveWatch(Watch watch, FilePathWatcherImpl* watcher); + + // Callback for InotifyReaderTask. + void OnInotifyEvent(const inotify_event* event); + + private: + friend struct ::base::DefaultLazyInstanceTraits<InotifyReader>; + + typedef std::set<FilePathWatcherImpl*> WatcherSet; + + InotifyReader(); + ~InotifyReader(); + + // We keep track of which delegates want to be notified on which watches. + base::hash_map<Watch, WatcherSet> watchers_; + + // Lock to protect watchers_. + base::Lock lock_; + + // Separate thread on which we run blocking read for inotify events. + base::Thread thread_; + + // File descriptor returned by inotify_init. + const int inotify_fd_; + + // Use self-pipe trick to unblock select during shutdown. + int shutdown_pipe_[2]; + + // Flag set to true when startup was successful. + bool valid_; + + DISALLOW_COPY_AND_ASSIGN(InotifyReader); +}; + +class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate, + public MessageLoop::DestructionObserver { + public: + FilePathWatcherImpl(); + + // Called for each event coming from the watch. |fired_watch| identifies the + // watch that fired, |child| indicates what has changed, and is relative to + // the currently watched path for |fired_watch|. The flag |created| is true if + // the object appears. + void OnFilePathChanged(InotifyReader::Watch fired_watch, + const FilePath::StringType& child, + bool created); + + // Start watching |path| for changes and notify |delegate| on each change. + // Returns true if watch for |path| has been added successfully. + virtual bool Watch(const FilePath& path, + bool recursive, + FilePathWatcher::Delegate* delegate) OVERRIDE; + + // Cancel the watch. This unregisters the instance with InotifyReader. + virtual void Cancel() OVERRIDE; + + // Deletion of the FilePathWatcher will call Cancel() to dispose of this + // object in the right thread. This also observes destruction of the required + // cleanup thread, in case it quits before Cancel() is called. + virtual void WillDestroyCurrentMessageLoop() OVERRIDE; + + protected: + virtual ~FilePathWatcherImpl() {} + + private: + // Cleans up and stops observing the |message_loop_| thread. + virtual void CancelOnMessageLoopThread() OVERRIDE; + + // Inotify watches are installed for all directory components of |target_|. A + // WatchEntry instance holds the watch descriptor for a component and the + // subdirectory for that identifies the next component. If a symbolic link + // is being watched, the target of the link is also kept. + struct WatchEntry { + WatchEntry(InotifyReader::Watch watch, const FilePath::StringType& subdir) + : watch_(watch), + subdir_(subdir) {} + + InotifyReader::Watch watch_; + FilePath::StringType subdir_; + FilePath::StringType linkname_; + }; + typedef std::vector<WatchEntry> WatchVector; + + // Reconfigure to watch for the most specific parent directory of |target_| + // that exists. Updates |watched_path_|. Returns true on success. + bool UpdateWatches() WARN_UNUSED_RESULT; + + // Delegate to notify upon changes. + scoped_refptr<FilePathWatcher::Delegate> delegate_; + + // The file or directory we're supposed to watch. + FilePath target_; + + // The vector of watches and next component names for all path components, + // starting at the root directory. The last entry corresponds to the watch for + // |target_| and always stores an empty next component name in |subdir_|. + WatchVector watches_; + + DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl); +}; + +void InotifyReaderCallback(InotifyReader* reader, int inotify_fd, + int shutdown_fd) { + // Make sure the file descriptors are good for use with select(). + CHECK_LE(0, inotify_fd); + CHECK_GT(FD_SETSIZE, inotify_fd); + CHECK_LE(0, shutdown_fd); + CHECK_GT(FD_SETSIZE, shutdown_fd); + + while (true) { + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(inotify_fd, &rfds); + FD_SET(shutdown_fd, &rfds); + + // Wait until some inotify events are available. + int select_result = + HANDLE_EINTR(select(std::max(inotify_fd, shutdown_fd) + 1, + &rfds, NULL, NULL, NULL)); + if (select_result < 0) { + DPLOG(WARNING) << "select failed"; + return; + } + + if (FD_ISSET(shutdown_fd, &rfds)) + return; + + // Adjust buffer size to current event queue size. + int buffer_size; + int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd, FIONREAD, + &buffer_size)); + + if (ioctl_result != 0) { + DPLOG(WARNING) << "ioctl failed"; + return; + } + + std::vector<char> buffer(buffer_size); + + ssize_t bytes_read = HANDLE_EINTR(read(inotify_fd, &buffer[0], + buffer_size)); + + if (bytes_read < 0) { + DPLOG(WARNING) << "read from inotify fd failed"; + return; + } + + ssize_t i = 0; + while (i < bytes_read) { + inotify_event* event = reinterpret_cast<inotify_event*>(&buffer[i]); + size_t event_size = sizeof(inotify_event) + event->len; + DCHECK(i + event_size <= static_cast<size_t>(bytes_read)); + reader->OnInotifyEvent(event); + i += event_size; + } + } +} + +static base::LazyInstance<InotifyReader>::Leaky g_inotify_reader = + LAZY_INSTANCE_INITIALIZER; + +InotifyReader::InotifyReader() + : thread_("inotify_reader"), + inotify_fd_(inotify_init()), + valid_(false) { + 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_, + shutdown_pipe_[0])); + valid_ = true; + } +} + +InotifyReader::~InotifyReader() { + if (valid_) { + // Write to the self-pipe so that the select call in InotifyReaderTask + // returns. + ssize_t ret = HANDLE_EINTR(write(shutdown_pipe_[1], "", 1)); + DPCHECK(ret > 0); + DCHECK_EQ(ret, 1); + thread_.Stop(); + } + if (inotify_fd_ >= 0) + close(inotify_fd_); + if (shutdown_pipe_[0] >= 0) + close(shutdown_pipe_[0]); + if (shutdown_pipe_[1] >= 0) + close(shutdown_pipe_[1]); +} + +InotifyReader::Watch InotifyReader::AddWatch( + const FilePath& path, FilePathWatcherImpl* watcher) { + if (!valid_) + return kInvalidWatch; + + base::AutoLock auto_lock(lock_); + + Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(), + IN_CREATE | IN_DELETE | + IN_CLOSE_WRITE | IN_MOVE | + IN_ONLYDIR); + + if (watch == kInvalidWatch) + return kInvalidWatch; + + watchers_[watch].insert(watcher); + + return watch; +} + +bool InotifyReader::RemoveWatch(Watch watch, + FilePathWatcherImpl* watcher) { + if (!valid_) + return false; + + base::AutoLock auto_lock(lock_); + + watchers_[watch].erase(watcher); + + if (watchers_[watch].empty()) { + watchers_.erase(watch); + return (inotify_rm_watch(inotify_fd_, watch) == 0); + } + + return true; +} + +void InotifyReader::OnInotifyEvent(const inotify_event* event) { + if (event->mask & IN_IGNORED) + return; + + FilePath::StringType child(event->len ? event->name : FILE_PATH_LITERAL("")); + base::AutoLock auto_lock(lock_); + + for (WatcherSet::iterator watcher = watchers_[event->wd].begin(); + watcher != watchers_[event->wd].end(); + ++watcher) { + (*watcher)->OnFilePathChanged(event->wd, + child, + event->mask & (IN_CREATE | IN_MOVED_TO)); + } +} + +FilePathWatcherImpl::FilePathWatcherImpl() + : delegate_(NULL) { +} + +void FilePathWatcherImpl::OnFilePathChanged(InotifyReader::Watch fired_watch, + const FilePath::StringType& child, + bool created) { + if (!message_loop()->BelongsToCurrentThread()) { + // Switch to message_loop_ to access watches_ safely. + message_loop()->PostTask(FROM_HERE, + base::Bind(&FilePathWatcherImpl::OnFilePathChanged, + this, + fired_watch, + child, + created)); + return; + } + + DCHECK(MessageLoopForIO::current()); + + // Find the entry in |watches_| that corresponds to |fired_watch|. + WatchVector::const_iterator watch_entry(watches_.begin()); + for ( ; watch_entry != watches_.end(); ++watch_entry) { + if (fired_watch == watch_entry->watch_) { + // Check whether a path component of |target_| changed. + bool change_on_target_path = child.empty() || + ((child == watch_entry->subdir_) && watch_entry->linkname_.empty()) || + (child == watch_entry->linkname_); + + // Check whether the change references |target_| or a direct child. + DCHECK(watch_entry->subdir_.empty() || + (watch_entry + 1) != watches_.end()); + bool target_changed = + (watch_entry->subdir_.empty() && (child == watch_entry->linkname_)) || + (watch_entry->subdir_.empty() && watch_entry->linkname_.empty()) || + (watch_entry->subdir_ == child && (watch_entry + 1)->subdir_.empty()); + + // Update watches if a directory component of the |target_| path + // (dis)appears. Note that we don't add the additional restriction + // of checking the event mask to see if it is for a directory here + // as changes to symlinks on the target path will not have + // IN_ISDIR set in the event masks. As a result we may sometimes + // call UpdateWatches() unnecessarily. + if (change_on_target_path && !UpdateWatches()) { + delegate_->OnFilePathError(target_); + return; + } + + // Report the following events: + // - The target or a direct child of the target got changed (in case the + // watched path refers to a directory). + // - One of the parent directories got moved or deleted, since the target + // disappears in this case. + // - One of the parent directories appears. The event corresponding to + // the target appearing might have been missed in this case, so + // recheck. + if (target_changed || + (change_on_target_path && !created) || + (change_on_target_path && file_util::PathExists(target_))) { + delegate_->OnFilePathChanged(target_); + return; + } + } + } +} + +bool FilePathWatcherImpl::Watch(const FilePath& path, + bool recursive, + FilePathWatcher::Delegate* delegate) { + DCHECK(target_.empty()); + DCHECK(MessageLoopForIO::current()); + if (recursive) { + // Recursive watch is not supported on this platform. + NOTIMPLEMENTED(); + return false; + } + + set_message_loop(base::MessageLoopProxy::current()); + delegate_ = delegate; + target_ = path; + MessageLoop::current()->AddDestructionObserver(this); + + std::vector<FilePath::StringType> comps; + target_.GetComponents(&comps); + DCHECK(!comps.empty()); + std::vector<FilePath::StringType>::const_iterator comp = comps.begin(); + for (++comp; comp != comps.end(); ++comp) + watches_.push_back(WatchEntry(InotifyReader::kInvalidWatch, *comp)); + + watches_.push_back(WatchEntry(InotifyReader::kInvalidWatch, + FilePath::StringType())); + return UpdateWatches(); +} + +void FilePathWatcherImpl::Cancel() { + if (!delegate_) { + // Watch was never called, or the |message_loop_| thread is already gone. + set_cancelled(); + return; + } + + // 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, + make_scoped_refptr(this))); + } else { + CancelOnMessageLoopThread(); + } +} + +void FilePathWatcherImpl::CancelOnMessageLoopThread() { + if (!is_cancelled()) + set_cancelled(); + + if (delegate_) { + MessageLoop::current()->RemoveDestructionObserver(this); + delegate_ = NULL; + } + + for (WatchVector::iterator watch_entry(watches_.begin()); + watch_entry != watches_.end(); ++watch_entry) { + if (watch_entry->watch_ != InotifyReader::kInvalidWatch) + g_inotify_reader.Get().RemoveWatch(watch_entry->watch_, this); + } + watches_.clear(); + target_.clear(); +} + +void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() { + CancelOnMessageLoopThread(); +} + +bool FilePathWatcherImpl::UpdateWatches() { + // Ensure this runs on the |message_loop_| exclusively in order to avoid + // concurrency issues. + DCHECK(message_loop()->BelongsToCurrentThread()); + + // Walk the list of watches and update them as we go. + FilePath path(FILE_PATH_LITERAL("/")); + bool path_valid = true; + for (WatchVector::iterator watch_entry(watches_.begin()); + watch_entry != watches_.end(); ++watch_entry) { + InotifyReader::Watch old_watch = watch_entry->watch_; + if (path_valid) { + watch_entry->watch_ = g_inotify_reader.Get().AddWatch(path, this); + if ((watch_entry->watch_ == InotifyReader::kInvalidWatch) && + file_util::IsLink(path)) { + FilePath link; + if (file_util::ReadSymbolicLink(path, &link)) { + if (!link.IsAbsolute()) + link = path.DirName().Append(link); + // Try watching symlink target directory. If the link target is "/", + // then we shouldn't get here in normal situations and if we do, we'd + // watch "/" for changes to a component "/" which is harmless so no + // special treatment of this case is required. + watch_entry->watch_ = + g_inotify_reader.Get().AddWatch(link.DirName(), this); + if (watch_entry->watch_ != InotifyReader::kInvalidWatch) { + watch_entry->linkname_ = link.BaseName().value(); + } else { + DPLOG(WARNING) << "Watch failed for " << link.DirName().value(); + // TODO(craig) Symlinks only work if the parent directory + // for the target exist. Ideally we should make sure we've + // watched all the components of the symlink path for + // changes. See crbug.com/91561 for details. + } + } + } + if (watch_entry->watch_ == InotifyReader::kInvalidWatch) { + path_valid = false; + } + } else { + watch_entry->watch_ = InotifyReader::kInvalidWatch; + } + if (old_watch != InotifyReader::kInvalidWatch && + old_watch != watch_entry->watch_) { + g_inotify_reader.Get().RemoveWatch(old_watch, this); + } + path = path.Append(watch_entry->subdir_); + } + + return true; +} + +} // namespace + +FilePathWatcher::FilePathWatcher() { + impl_ = new FilePathWatcherImpl(); +} + +} // namespace files +} // namespace base
diff --git a/src/base/files/file_path_watcher_stub.cc b/src/base/files/file_path_watcher_stub.cc new file mode 100644 index 0000000..0c25d7f --- /dev/null +++ b/src/base/files/file_path_watcher_stub.cc
@@ -0,0 +1,38 @@ +// 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. + +// This file exists for Unix systems which don't have the inotify headers, and +// thus cannot build file_watcher_inotify.cc + +#include "base/files/file_path_watcher.h" + +namespace base { +namespace files { + +namespace { + +class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate { + public: + virtual bool Watch(const FilePath& path, + bool recursive, + FilePathWatcher::Delegate* delegate) OVERRIDE { + return false; + } + + virtual void Cancel() OVERRIDE {} + + virtual void CancelOnMessageLoopThread() OVERRIDE {} + + protected: + virtual ~FilePathWatcherImpl() {} +}; + +} // namespace + +FilePathWatcher::FilePathWatcher() { + impl_ = new FilePathWatcherImpl(); +} + +} // namespace files +} // namespace base
diff --git a/src/base/files/file_path_watcher_win.cc b/src/base/files/file_path_watcher_win.cc new file mode 100644 index 0000000..c25260c --- /dev/null +++ b/src/base/files/file_path_watcher_win.cc
@@ -0,0 +1,295 @@ +// 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_path_watcher.h" + +#include "base/bind.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop_proxy.h" +#include "base/time.h" +#include "base/win/object_watcher.h" + +namespace base { +namespace files { + +namespace { + +class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate, + public base::win::ObjectWatcher::Delegate, + public MessageLoop::DestructionObserver { + public: + FilePathWatcherImpl() + : delegate_(NULL), + handle_(INVALID_HANDLE_VALUE), + recursive_watch_(false) {} + + // FilePathWatcher::PlatformDelegate overrides. + virtual bool Watch(const FilePath& path, + bool recursive, + FilePathWatcher::Delegate* delegate) OVERRIDE; + virtual void Cancel() OVERRIDE; + + // Deletion of the FilePathWatcher will call Cancel() to dispose of this + // object in the right thread. This also observes destruction of the required + // cleanup thread, in case it quits before Cancel() is called. + virtual void WillDestroyCurrentMessageLoop() OVERRIDE; + + // Callback from MessageLoopForIO. + virtual void OnObjectSignaled(HANDLE object); + + private: + virtual ~FilePathWatcherImpl() {} + + // Setup a watch handle for directory |dir|. Set |recursive| to true to watch + // the directory sub trees. Returns true if no fatal error occurs. |handle| + // will receive the handle value if |dir| is watchable, otherwise + // INVALID_HANDLE_VALUE. + static bool SetupWatchHandle(const FilePath& dir, + bool recursive, + HANDLE* handle) WARN_UNUSED_RESULT; + + // (Re-)Initialize the watch handle. + bool UpdateWatch() WARN_UNUSED_RESULT; + + // Destroy the watch handle. + void DestroyWatch(); + + // Cleans up and stops observing the |message_loop_| thread. + void CancelOnMessageLoopThread() OVERRIDE; + + // Delegate to notify upon changes. + scoped_refptr<FilePathWatcher::Delegate> delegate_; + + // Path we're supposed to watch (passed to delegate). + FilePath target_; + + // Handle for FindFirstChangeNotification. + HANDLE handle_; + + // ObjectWatcher to watch handle_ for events. + base::win::ObjectWatcher watcher_; + + // Set to true to watch the sub trees of the specified directory file path. + bool recursive_watch_; + + // Keep track of the last modified time of the file. We use nulltime + // to represent the file not existing. + base::Time last_modified_; + + // The time at which we processed the first notification with the + // |last_modified_| time stamp. + base::Time first_notification_; + + DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl); +}; + +bool FilePathWatcherImpl::Watch(const FilePath& path, + bool recursive, + FilePathWatcher::Delegate* delegate) { + DCHECK(target_.value().empty()); // Can only watch one path. + + set_message_loop(base::MessageLoopProxy::current()); + delegate_ = delegate; + target_ = path; + recursive_watch_ = recursive; + MessageLoop::current()->AddDestructionObserver(this); + + if (!UpdateWatch()) + return false; + + watcher_.StartWatching(handle_, this); + + return true; +} + +void FilePathWatcherImpl::Cancel() { + if (!delegate_) { + // Watch was never called, or the |message_loop_| has already quit. + set_cancelled(); + return; + } + + // 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))); + } else { + CancelOnMessageLoopThread(); + } +} + +void FilePathWatcherImpl::CancelOnMessageLoopThread() { + set_cancelled(); + + if (handle_ != INVALID_HANDLE_VALUE) + DestroyWatch(); + + if (delegate_) { + MessageLoop::current()->RemoveDestructionObserver(this); + delegate_ = NULL; + } +} + +void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() { + CancelOnMessageLoopThread(); +} + +void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) { + DCHECK(object == handle_); + // Make sure we stay alive through the body of this function. + scoped_refptr<FilePathWatcherImpl> keep_alive(this); + + if (!UpdateWatch()) { + delegate_->OnFilePathError(target_); + return; + } + + // Check whether the event applies to |target_| and notify the delegate. + base::PlatformFileInfo file_info; + bool file_exists = file_util::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(); + delegate_->OnFilePathChanged(target_); + } else if (file_exists && !first_notification_.is_null()) { + // The target's last modification time is equal to what's on record. This + // means that either an unrelated event occurred, or the target changed + // again (file modification times only have a resolution of 1s). Comparing + // file modification times against the wall clock is not reliable to find + // out whether the change is recent, since this code might just run too + // late. Moreover, there's no guarantee that file modification time and wall + // clock times come from the same source. + // + // Instead, the time at which the first notification carrying the current + // |last_notified_| time stamp is recorded. Later notifications that find + // the same file modification time only need to be forwarded until wall + // 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)) { + // Stop further notifications for this |last_modification_| time stamp. + first_notification_ = base::Time(); + } + delegate_->OnFilePathChanged(target_); + } else if (!file_exists && !last_modified_.is_null()) { + last_modified_ = base::Time(); + delegate_->OnFilePathChanged(target_); + } + + // The watch may have been cancelled by the callback. + if (handle_ != INVALID_HANDLE_VALUE) + watcher_.StartWatching(handle_, this); +} + +// static +bool FilePathWatcherImpl::SetupWatchHandle(const FilePath& dir, + bool recursive, + HANDLE* handle) { + *handle = FindFirstChangeNotification( + dir.value().c_str(), + recursive, + FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE | + FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SECURITY); + if (*handle != INVALID_HANDLE_VALUE) { + // Make sure the handle we got points to an existing directory. It seems + // that windows sometimes hands out watches to directories that are + // about to go away, but doesn't sent notifications if that happens. + if (!file_util::DirectoryExists(dir)) { + FindCloseChangeNotification(*handle); + *handle = INVALID_HANDLE_VALUE; + } + return true; + } + + // If FindFirstChangeNotification failed because the target directory + // doesn't exist, access is denied (happens if the file is already gone but + // there are still handles open), or the target is not a directory, try the + // immediate parent directory instead. + DWORD error_code = GetLastError(); + if (error_code != ERROR_FILE_NOT_FOUND && + error_code != ERROR_PATH_NOT_FOUND && + error_code != ERROR_ACCESS_DENIED && + error_code != ERROR_SHARING_VIOLATION && + error_code != ERROR_DIRECTORY) { + using ::operator<<; // Pick the right operator<< below. + DPLOG(ERROR) << "FindFirstChangeNotification failed for " + << dir.value(); + return false; + } + + return true; +} + +bool FilePathWatcherImpl::UpdateWatch() { + if (handle_ != INVALID_HANDLE_VALUE) + DestroyWatch(); + + base::PlatformFileInfo file_info; + if (file_util::GetFileInfo(target_, &file_info)) { + last_modified_ = file_info.last_modified; + first_notification_ = base::Time::Now(); + } + + // Start at the target and walk up the directory chain until we succesfully + // create a watch handle in |handle_|. |child_dirs| keeps a stack of child + // directories stripped from target, in reverse order. + std::vector<FilePath> child_dirs; + FilePath watched_path(target_); + while (true) { + if (!SetupWatchHandle(watched_path, recursive_watch_, &handle_)) + return false; + + // Break if a valid handle is returned. Try the parent directory otherwise. + if (handle_ != INVALID_HANDLE_VALUE) + break; + + // Abort if we hit the root directory. + child_dirs.push_back(watched_path.BaseName()); + FilePath parent(watched_path.DirName()); + if (parent == watched_path) { + DLOG(ERROR) << "Reached the root directory"; + return false; + } + watched_path = parent; + } + + // At this point, handle_ is valid. However, the bottom-up search that the + // above code performs races against directory creation. So try to walk back + // down and see whether any children appeared in the mean time. + while (!child_dirs.empty()) { + watched_path = watched_path.Append(child_dirs.back()); + child_dirs.pop_back(); + HANDLE temp_handle = INVALID_HANDLE_VALUE; + if (!SetupWatchHandle(watched_path, recursive_watch_, &temp_handle)) + return false; + if (temp_handle == INVALID_HANDLE_VALUE) + break; + FindCloseChangeNotification(handle_); + handle_ = temp_handle; + } + + return true; +} + +void FilePathWatcherImpl::DestroyWatch() { + watcher_.StopWatching(); + FindCloseChangeNotification(handle_); + handle_ = INVALID_HANDLE_VALUE; +} + +} // namespace + +FilePathWatcher::FilePathWatcher() { + impl_ = new FilePathWatcherImpl(); +} + +} // namespace files +} // namespace base
diff --git a/src/base/files/important_file_writer.cc b/src/base/files/important_file_writer.cc new file mode 100644 index 0000000..351adc2 --- /dev/null +++ b/src/base/files/important_file_writer.cc
@@ -0,0 +1,167 @@ +// 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/important_file_writer.h" + +#include <stdio.h> + +#include <string> + +#include "base/bind.h" +#include "base/critical_closure.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/task_runner.h" +#include "base/metrics/histogram.h" +#include "base/string_number_conversions.h" +#include "base/threading/thread.h" +#include "base/time.h" + +namespace base { + +namespace { + +const int kDefaultCommitIntervalMs = 10000; + +enum TempFileFailure { + FAILED_CREATING, + FAILED_OPENING, + FAILED_CLOSING, + FAILED_WRITING, + FAILED_RENAMING, + TEMP_FILE_FAILURE_MAX +}; + +void LogFailure(const FilePath& path, TempFileFailure failure_code, + const std::string& message) { + UMA_HISTOGRAM_ENUMERATION("ImportantFile.TempFileFailures", failure_code, + TEMP_FILE_FAILURE_MAX); + DPLOG(WARNING) << "temp file failure: " << path.value().c_str() + << " : " << message; +} + +} // namespace + +// static +bool ImportantFileWriter::WriteFileAtomically(const FilePath& path, + const std::string& data) { + // Write the data to a temp file then rename to avoid data loss if we crash + // while writing the file. Ensure that the temp file is on the same volume + // 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)) { + LogFailure(path, FAILED_CREATING, "could not create temporary file"); + return false; + } + + int flags = PLATFORM_FILE_OPEN | PLATFORM_FILE_WRITE; + PlatformFile tmp_file = + CreatePlatformFile(tmp_file_path, flags, NULL, NULL); + if (tmp_file == kInvalidPlatformFileValue) { + LogFailure(path, FAILED_OPENING, "could not open temporary file"); + return false; + } + + // If this happens in the wild something really bad is going on. + CHECK_LE(data.length(), static_cast<size_t>(kint32max)); + int bytes_written = WritePlatformFile( + tmp_file, 0, data.data(), static_cast<int>(data.length())); + FlushPlatformFile(tmp_file); // Ignore return value. + + if (!ClosePlatformFile(tmp_file)) { + LogFailure(path, FAILED_CLOSING, "failed to close temporary file"); + file_util::Delete(tmp_file_path, false); + return false; + } + + if (bytes_written < static_cast<int>(data.length())) { + LogFailure(path, FAILED_WRITING, "error writing, bytes_written=" + + IntToString(bytes_written)); + file_util::Delete(tmp_file_path, false); + return false; + } + + if (!file_util::ReplaceFile(tmp_file_path, path)) { + LogFailure(path, FAILED_RENAMING, "could not rename temporary file"); + file_util::Delete(tmp_file_path, false); + return false; + } + + return true; +} + +ImportantFileWriter::ImportantFileWriter( + const FilePath& path, base::SequencedTaskRunner* task_runner) + : path_(path), + task_runner_(task_runner), + serializer_(NULL), + commit_interval_(TimeDelta::FromMilliseconds( + kDefaultCommitIntervalMs)) { + DCHECK(CalledOnValidThread()); + DCHECK(task_runner_.get()); +} + +ImportantFileWriter::~ImportantFileWriter() { + // We're usually a member variable of some other object, which also tends + // to be our serializer. It may not be safe to call back to the parent object + // being destructed. + DCHECK(!HasPendingWrite()); +} + +bool ImportantFileWriter::HasPendingWrite() const { + DCHECK(CalledOnValidThread()); + return timer_.IsRunning(); +} + +void ImportantFileWriter::WriteNow(const std::string& data) { + DCHECK(CalledOnValidThread()); + if (data.length() > static_cast<size_t>(kint32max)) { + NOTREACHED(); + return; + } + + if (HasPendingWrite()) + timer_.Stop(); + + if (!task_runner_->PostTask( + FROM_HERE, + MakeCriticalClosure( + Bind(IgnoreResult(&ImportantFileWriter::WriteFileAtomically), + path_, data)))) { + // Posting the task to background message loop is not expected + // to fail, but if it does, avoid losing data and just hit the disk + // on the current thread. + NOTREACHED(); + + WriteFileAtomically(path_, data); + } +} + +void ImportantFileWriter::ScheduleWrite(DataSerializer* serializer) { + DCHECK(CalledOnValidThread()); + + DCHECK(serializer); + serializer_ = serializer; + + if (!timer_.IsRunning()) { + timer_.Start(FROM_HERE, commit_interval_, this, + &ImportantFileWriter::DoScheduledWrite); + } +} + +void ImportantFileWriter::DoScheduledWrite() { + DCHECK(serializer_); + std::string data; + if (serializer_->SerializeData(&data)) { + WriteNow(data); + } else { + DLOG(WARNING) << "failed to serialize data to be saved in " + << path_.value().c_str(); + } + serializer_ = NULL; +} + +} // namespace base
diff --git a/src/base/files/important_file_writer.h b/src/base/files/important_file_writer.h new file mode 100644 index 0000000..9bc8f07 --- /dev/null +++ b/src/base/files/important_file_writer.h
@@ -0,0 +1,121 @@ +// 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_IMPORTANT_FILE_WRITER_H_ +#define BASE_FILES_IMPORTANT_FILE_WRITER_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/threading/non_thread_safe.h" +#include "base/time.h" +#include "base/timer.h" + +namespace base { + +class SequencedTaskRunner; +class Thread; + +// Helper to ensure that a file won't be corrupted by the write (for example on +// application crash). Consider a naive way to save an important file F: +// +// 1. Open F for writing, truncating it. +// 2. Write new data to F. +// +// It's good when it works, but it gets very bad if step 2. doesn't complete. +// It can be caused by a crash, a computer hang, or a weird I/O error. And you +// end up with a broken file. +// +// To be safe, we don't start with writing directly to F. Instead, we write to +// to a temporary file. Only after that write is successful, we rename the +// temporary file to target filename. +// +// If you want to know more about this approach and ext3/ext4 fsync issues, see +// http://valhenson.livejournal.com/37921.html +class BASE_EXPORT ImportantFileWriter : public NonThreadSafe { + public: + // Used by ScheduleSave to lazily provide the data to be saved. Allows us + // to also batch data serializations. + class BASE_EXPORT DataSerializer { + public: + // Should put serialized string in |data| and return true on successful + // serialization. Will be called on the same thread on which + // ImportantFileWriter has been created. + virtual bool SerializeData(std::string* data) = 0; + + protected: + virtual ~DataSerializer() {} + }; + + // Save |data| to |path| in an atomic manner (see the class comment above). + // Blocks and writes data on the current thread. + static bool WriteFileAtomically(const FilePath& path, + const std::string& data); + + // Initialize the writer. + // |path| is the name of file to write. + // |task_runner| is the SequencedTaskRunner instance where on which we will + // execute file I/O operations. + // All non-const methods, ctor and dtor must be called on the same thread. + ImportantFileWriter(const FilePath& path, + base::SequencedTaskRunner* task_runner); + + // You have to ensure that there are no pending writes at the moment + // of destruction. + ~ImportantFileWriter(); + + const FilePath& path() const { return path_; } + + // Returns true if there is a scheduled write pending which has not yet + // been started. + bool HasPendingWrite() const; + + // Save |data| to target filename. Does not block. If there is a pending write + // scheduled by ScheduleWrite, it is cancelled. + void WriteNow(const std::string& data); + + // Schedule a save to target filename. Data will be serialized and saved + // to disk after the commit interval. If another ScheduleWrite is issued + // before that, only one serialization and write to disk will happen, and + // the most recent |serializer| will be used. This operation does not block. + // |serializer| should remain valid through the lifetime of + // ImportantFileWriter. + void ScheduleWrite(DataSerializer* serializer); + + // Serialize data pending to be saved and execute write on backend thread. + void DoScheduledWrite(); + + TimeDelta commit_interval() const { + return commit_interval_; + } + + void set_commit_interval(const TimeDelta& interval) { + commit_interval_ = interval; + } + + private: + // Path being written to. + const FilePath path_; + + // TaskRunner for the thread on which file I/O can be done. + const scoped_refptr<base::SequencedTaskRunner> task_runner_; + + // Timer used to schedule commit after ScheduleWrite. + OneShotTimer<ImportantFileWriter> timer_; + + // Serializer which will provide the data to be saved. + DataSerializer* serializer_; + + // Time delta after which scheduled data will be written to disk. + TimeDelta commit_interval_; + + DISALLOW_COPY_AND_ASSIGN(ImportantFileWriter); +}; + +} // namespace base + +#endif // BASE_FILES_IMPORTANT_FILE_WRITER_H_
diff --git a/src/base/files/important_file_writer_unittest.cc b/src/base/files/important_file_writer_unittest.cc new file mode 100644 index 0000000..3bd3016 --- /dev/null +++ b/src/base/files/important_file_writer_unittest.cc
@@ -0,0 +1,125 @@ +// 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/important_file_writer.h" + +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/threading/thread.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +std::string GetFileContent(const FilePath& path) { + std::string content; + if (!file_util::ReadFileToString(path, &content)) { + NOTREACHED(); + } + return content; +} + +class DataSerializer : public ImportantFileWriter::DataSerializer { + public: + explicit DataSerializer(const std::string& data) : data_(data) { + } + + virtual bool SerializeData(std::string* output) { + output->assign(data_); + return true; + } + + private: + const std::string data_; +}; + +} // namespace + +class ImportantFileWriterTest : public testing::Test { + public: + ImportantFileWriterTest() { } + virtual void SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + file_ = temp_dir_.path().AppendASCII("test-file"); + } + + protected: + FilePath file_; + MessageLoop loop_; + + private: + ScopedTempDir temp_dir_; +}; + +TEST_F(ImportantFileWriterTest, Basic) { + ImportantFileWriter writer(file_, + MessageLoopProxy::current()); + EXPECT_FALSE(file_util::PathExists(writer.path())); + writer.WriteNow("foo"); + loop_.RunUntilIdle(); + + ASSERT_TRUE(file_util::PathExists(writer.path())); + EXPECT_EQ("foo", GetFileContent(writer.path())); +} + +TEST_F(ImportantFileWriterTest, ScheduleWrite) { + ImportantFileWriter writer(file_, + MessageLoopProxy::current()); + writer.set_commit_interval(TimeDelta::FromMilliseconds(25)); + EXPECT_FALSE(writer.HasPendingWrite()); + DataSerializer serializer("foo"); + writer.ScheduleWrite(&serializer); + EXPECT_TRUE(writer.HasPendingWrite()); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + MessageLoop::QuitClosure(), + TimeDelta::FromMilliseconds(100)); + MessageLoop::current()->Run(); + EXPECT_FALSE(writer.HasPendingWrite()); + ASSERT_TRUE(file_util::PathExists(writer.path())); + EXPECT_EQ("foo", GetFileContent(writer.path())); +} + +TEST_F(ImportantFileWriterTest, DoScheduledWrite) { + ImportantFileWriter writer(file_, + MessageLoopProxy::current()); + EXPECT_FALSE(writer.HasPendingWrite()); + DataSerializer serializer("foo"); + writer.ScheduleWrite(&serializer); + EXPECT_TRUE(writer.HasPendingWrite()); + writer.DoScheduledWrite(); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + MessageLoop::QuitClosure(), + TimeDelta::FromMilliseconds(100)); + MessageLoop::current()->Run(); + EXPECT_FALSE(writer.HasPendingWrite()); + ASSERT_TRUE(file_util::PathExists(writer.path())); + EXPECT_EQ("foo", GetFileContent(writer.path())); +} + +TEST_F(ImportantFileWriterTest, BatchingWrites) { + ImportantFileWriter writer(file_, + MessageLoopProxy::current()); + writer.set_commit_interval(TimeDelta::FromMilliseconds(25)); + DataSerializer foo("foo"), bar("bar"), baz("baz"); + writer.ScheduleWrite(&foo); + writer.ScheduleWrite(&bar); + writer.ScheduleWrite(&baz); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + MessageLoop::QuitClosure(), + TimeDelta::FromMilliseconds(100)); + MessageLoop::current()->Run(); + ASSERT_TRUE(file_util::PathExists(writer.path())); + EXPECT_EQ("baz", GetFileContent(writer.path())); +} + +} // namespace base
diff --git a/src/base/files/scoped_temp_dir.cc b/src/base/files/scoped_temp_dir.cc new file mode 100644 index 0000000..509f808 --- /dev/null +++ b/src/base/files/scoped_temp_dir.cc
@@ -0,0 +1,86 @@ +// 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/scoped_temp_dir.h" + +#include "base/file_util.h" +#include "base/logging.h" + +namespace base { + +ScopedTempDir::ScopedTempDir() { +} + +ScopedTempDir::~ScopedTempDir() { + if (!path_.empty() && !Delete()) + DLOG(WARNING) << "Could not delete temp dir in dtor."; +} + +bool ScopedTempDir::CreateUniqueTempDir() { + if (!path_.empty()) + return false; + + // 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_)) + return false; + + return true; +} + +bool ScopedTempDir::CreateUniqueTempDirUnderPath(const FilePath& base_path) { + if (!path_.empty()) + return false; + + // If |base_path| does not exist, create it. + if (!file_util::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_)) + return false; + + return true; +} + +bool ScopedTempDir::Set(const FilePath& path) { + if (!path_.empty()) + return false; + + if (!file_util::DirectoryExists(path) && + !file_util::CreateDirectory(path)) + return false; + + path_ = path; + return true; +} + +bool ScopedTempDir::Delete() { + if (path_.empty()) + return false; + + bool ret = file_util::Delete(path_, true); + if (ret) { + // We only clear the path if deleted the directory. + path_.clear(); + } + + return ret; +} + +FilePath ScopedTempDir::Take() { + FilePath ret = path_; + path_ = FilePath(); + return ret; +} + +bool ScopedTempDir::IsValid() const { + return !path_.empty() && file_util::DirectoryExists(path_); +} + +} // namespace base
diff --git a/src/base/files/scoped_temp_dir.h b/src/base/files/scoped_temp_dir.h new file mode 100644 index 0000000..641e7ef --- /dev/null +++ b/src/base/files/scoped_temp_dir.h
@@ -0,0 +1,62 @@ +// 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. + +#ifndef BASE_FILES_SCOPED_TEMP_DIR_H_ +#define BASE_FILES_SCOPED_TEMP_DIR_H_ + +// An object representing a temporary / scratch directory that should be cleaned +// up (recursively) when this object goes out of scope. Note that since +// deletion occurs during the destructor, no further error handling is possible +// if the directory fails to be deleted. As a result, deletion is not +// guaranteed by this class. +// +// Multiple calls to the methods which establish a temporary directory +// (CreateUniqueTempDir, CreateUniqueTempDirUnderPath, and Set) must have +// intervening calls to Delete or Take, or the calls will fail. + +#include "base/base_export.h" +#include "base/file_path.h" + +namespace base { + +class BASE_EXPORT ScopedTempDir { + public: + // No directory is owned/created initially. + ScopedTempDir(); + + // Recursively delete path. + ~ScopedTempDir(); + + // Creates a unique directory in TempPath, and takes ownership of it. + // See file_util::CreateNewTemporaryDirectory. + bool CreateUniqueTempDir() WARN_UNUSED_RESULT; + + // Creates a unique directory under a given path, and takes ownership of it. + bool CreateUniqueTempDirUnderPath(const FilePath& path) WARN_UNUSED_RESULT; + + // Takes ownership of directory at |path|, creating it if necessary. + // Don't call multiple times unless Take() has been called first. + bool Set(const FilePath& path) WARN_UNUSED_RESULT; + + // Deletes the temporary directory wrapped by this object. + bool Delete() WARN_UNUSED_RESULT; + + // Caller takes ownership of the temporary directory so it won't be destroyed + // when this object goes out of scope. + FilePath Take(); + + const FilePath& path() const { return path_; } + + // Returns true if path_ is non-empty and exists. + bool IsValid() const; + + private: + FilePath path_; + + DISALLOW_COPY_AND_ASSIGN(ScopedTempDir); +}; + +} // namespace base + +#endif // BASE_FILES_SCOPED_TEMP_DIR_H_
diff --git a/src/base/files/scoped_temp_dir_unittest.cc b/src/base/files/scoped_temp_dir_unittest.cc new file mode 100644 index 0000000..8497ac6 --- /dev/null +++ b/src/base/files/scoped_temp_dir_unittest.cc
@@ -0,0 +1,117 @@ +// 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 <string> + +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/platform_file.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(ScopedTempDir, FullPath) { + FilePath test_path; + file_util::CreateNewTempDirectory(FILE_PATH_LITERAL("scoped_temp_dir"), + &test_path); + + // Against an existing dir, it should get destroyed when leaving scope. + EXPECT_TRUE(file_util::DirectoryExists(test_path)); + { + ScopedTempDir dir; + EXPECT_TRUE(dir.Set(test_path)); + EXPECT_TRUE(dir.IsValid()); + } + EXPECT_FALSE(file_util::DirectoryExists(test_path)); + + { + ScopedTempDir dir; + EXPECT_TRUE(dir.Set(test_path)); + // Now the dir doesn't exist, so ensure that it gets created. + EXPECT_TRUE(file_util::DirectoryExists(test_path)); + // When we call Release(), it shouldn't get destroyed when leaving scope. + FilePath path = dir.Take(); + EXPECT_EQ(path.value(), test_path.value()); + EXPECT_FALSE(dir.IsValid()); + } + EXPECT_TRUE(file_util::DirectoryExists(test_path)); + + // Clean up. + { + ScopedTempDir dir; + EXPECT_TRUE(dir.Set(test_path)); + } + EXPECT_FALSE(file_util::DirectoryExists(test_path)); +} + +TEST(ScopedTempDir, TempDir) { + // In this case, just verify that a directory was created and that it's a + // child of TempDir. + FilePath test_path; + { + ScopedTempDir dir; + EXPECT_TRUE(dir.CreateUniqueTempDir()); + test_path = dir.path(); + EXPECT_TRUE(file_util::DirectoryExists(test_path)); + FilePath tmp_dir; + EXPECT_TRUE(file_util::GetTempDir(&tmp_dir)); + EXPECT_TRUE(test_path.value().find(tmp_dir.value()) != std::string::npos); + } + EXPECT_FALSE(file_util::DirectoryExists(test_path)); +} + +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)); + + FilePath test_path; + { + ScopedTempDir dir; + EXPECT_TRUE(dir.CreateUniqueTempDirUnderPath(base_path)); + test_path = dir.path(); + EXPECT_TRUE(file_util::DirectoryExists(test_path)); + EXPECT_TRUE(base_path.IsParent(test_path)); + EXPECT_TRUE(test_path.value().find(base_path.value()) != std::string::npos); + } + EXPECT_FALSE(file_util::DirectoryExists(test_path)); + file_util::Delete(base_path, true); +} + +TEST(ScopedTempDir, MultipleInvocations) { + ScopedTempDir dir; + EXPECT_TRUE(dir.CreateUniqueTempDir()); + EXPECT_FALSE(dir.CreateUniqueTempDir()); + EXPECT_TRUE(dir.Delete()); + EXPECT_TRUE(dir.CreateUniqueTempDir()); + EXPECT_FALSE(dir.CreateUniqueTempDir()); + ScopedTempDir other_dir; + EXPECT_TRUE(other_dir.Set(dir.Take())); + EXPECT_TRUE(dir.CreateUniqueTempDir()); + EXPECT_FALSE(dir.CreateUniqueTempDir()); + EXPECT_FALSE(other_dir.CreateUniqueTempDir()); +} + +#if defined(OS_WIN) +TEST(ScopedTempDir, LockedTempDir) { + ScopedTempDir dir; + EXPECT_TRUE(dir.CreateUniqueTempDir()); + int file_flags = base::PLATFORM_FILE_CREATE_ALWAYS | + base::PLATFORM_FILE_WRITE; + base::PlatformFileError error_code = base::PLATFORM_FILE_OK; + FilePath file_path(dir.path().Append(FILE_PATH_LITERAL("temp"))); + base::PlatformFile file = base::CreatePlatformFile(file_path, file_flags, + NULL, &error_code); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + EXPECT_FALSE(dir.Delete()); // We should not be able to delete. + EXPECT_FALSE(dir.path().empty()); // We should still have a valid path. + EXPECT_TRUE(base::ClosePlatformFile(file)); + // Now, we should be able to delete. + EXPECT_TRUE(dir.Delete()); +} +#endif // defined(OS_WIN) + +} // namespace base
diff --git a/src/base/float_util.h b/src/base/float_util.h new file mode 100644 index 0000000..e2fa484 --- /dev/null +++ b/src/base/float_util.h
@@ -0,0 +1,44 @@ +// 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_FLOAT_UTIL_H_ +#define BASE_FLOAT_UTIL_H_ + +#include "build/build_config.h" + +#if defined(OS_STARBOARD) +#include "starboard/double.h" +#else +#include <float.h> +#include <math.h> +#include <cmath> +#endif + +namespace base { + +inline bool IsFinite(const double& number) { +#if defined(OS_STARBOARD) + return SbDoubleIsFinite(number); +#elif defined(__LB_SHELL__) && defined(__LB_LINUX__) + // On Linux, math.h defines fpclassify() as a macro which is undefined in + // cmath. However, as math.h has guard to avoid being included again, the + // fpclassify() macro is no longer available if cmath is included after + // math.h, even if math.h is included again. So we have to use + // std::fpclassify() on Linux. + return std::fpclassify(number) != FP_INFINITE; +#elif defined(__LB_SHELL__) + return fpclassify(number) != FP_INFINITE; +#elif defined(OS_ANDROID) + // isfinite isn't available on Android: http://b.android.com/34793 + return finite(number) != 0; +#elif defined(OS_POSIX) + return isfinite(number) != 0; +#elif defined(OS_WIN) + return _finite(number) != 0; +#endif +} + +} // namespace base + +#endif // BASE_FLOAT_UTIL_H_
diff --git a/src/base/format_macros.h b/src/base/format_macros.h new file mode 100644 index 0000000..a073b83 --- /dev/null +++ b/src/base/format_macros.h
@@ -0,0 +1,77 @@ +// Copyright (c) 2009 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_FORMAT_MACROS_H_ +#define BASE_FORMAT_MACROS_H_ + +// This file defines the format macros for some integer types. + +// To print a 64-bit value in a portable way: +// int64_t value; +// printf("xyz:%" PRId64, value); +// The "d" in the macro corresponds to %d; you can also use PRIu64 etc. +// +// For wide strings, prepend "Wide" to the macro: +// int64_t value; +// StringPrintf(L"xyz: %" WidePRId64, value); +// +// To print a size_t value in a portable way: +// size_t size; +// printf("xyz: %" PRIuS, size); +// The "u" in the macro corresponds to %u, and S is for "size". + +#include "build/build_config.h" + +#if defined(OS_STARBOARD) +#include "starboard/types.h" +#endif + +#if (defined(OS_POSIX) || defined(OS_STARBOARD)) && !defined(COMPILER_MSVC) + +#if (defined(_INTTYPES_H) || defined(_INTTYPES_H_)) && !defined(PRId64) +#error "inttypes.h has already been included before this header file, but " +#error "without __STDC_FORMAT_MACROS defined." +#endif + +#if !defined(__STDC_FORMAT_MACROS) +#define __STDC_FORMAT_MACROS +#endif + +#include <inttypes.h> + +// GCC will concatenate wide and narrow strings correctly, so nothing needs to +// be done here. +#define WidePRId64 PRId64 +#define WidePRIu64 PRIu64 +#define WidePRIx64 PRIx64 + +#if !defined(PRIuS) +#define PRIuS "zu" +#endif + +#else // OS_WIN || __LB_XB1__ || __LB_XB360__ + +#if !defined(PRId64) +#define PRId64 "I64d" +#endif + +#if !defined(PRIu64) +#define PRIu64 "I64u" +#endif + +#if !defined(PRIx64) +#define PRIx64 "I64x" +#endif + +#define WidePRId64 L"I64d" +#define WidePRIu64 L"I64u" +#define WidePRIx64 L"I64x" + +#if !defined(PRIuS) +#define PRIuS "Iu" +#endif + +#endif + +#endif // BASE_FORMAT_MACROS_H_
diff --git a/src/base/gmock_unittest.cc b/src/base/gmock_unittest.cc new file mode 100644 index 0000000..855380a --- /dev/null +++ b/src/base/gmock_unittest.cc
@@ -0,0 +1,137 @@ +// Copyright (c) 2009 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 test is a simple sanity check to make sure gmock is able to build/link +// correctly. It just instantiates a mock object and runs through a couple of +// the basic mock features. + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Gmock matchers and actions that we use below. +using testing::AnyOf; +using testing::Eq; +using testing::Return; +using testing::SetArgumentPointee; +using testing::WithArg; +using testing::_; + +namespace { + +// Simple class that we can mock out the behavior for. Everything is virtual +// for easy mocking. +class SampleClass { + public: + SampleClass() {} + virtual ~SampleClass() {} + + virtual int ReturnSomething() { + return -1; + } + + virtual void ReturnNothingConstly() const { + } + + virtual void OutputParam(int* a) { + } + + virtual int ReturnSecond(int a, int b) { + return b; + } +}; + +// Declare a mock for the class. +class MockSampleClass : public SampleClass { + public: + MOCK_METHOD0(ReturnSomething, int()); + MOCK_CONST_METHOD0(ReturnNothingConstly, void()); + MOCK_METHOD1(OutputParam, void(int* a)); + MOCK_METHOD2(ReturnSecond, int(int a, int b)); +}; + +// Create a couple of custom actions. Custom actions can be used for adding +// more complex behavior into your mock...though if you start needing these, ask +// if you're asking your mock to do too much. +ACTION(ReturnVal) { + // Return the first argument received. + return arg0; +} +ACTION(ReturnSecond) { + // Returns the second argument. This basically implemetns ReturnSecond. + return arg1; +} + +TEST(GmockTest, SimpleMatchAndActions) { + // Basic test of some simple gmock matchers, actions, and cardinality + // expectations. + MockSampleClass mock; + + EXPECT_CALL(mock, ReturnSomething()) + .WillOnce(Return(1)) + .WillOnce(Return(2)) + .WillOnce(Return(3)); + EXPECT_EQ(1, mock.ReturnSomething()); + EXPECT_EQ(2, mock.ReturnSomething()); + EXPECT_EQ(3, mock.ReturnSomething()); + + EXPECT_CALL(mock, ReturnNothingConstly()).Times(2); + mock.ReturnNothingConstly(); + mock.ReturnNothingConstly(); +} + +TEST(GmockTest, AssignArgument) { + // Capture an argument for examination. + MockSampleClass mock; + + EXPECT_CALL(mock, OutputParam(_)) + .WillRepeatedly(SetArgumentPointee<0>(5)); + + int arg = 0; + mock.OutputParam(&arg); + EXPECT_EQ(5, arg); +} + +TEST(GmockTest, SideEffects) { + // Capture an argument for examination. + MockSampleClass mock; + + EXPECT_CALL(mock, OutputParam(_)) + .WillRepeatedly(SetArgumentPointee<0>(5)); + + int arg = 0; + mock.OutputParam(&arg); + EXPECT_EQ(5, arg); +} + +TEST(GmockTest, CustomAction_ReturnSecond) { + // Test a mock of the ReturnSecond behavior using an action that provides an + // alternate implementation of the function. Danger here though, this is + // starting to add too much behavior of the mock, which means the mock + // implementation might start to have bugs itself. + MockSampleClass mock; + + EXPECT_CALL(mock, ReturnSecond(_, AnyOf(Eq(4), Eq(5)))) + .WillRepeatedly(ReturnSecond()); + EXPECT_EQ(4, mock.ReturnSecond(-1, 4)); + EXPECT_EQ(5, mock.ReturnSecond(0, 5)); + EXPECT_EQ(4, mock.ReturnSecond(0xdeadbeef, 4)); + EXPECT_EQ(4, mock.ReturnSecond(112358, 4)); + EXPECT_EQ(5, mock.ReturnSecond(1337, 5)); +} + +TEST(GmockTest, CustomAction_ReturnVal) { + // Alternate implemention of ReturnSecond using a more general custom action, + // and a WithArg adapter to bridge the interfaces. + MockSampleClass mock; + + EXPECT_CALL(mock, ReturnSecond(_, AnyOf(Eq(4), Eq(5)))) + .WillRepeatedly(WithArg<1>(ReturnVal())); + EXPECT_EQ(4, mock.ReturnSecond(-1, 4)); + EXPECT_EQ(5, mock.ReturnSecond(0, 5)); + EXPECT_EQ(4, mock.ReturnSecond(0xdeadbeef, 4)); + EXPECT_EQ(4, mock.ReturnSecond(112358, 4)); + EXPECT_EQ(5, mock.ReturnSecond(1337, 5)); +} + +} // namespace
diff --git a/src/base/gtest_prod_util.h b/src/base/gtest_prod_util.h new file mode 100644 index 0000000..3289e63 --- /dev/null +++ b/src/base/gtest_prod_util.h
@@ -0,0 +1,66 @@ +// 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_GTEST_PROD_UTIL_H_ +#define BASE_GTEST_PROD_UTIL_H_ + +#include "testing/gtest/include/gtest/gtest_prod.h" + +// This is a wrapper for gtest's FRIEND_TEST macro that friends +// test with all possible prefixes. This is very helpful when changing the test +// prefix, because the friend declarations don't need to be updated. +// +// Example usage: +// +// class MyClass { +// private: +// void MyMethod(); +// FRIEND_TEST_ALL_PREFIXES(MyClassTest, MyMethod); +// }; +#define FRIEND_TEST_ALL_PREFIXES(test_case_name, test_name) \ + FRIEND_TEST(test_case_name, test_name); \ + FRIEND_TEST(test_case_name, DISABLED_##test_name); \ + FRIEND_TEST(test_case_name, FLAKY_##test_name) + +// C++ compilers will refuse to compile the following code: +// +// namespace foo { +// class MyClass { +// private: +// FRIEND_TEST_ALL_PREFIXES(MyClassTest, TestMethod); +// bool private_var; +// }; +// } // namespace foo +// +// class MyClassTest::TestMethod() { +// foo::MyClass foo_class; +// foo_class.private_var = true; +// } +// +// Unless you forward declare MyClassTest::TestMethod outside of namespace foo. +// Use FORWARD_DECLARE_TEST to do so for all possible prefixes. +// +// Example usage: +// +// FORWARD_DECLARE_TEST(MyClassTest, TestMethod); +// +// namespace foo { +// class MyClass { +// private: +// FRIEND_TEST_ALL_PREFIXES(::MyClassTest, TestMethod); // NOTE use of :: +// bool private_var; +// }; +// } // namespace foo +// +// class MyClassTest::TestMethod() { +// foo::MyClass foo_class; +// foo_class.private_var = true; +// } + +#define FORWARD_DECLARE_TEST(test_case_name, test_name) \ + class test_case_name##_##test_name##_Test; \ + class test_case_name##_##DISABLED_##test_name##_Test; \ + class test_case_name##_##FLAKY_##test_name##_Test + +#endif // BASE_GTEST_PROD_UTIL_H_
diff --git a/src/base/guid.cc b/src/base/guid.cc new file mode 100644 index 0000000..920dae5 --- /dev/null +++ b/src/base/guid.cc
@@ -0,0 +1,32 @@ +// 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/guid.h" + +#include "base/rand_util.h" +#include "base/stringprintf.h" + +namespace base { + +bool IsValidGUID(const std::string& guid) { + const size_t kGUIDLength = 36U; + if (guid.length() != kGUIDLength) + return false; + + 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) { + if (current != '-') + return false; + } else { + if (hexchars.find(current) == std::string::npos) + return false; + } + } + + return true; +} + +} // namespace guid
diff --git a/src/base/guid.h b/src/base/guid.h new file mode 100644 index 0000000..e730f4c --- /dev/null +++ b/src/base/guid.h
@@ -0,0 +1,32 @@ +// 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_GUID_H_ +#define BASE_GUID_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "build/build_config.h" + +namespace base { + +// Generate a 128-bit random GUID of the form: "%08X-%04X-%04X-%04X-%012llX". +// If GUID generation fails an empty string is returned. +// The POSIX implementation uses psuedo random number generation to create +// the GUID. The Windows implementation uses system services. +BASE_EXPORT std::string GenerateGUID(); + +// Returns true if the input string conforms to the GUID format. +BASE_EXPORT bool IsValidGUID(const std::string& guid); + +#if defined(OS_POSIX) || defined(OS_STARBOARD) +// For unit testing purposes only. Do not use outside of tests. +BASE_EXPORT std::string RandomDataToGUIDString(const uint64 bytes[2]); +#endif + +} // namespace guid + +#endif // BASE_GUID_H_
diff --git a/src/base/guid_posix.cc b/src/base/guid_posix.cc new file mode 100644 index 0000000..89811d0 --- /dev/null +++ b/src/base/guid_posix.cc
@@ -0,0 +1,28 @@ +// 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/guid.h" + +#include "base/rand_util.h" +#include "base/stringprintf.h" + +namespace base { + +std::string GenerateGUID() { + uint64 sixteen_bytes[2] = { base::RandUint64(), base::RandUint64() }; + return RandomDataToGUIDString(sixteen_bytes); +} + +// TODO(cmasone): Once we're comfortable this works, migrate Windows code to +// use this as well. +std::string RandomDataToGUIDString(const uint64 bytes[2]) { + return StringPrintf("%08X-%04X-%04X-%04X-%012llX", + static_cast<unsigned int>(bytes[0] >> 32), + static_cast<unsigned int>((bytes[0] >> 16) & 0x0000ffff), + static_cast<unsigned int>(bytes[0] & 0x0000ffff), + static_cast<unsigned int>(bytes[1] >> 48), + bytes[1] & 0x0000ffffffffffffULL); +} + +} // namespace guid
diff --git a/src/base/guid_shell.cc b/src/base/guid_shell.cc new file mode 100644 index 0000000..8336580 --- /dev/null +++ b/src/base/guid_shell.cc
@@ -0,0 +1,17 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "guid_posix.cc"
diff --git a/src/base/guid_starboard.cc b/src/base/guid_starboard.cc new file mode 100644 index 0000000..f050ba0 --- /dev/null +++ b/src/base/guid_starboard.cc
@@ -0,0 +1,15 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "guid_posix.cc"
diff --git a/src/base/guid_unittest.cc b/src/base/guid_unittest.cc new file mode 100644 index 0000000..18c04a9 --- /dev/null +++ b/src/base/guid_unittest.cc
@@ -0,0 +1,42 @@ +// 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/guid.h" + +#include <limits> + +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_POSIX) +TEST(GUIDTest, GUIDGeneratesAllZeroes) { + uint64 bytes[] = { 0, 0 }; + std::string clientid = base::RandomDataToGUIDString(bytes); + EXPECT_EQ("00000000-0000-0000-0000-000000000000", clientid); +} + +TEST(GUIDTest, GUIDGeneratesCorrectly) { + uint64 bytes[] = { 0x0123456789ABCDEFULL, 0xFEDCBA9876543210ULL }; + std::string clientid = base::RandomDataToGUIDString(bytes); + EXPECT_EQ("01234567-89AB-CDEF-FEDC-BA9876543210", clientid); +} +#endif + +TEST(GUIDTest, GUIDCorrectlyFormatted) { + const int kIterations = 10; + for (int it = 0; it < kIterations; ++it) { + std::string guid = base::GenerateGUID(); + EXPECT_TRUE(base::IsValidGUID(guid)); + } +} + +TEST(GUIDTest, GUIDBasicUniqueness) { + const int kIterations = 10; + for (int it = 0; it < kIterations; ++it) { + std::string guid1 = base::GenerateGUID(); + std::string guid2 = base::GenerateGUID(); + EXPECT_EQ(36U, guid1.length()); + EXPECT_EQ(36U, guid2.length()); + EXPECT_NE(guid1, guid2); + } +}
diff --git a/src/base/guid_win.cc b/src/base/guid_win.cc new file mode 100644 index 0000000..1cddff8 --- /dev/null +++ b/src/base/guid_win.cc
@@ -0,0 +1,38 @@ +// 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/guid.h" + +#include <stdlib.h> + +#include <objbase.h> +#include <windows.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" + +namespace base { + +std::string GenerateGUID() { + const int kGUIDSize = 39; + + GUID guid; + HRESULT guid_result = CoCreateGuid(&guid); + DCHECK(SUCCEEDED(guid_result)); + if (!SUCCEEDED(guid_result)) + return std::string(); + + std::wstring guid_string; + int result = StringFromGUID2(guid, + WriteInto(&guid_string, kGUIDSize), kGUIDSize); + DCHECK(result == kGUIDSize); + if (result != kGUIDSize) + return std::string(); + + return WideToUTF8(guid_string.substr(1, guid_string.length() - 2)); +} + +} // namespace guid
diff --git a/src/base/hash.cc b/src/base/hash.cc new file mode 100644 index 0000000..411e186 --- /dev/null +++ b/src/base/hash.cc
@@ -0,0 +1,100 @@ +// Copyright (c) 2010, Paul Hsieh +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither my name, Paul Hsieh, nor the names of any other contributors to the +// code use may not be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +// From http://www.azillionmonkeys.com/qed/hash.html + +#include "base/hash.h" + +typedef uint32 uint32_t; +typedef uint16 uint16_t; + +namespace base { + +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif + +uint32 SuperFastHash(const char * data, int len) { + uint32_t hash = len, tmp; + int rem; + + if (len <= 0 || data == NULL) + return 0; + + rem = len & 3; + len >>= 2; + + /* Main loop */ + for (; len > 0; len--) { + hash += get16bits(data); + tmp = (get16bits(data + 2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + data += 2 * sizeof(uint16_t); + hash += hash >> 11; + } + + /* Handle end cases */ + switch (rem) { + case 3: + hash += get16bits(data); + hash ^= hash << 16; + + // Treat the final character as signed. This ensures all platforms behave + // consistently with the original x86 code. + hash ^= static_cast<signed char>(data[sizeof(uint16_t)]) << 18; + hash += hash >> 11; + break; + case 2: + hash += get16bits(data); + hash ^= hash << 11; + hash += hash >> 17; + break; + case 1: + hash += static_cast<signed char>(*data); + hash ^= hash << 10; + hash += hash >> 1; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return hash; +} + +} // namespace base
diff --git a/src/base/hash.h b/src/base/hash.h new file mode 100644 index 0000000..cf8ea3a --- /dev/null +++ b/src/base/hash.h
@@ -0,0 +1,31 @@ +// 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. + +#ifndef BASE_HASH_H_ +#define BASE_HASH_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { + +// From http://www.azillionmonkeys.com/qed/hash.html +// This is the hash used on WebCore/platform/stringhash +BASE_EXPORT uint32 SuperFastHash(const char * data, int len); + +inline uint32 Hash(const char* key, size_t length) { + return SuperFastHash(key, static_cast<int>(length)); +} + +inline uint32 Hash(const std::string& key) { + if (key.empty()) + return 0; + return SuperFastHash(key.data(), static_cast<int>(key.size())); +} + +} // namespace base + +#endif // BASE_HASH_H_
diff --git a/src/base/hash_tables.h b/src/base/hash_tables.h new file mode 100644 index 0000000..b6f40be --- /dev/null +++ b/src/base/hash_tables.h
@@ -0,0 +1,162 @@ +// 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. +// + +// +// Deal with the differences between Microsoft and GNU implemenations +// of hash_map. Allows all platforms to use |base::hash_map| and +// |base::hash_set|. +// eg: +// base::hash_map<int> my_map; +// base::hash_set<int> my_set; +// +// NOTE: It is an explicit non-goal of this class to provide a generic hash +// function for pointers. If you want to hash a pointers to a particular class, +// please define the template specialization elsewhere (for example, in its +// header file) and keep it specific to just pointers to that class. This is +// because identity hashes are not desirable for all types that might show up +// in containers as pointers. + +#ifndef BASE_HASH_TABLES_H_ +#define BASE_HASH_TABLES_H_ + +#include <string> + +#include "build/build_config.h" + +#include "base/string16.h" + +#if defined(OS_STARBOARD) +#include "starboard/configuration.h" +#define BASE_HASH_DEFINE_LONG_LONG_HASHES !SB_HAS(LONG_LONG_HASH) +#define BASE_HASH_DEFINE_STRING_HASHES !SB_HAS(STRING_HASH) +#define BASE_HASH_USE_HASH !SB_HAS(HASH_USING) +#define BASE_HASH_MAP_INCLUDE SB_HASH_MAP_INCLUDE +#define BASE_HASH_NAMESPACE SB_HASH_NAMESPACE +#define BASE_HASH_SET_INCLUDE SB_HASH_SET_INCLUDE +#if !SB_HAS(HASH_VALUE) +#define BASE_HASH_USE_HASH_STRUCT +#endif +#elif defined(COMPILER_MSVC) +#define BASE_HASH_DEFINE_LONG_LONG_HASHES 0 +#define BASE_HASH_DEFINE_STRING_HASHES 0 +#define BASE_HASH_USE_HASH 0 +#define BASE_HASH_MAP_INCLUDE <hash_map> +#define BASE_HASH_NAMESPACE stdext +#define BASE_HASH_SET_INCLUDE <hash_set> +#elif defined(COMPILER_GCC) +#if defined(OS_ANDROID) || (defined(__LB_SHELL__) && !defined(__LB_LINUX__)) +#define BASE_HASH_DEFINE_LONG_LONG_HASHES 0 +#define BASE_HASH_DEFINE_STRING_HASHES !defined(__LB_SHELL__) +#define BASE_HASH_MAP_INCLUDE <hash_map> +#define BASE_HASH_NAMESPACE std +#define BASE_HASH_SET_INCLUDE <hash_set> +#else +#define BASE_HASH_DEFINE_LONG_LONG_HASHES 1 +#define BASE_HASH_DEFINE_STRING_HASHES 1 +#define BASE_HASH_MAP_INCLUDE <ext/hash_map> +#define BASE_HASH_NAMESPACE __gnu_cxx +#define BASE_HASH_SET_INCLUDE <ext/hash_set> +#define BASE_HASH_USE_HASH_STRUCT +#endif +#if defined(__LB_LINUX__) +#define BASE_HASH_USE_HASH 1 +#else +#define BASE_HASH_USE_HASH 0 +#endif +#else // COMPILER +#error define BASE_HASH_NAMESPACE for your compiler +#endif // COMPILER + +// This is a hack to disable the gcc 4.4 warning about hash_map and hash_set +// being deprecated. We can get rid of this when we upgrade to VS2008 and we +// can use <tr1/unordered_map> and <tr1/unordered_set>. +#ifdef __DEPRECATED +#define CHROME_OLD__DEPRECATED __DEPRECATED +#undef __DEPRECATED +#endif + +#include BASE_HASH_MAP_INCLUDE +#include BASE_HASH_SET_INCLUDE + +#ifdef CHROME_OLD__DEPRECATED +#define __DEPRECATED CHROME_OLD__DEPRECATED +#undef CHROME_OLD__DEPRECATED +#endif + +#if BASE_HASH_DEFINE_LONG_LONG_HASHES +// The GNU C++ library provides identity hash functions for many integral types, +// but not for |long long|. This hash function will truncate if |size_t| is +// narrower than |long long|. This is probably good enough for what we will +// use it for. + +#define DEFINE_TRIVIAL_HASH(integral_type) \ + template<> \ + struct hash<integral_type> { \ + std::size_t operator()(integral_type value) const { \ + return static_cast<std::size_t>(value); \ + } \ + } + +namespace BASE_HASH_NAMESPACE { +DEFINE_TRIVIAL_HASH(long long); +DEFINE_TRIVIAL_HASH(unsigned long long); + +template <typename T> +struct hash<T*> { + std::size_t operator()(T* value) const { + return BASE_HASH_NAMESPACE::hash<uintptr_t>()( + reinterpret_cast<uintptr_t>(value)); + } +}; +} // namespace BASE_HASH_NAMESPACE + +#undef DEFINE_TRIVIAL_HASH +#endif // BASE_HASH_DEFINE_LONG_LONG_HASHES + + +#if BASE_HASH_DEFINE_STRING_HASHES +// Implement string hash functions so that strings of various flavors can +// be used as keys in STL maps and sets. The hash algorithm comes from the +// GNU C++ library, in <tr1/functional>. It is duplicated here because GCC +// versions prior to 4.3.2 are unable to compile <tr1/functional> when RTTI +// is disabled, as it is in our build. + +#define DEFINE_STRING_HASH(string_type) \ + template<> \ + struct hash<string_type> { \ + std::size_t operator()(const string_type& s) const { \ + std::size_t result = 0; \ + for (string_type::const_iterator i = s.begin(); i != s.end(); ++i) \ + result = (result * 131) + *i; \ + return result; \ + } \ + } + +namespace BASE_HASH_NAMESPACE { +DEFINE_STRING_HASH(std::string); +DEFINE_STRING_HASH(string16); +} // namespace BASE_HASH_NAMESPACE + +#undef DEFINE_STRING_HASH +#endif // BASE_HASH_DEFINE_STRING_HASHES + + +namespace base { +#if BASE_HASH_USE_HASH +using BASE_HASH_NAMESPACE::hash; +#endif +using BASE_HASH_NAMESPACE::hash_map; +using BASE_HASH_NAMESPACE::hash_multimap; +using BASE_HASH_NAMESPACE::hash_multiset; +using BASE_HASH_NAMESPACE::hash_set; +} + +#undef BASE_HASH_DEFINE_LONG_LONG_HASHES +#undef BASE_HASH_DEFINE_STRING_HASHES +#undef BASE_HASH_MAP_INCLUDE +#undef BASE_HASH_SET_INCLUDE +#undef BASE_HASH_USE_HASH + +#endif // BASE_HASH_TABLES_H_
diff --git a/src/base/hi_res_timer_manager.h b/src/base/hi_res_timer_manager.h new file mode 100644 index 0000000..1bd5538 --- /dev/null +++ b/src/base/hi_res_timer_manager.h
@@ -0,0 +1,34 @@ +// 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. + +#ifndef BASE_HI_RES_TIMER_MANAGER_H_ +#define BASE_HI_RES_TIMER_MANAGER_H_ + +#include "base/base_export.h" +#include "base/system_monitor/system_monitor.h" + +// Ensures that the Windows high resolution timer is only used +// when not running on battery power. +class BASE_EXPORT HighResolutionTimerManager + : public base::SystemMonitor::PowerObserver { + public: + HighResolutionTimerManager(); + virtual ~HighResolutionTimerManager(); + + // base::SystemMonitor::PowerObserver: + virtual void OnPowerStateChange(bool on_battery_power) OVERRIDE; + + // Returns true if the hi resolution clock could be used right now. + bool hi_res_clock_available() const { return hi_res_clock_available_; } + + private: + // Enable or disable the faster multimedia timer. + void UseHiResClock(bool use); + + bool hi_res_clock_available_; + + DISALLOW_COPY_AND_ASSIGN(HighResolutionTimerManager); +}; + +#endif // BASE_HI_RES_TIMER_MANAGER_H_
diff --git a/src/base/hi_res_timer_manager_posix.cc b/src/base/hi_res_timer_manager_posix.cc new file mode 100644 index 0000000..7c16eb7 --- /dev/null +++ b/src/base/hi_res_timer_manager_posix.cc
@@ -0,0 +1,20 @@ +// 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/hi_res_timer_manager.h" + +// On POSIX we don't need to do anything special with the system timer. + +HighResolutionTimerManager::HighResolutionTimerManager() + : hi_res_clock_available_(false) { +} + +HighResolutionTimerManager::~HighResolutionTimerManager() { +} + +void HighResolutionTimerManager::OnPowerStateChange(bool on_battery_power) { +} + +void HighResolutionTimerManager::UseHiResClock(bool use) { +}
diff --git a/src/base/hi_res_timer_manager_unittest.cc b/src/base/hi_res_timer_manager_unittest.cc new file mode 100644 index 0000000..07651ce --- /dev/null +++ b/src/base/hi_res_timer_manager_unittest.cc
@@ -0,0 +1,52 @@ +// 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/hi_res_timer_manager.h" + +#include "base/memory/scoped_ptr.h" +#include "base/system_monitor/system_monitor.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +// http://crbug.com/114048 +TEST(HiResTimerManagerTest, DISABLED_ToggleOnOff) { + MessageLoop loop; + scoped_ptr<base::SystemMonitor> system_monitor(new base::SystemMonitor()); + HighResolutionTimerManager manager; + + // At this point, we don't know if the high resolution timers are on or off, + // it depends on what system the tests are running on (for example, if this + // test is running on a laptop/battery, then the SystemMonitor would have + // already set the PowerState to battery power; but if we're running on a + // desktop, then the PowerState will be non-battery power). Simulate a power + // level change to get to a deterministic state. + manager.OnPowerStateChange(/* on_battery */ false); + + // Loop a few times to test power toggling. + for (int loop = 2; loop >= 0; --loop) { + // The manager has the high resolution clock enabled now. + EXPECT_TRUE(manager.hi_res_clock_available()); + // But the Time class has it off, because it hasn't been activated. + EXPECT_FALSE(base::Time::IsHighResolutionTimerInUse()); + + // Activate the high resolution timer. + base::Time::ActivateHighResolutionTimer(true); + EXPECT_TRUE(base::Time::IsHighResolutionTimerInUse()); + + // Simulate a on-battery power event. + manager.OnPowerStateChange(/* on_battery */ true); + EXPECT_FALSE(manager.hi_res_clock_available()); + EXPECT_FALSE(base::Time::IsHighResolutionTimerInUse()); + + // Simulate a off-battery power event. + manager.OnPowerStateChange(/* on_battery */ false); + EXPECT_TRUE(manager.hi_res_clock_available()); + EXPECT_TRUE(base::Time::IsHighResolutionTimerInUse()); + + // De-activate the high resolution timer. + base::Time::ActivateHighResolutionTimer(false); + } +} +#endif // defined(OS_WIN)
diff --git a/src/base/hi_res_timer_manager_win.cc b/src/base/hi_res_timer_manager_win.cc new file mode 100644 index 0000000..1a92394 --- /dev/null +++ b/src/base/hi_res_timer_manager_win.cc
@@ -0,0 +1,30 @@ +// 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/hi_res_timer_manager.h" + +#include "base/time.h" + +HighResolutionTimerManager::HighResolutionTimerManager() + : hi_res_clock_available_(false) { + base::SystemMonitor* system_monitor = base::SystemMonitor::Get(); + system_monitor->AddPowerObserver(this); + UseHiResClock(!system_monitor->BatteryPower()); +} + +HighResolutionTimerManager::~HighResolutionTimerManager() { + base::SystemMonitor::Get()->RemovePowerObserver(this); + UseHiResClock(false); +} + +void HighResolutionTimerManager::OnPowerStateChange(bool on_battery_power) { + UseHiResClock(!on_battery_power); +} + +void HighResolutionTimerManager::UseHiResClock(bool use) { + if (use == hi_res_clock_available_) + return; + hi_res_clock_available_ = use; + base::Time::EnableHighResolutionTimer(use); +}
diff --git a/src/base/i18n/base_i18n_export.h b/src/base/i18n/base_i18n_export.h new file mode 100644 index 0000000..7ef4ee8 --- /dev/null +++ b/src/base/i18n/base_i18n_export.h
@@ -0,0 +1,29 @@ +// 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. + +#ifndef BASE_I18N_BASE_I18N_EXPORT_H_ +#define BASE_I18N_BASE_I18N_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(_MSC_VER) + +#if defined(BASE_I18N_IMPLEMENTATION) +#define BASE_I18N_EXPORT __declspec(dllexport) +#else +#define BASE_I18N_EXPORT __declspec(dllimport) +#endif // defined(BASE_I18N_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(BASE_I18N_IMPLEMENTATION) +#define BASE_I18N_EXPORT __attribute__((visibility("default"))) +#else +#define BASE_I18N_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define BASE_I18N_EXPORT +#endif + +#endif // BASE_I18N_BASE_I18N_EXPORT_H_
diff --git a/src/base/i18n/bidi_line_iterator.cc b/src/base/i18n/bidi_line_iterator.cc new file mode 100644 index 0000000..6251185 --- /dev/null +++ b/src/base/i18n/bidi_line_iterator.cc
@@ -0,0 +1,74 @@ +// 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/i18n/bidi_line_iterator.h" + +#include "base/logging.h" + +namespace base { +namespace i18n { + +namespace { + UBiDiLevel GetParagraphLevelForDirection(TextDirection direction) { + switch (direction) { + case UNKNOWN_DIRECTION: + return UBIDI_DEFAULT_LTR; + break; + case RIGHT_TO_LEFT: + return 1; // Highest RTL level. + break; + case LEFT_TO_RIGHT: + return 0; // Highest LTR level. + break; + default: + NOTREACHED(); + return 0; + } + } +} // namespace + +BiDiLineIterator::BiDiLineIterator() : bidi_(NULL) { +} + +BiDiLineIterator::~BiDiLineIterator() { + if (bidi_) { + ubidi_close(bidi_); + bidi_ = NULL; + } +} + +bool BiDiLineIterator::Open(const string16& text, TextDirection direction) { + DCHECK(!bidi_); + UErrorCode error = U_ZERO_ERROR; + bidi_ = ubidi_openSized(static_cast<int>(text.length()), 0, &error); + if (U_FAILURE(error)) + return false; + ubidi_setPara(bidi_, text.data(), static_cast<int>(text.length()), + GetParagraphLevelForDirection(direction), NULL, &error); + return (U_SUCCESS(error) == TRUE); +} + +int BiDiLineIterator::CountRuns() { + DCHECK(bidi_ != NULL); + UErrorCode error = U_ZERO_ERROR; + const int runs = ubidi_countRuns(bidi_, &error); + return U_SUCCESS(error) ? runs : 0; +} + +UBiDiDirection BiDiLineIterator::GetVisualRun(int index, + int* start, + int* length) { + DCHECK(bidi_ != NULL); + return ubidi_getVisualRun(bidi_, index, start, length); +} + +void BiDiLineIterator::GetLogicalRun(int start, + int* end, + UBiDiLevel* level) { + DCHECK(bidi_ != NULL); + ubidi_getLogicalRun(bidi_, start, end, level); +} + +} // namespace i18n +} // namespace base
diff --git a/src/base/i18n/bidi_line_iterator.h b/src/base/i18n/bidi_line_iterator.h new file mode 100644 index 0000000..8e1217b --- /dev/null +++ b/src/base/i18n/bidi_line_iterator.h
@@ -0,0 +1,48 @@ +// 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. + +#ifndef BASE_I18N_BIDI_LINE_ITERATOR_H_ +#define BASE_I18N_BIDI_LINE_ITERATOR_H_ + +#include "unicode/ubidi.h" + +#include "base/basictypes.h" +#include "base/i18n/base_i18n_export.h" +#include "base/i18n/rtl.h" +#include "base/string16.h" + +namespace base { +namespace i18n { + +// A simple wrapper class for the bidirectional iterator of ICU. +// This class uses the bidirectional iterator of ICU to split a line of +// bidirectional texts into visual runs in its display order. +class BASE_I18N_EXPORT BiDiLineIterator { + public: + BiDiLineIterator(); + ~BiDiLineIterator(); + + // Initializes the bidirectional iterator with the specified text. Returns + // whether initialization succeeded. + bool Open(const string16& text, TextDirection direction); + + // Returns the number of visual runs in the text, or zero on error. + int CountRuns(); + + // Gets the logical offset, length, and direction of the specified visual run. + UBiDiDirection GetVisualRun(int index, int* start, int* length); + + // Given a start position, figure out where the run ends (and the BiDiLevel). + void GetLogicalRun(int start, int* end, UBiDiLevel* level); + + private: + UBiDi* bidi_; + + DISALLOW_COPY_AND_ASSIGN(BiDiLineIterator); +}; + +} // namespace i18n +} // namespace base + +#endif // BASE_I18N_BIDI_LINE_ITERATOR_H_
diff --git a/src/base/i18n/break_iterator.cc b/src/base/i18n/break_iterator.cc new file mode 100644 index 0000000..339de3d --- /dev/null +++ b/src/base/i18n/break_iterator.cc
@@ -0,0 +1,126 @@ +// 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/i18n/break_iterator.h" + +#include "base/logging.h" +#include "unicode/ubrk.h" +#include "unicode/uchar.h" +#include "unicode/ustring.h" + +namespace base { +namespace i18n { + +const size_t npos = -1; + +BreakIterator::BreakIterator(const string16& str, BreakType break_type) + : iter_(NULL), + string_(str), + break_type_(break_type), + prev_(npos), + pos_(0) { +} + +BreakIterator::~BreakIterator() { + if (iter_) + ubrk_close(static_cast<UBreakIterator*>(iter_)); +} + +bool BreakIterator::Init() { + UErrorCode status = U_ZERO_ERROR; + UBreakIteratorType break_type; + switch (break_type_) { + case BREAK_CHARACTER: + break_type = UBRK_CHARACTER; + break; + case BREAK_WORD: + break_type = UBRK_WORD; + break; + case BREAK_LINE: + case BREAK_NEWLINE: + break_type = UBRK_LINE; + break; + default: + NOTREACHED() << "invalid break_type_"; + return false; + } + iter_ = ubrk_open(break_type, NULL, + string_.data(), static_cast<int32_t>(string_.size()), + &status); + if (U_FAILURE(status)) { + NOTREACHED() << "ubrk_open failed: " << u_errorName(status); + return false; + } + // Move the iterator to the beginning of the string. + ubrk_first(static_cast<UBreakIterator*>(iter_)); + return true; +} + +bool BreakIterator::Advance() { + int32_t pos; + int32_t status; + prev_ = pos_; + switch (break_type_) { + case BREAK_CHARACTER: + case BREAK_WORD: + case BREAK_LINE: + pos = ubrk_next(static_cast<UBreakIterator*>(iter_)); + if (pos == UBRK_DONE) { + pos_ = npos; + return false; + } + pos_ = static_cast<size_t>(pos); + return true; + case BREAK_NEWLINE: + do { + pos = ubrk_next(static_cast<UBreakIterator*>(iter_)); + if (pos == UBRK_DONE) + break; + pos_ = static_cast<size_t>(pos); + status = ubrk_getRuleStatus(static_cast<UBreakIterator*>(iter_)); + } while (status >= UBRK_LINE_SOFT && status < UBRK_LINE_SOFT_LIMIT); + if (pos == UBRK_DONE && prev_ == pos_) { + pos_ = npos; + return false; + } + return true; + default: + NOTREACHED() << "invalid break_type_"; + return false; + } +} + +bool BreakIterator::IsWord() const { + int32_t status = ubrk_getRuleStatus(static_cast<UBreakIterator*>(iter_)); + return (break_type_ == BREAK_WORD && status != UBRK_WORD_NONE); +} + +bool BreakIterator::IsEndOfWord(size_t position) const { + if (break_type_ != BREAK_WORD) + return false; + + UBreakIterator* iter = static_cast<UBreakIterator*>(iter_); + UBool boundary = ubrk_isBoundary(iter, static_cast<int32_t>(position)); + int32_t status = ubrk_getRuleStatus(iter); + return (!!boundary && status != UBRK_WORD_NONE); +} + +bool BreakIterator::IsStartOfWord(size_t position) const { + if (break_type_ != BREAK_WORD) + return false; + + UBreakIterator* iter = static_cast<UBreakIterator*>(iter_); + UBool boundary = ubrk_isBoundary(iter, static_cast<int32_t>(position)); + ubrk_next(iter); + int32_t next_status = ubrk_getRuleStatus(iter); + return (!!boundary && next_status != UBRK_WORD_NONE); +} + +string16 BreakIterator::GetString() const { + DCHECK(prev_ != npos && pos_ != npos); + return string_.substr(prev_, pos_ - prev_); +} + +} // namespace i18n +} // namespace base
diff --git a/src/base/i18n/break_iterator.h b/src/base/i18n/break_iterator.h new file mode 100644 index 0000000..d558e23 --- /dev/null +++ b/src/base/i18n/break_iterator.h
@@ -0,0 +1,131 @@ +// 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. + +#ifndef BASE_I18N_BREAK_ITERATOR_H_ +#define BASE_I18N_BREAK_ITERATOR_H_ + +#include "base/basictypes.h" +#include "base/string16.h" +#include "base/i18n/base_i18n_export.h" + +// The BreakIterator class iterates through the words, word breaks, and +// line breaks in a UTF-16 string. +// +// It provides several modes, BREAK_WORD, BREAK_LINE, and BREAK_NEWLINE, +// which modify how characters are aggregated into the returned string. +// +// Under BREAK_WORD mode, once a word is encountered any non-word +// characters are not included in the returned string (e.g. in the +// UTF-16 equivalent of the string " foo bar! ", the word breaks are at +// the periods in ". .foo. .bar.!. ."). +// Note that Chinese/Japanese/Thai do not use spaces between words so that +// boundaries can fall in the middle of a continuous run of non-space / +// non-punctuation characters. +// +// Under BREAK_LINE mode, once a line breaking opportunity is encountered, +// any non-word characters are included in the returned string, breaking +// only when a space-equivalent character or a line breaking opportunity +// is encountered (e.g. in the UTF16-equivalent of the string " foo bar! ", +// the breaks are at the periods in ". .foo .bar! ."). +// +// Note that lines can be broken at any character/syllable/grapheme cluster +// boundary in Chinese/Japanese/Korean and at word boundaries in Thai +// (Thai does not use spaces between words). Therefore, this is NOT the same +// as breaking only at space-equivalent characters where its former +// 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 +// (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."). +// +// To extract the words from a string, move a BREAK_WORD BreakIterator +// through the string and test whether IsWord() is true. E.g., +// BreakIterator iter(str, BreakIterator::BREAK_WORD); +// if (!iter.Init()) +// return false; +// while (iter.Advance()) { +// if (iter.IsWord()) { +// // Region [iter.prev(), iter.pos()) contains a word. +// VLOG(1) << "word: " << iter.GetString(); +// } +// } + +namespace base { +namespace i18n { + +class BASE_I18N_EXPORT BreakIterator { + public: + enum BreakType { + BREAK_WORD, + BREAK_LINE, + // TODO(jshin): Remove this after reviewing call sites. + // If call sites really need break only on space-like characters + // implement it separately. + BREAK_SPACE = BREAK_LINE, + BREAK_NEWLINE, + BREAK_CHARACTER, + }; + + // Requires |str| to live as long as the BreakIterator does. + BreakIterator(const string16& str, BreakType break_type); + ~BreakIterator(); + + // Init() must be called before any of the iterators are valid. + // Returns false if ICU failed to initialize. + bool Init(); + + // Advance to the next break. Returns false if we've run past the end of + // the string. (Note that the very last "break" is after the final + // character in the string, and when we advance to that position it's the + // last time Advance() returns true.) + bool Advance(); + + // 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. + 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 + // BREAK_NEWLINE modes. + bool IsEndOfWord(size_t position) const; + bool IsStartOfWord(size_t position) const; + + // Returns the string between prev() and pos(). + // Advance() must have been called successfully at least once for pos() to + // have advanced to somewhere useful. + string16 GetString() const; + + // Returns the value of pos() returned before Advance() was last called. + size_t prev() const { return prev_; } + + // Returns the current break position within the string, + // or BreakIterator::npos when done. + size_t pos() const { return pos_; } + + private: + // ICU iterator, avoiding ICU ubrk.h dependence. + // This is actually an ICU UBreakiterator* type, which turns out to be + // a typedef for a void* in the ICU headers. Using void* directly prevents + // callers from needing access to the ICU public headers directory. + void* iter_; + + // The string we're iterating over. + const string16& string_; + + // The breaking style (word/space/newline). + BreakType break_type_; + + // Previous and current iterator positions. + size_t prev_, pos_; + + DISALLOW_COPY_AND_ASSIGN(BreakIterator); +}; + +} // namespace i18n +} // namespace base + +#endif // BASE_I18N_BREAK_ITERATOR_H_
diff --git a/src/base/i18n/break_iterator_unittest.cc b/src/base/i18n/break_iterator_unittest.cc new file mode 100644 index 0000000..afb780c --- /dev/null +++ b/src/base/i18n/break_iterator_unittest.cc
@@ -0,0 +1,338 @@ +// 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/i18n/break_iterator.h" + +#include "base/string_piece.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace i18n { + +TEST(BreakIteratorTest, BreakWordEmpty) { + string16 empty; + BreakIterator iter(empty, BreakIterator::BREAK_WORD); + ASSERT_TRUE(iter.Init()); + EXPECT_FALSE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end. + EXPECT_FALSE(iter.IsWord()); +} + +TEST(BreakIteratorTest, BreakWord) { + string16 space(UTF8ToUTF16(" ")); + string16 str(UTF8ToUTF16(" foo bar! \npouet boom")); + BreakIterator iter(str, BreakIterator::BREAK_WORD); + ASSERT_TRUE(iter.Init()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(space, iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_TRUE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("foo"), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(space, iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_TRUE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("bar"), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("!"), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(space, iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("\n"), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_TRUE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("pouet"), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(space, iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_TRUE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("boom"), iter.GetString()); + EXPECT_FALSE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end. + EXPECT_FALSE(iter.IsWord()); +} + +TEST(BreakIteratorTest, BreakWide16) { + // Two greek words separated by space. + const string16 str(WideToUTF16( + L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9" + L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2")); + const string16 word1(str.substr(0, 10)); + const string16 word2(str.substr(11, 5)); + BreakIterator iter(str, BreakIterator::BREAK_WORD); + ASSERT_TRUE(iter.Init()); + EXPECT_TRUE(iter.Advance()); + EXPECT_TRUE(iter.IsWord()); + EXPECT_EQ(word1, iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16(" "), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_TRUE(iter.IsWord()); + EXPECT_EQ(word2, iter.GetString()); + EXPECT_FALSE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end. + EXPECT_FALSE(iter.IsWord()); +} + +TEST(BreakIteratorTest, BreakWide32) { + // U+1D49C MATHEMATICAL SCRIPT CAPITAL A + const char* very_wide_char = "\xF0\x9D\x92\x9C"; + const string16 str( + UTF8ToUTF16(base::StringPrintf("%s a", very_wide_char))); + const string16 very_wide_word(str.substr(0, 2)); + + BreakIterator iter(str, BreakIterator::BREAK_WORD); + ASSERT_TRUE(iter.Init()); + EXPECT_TRUE(iter.Advance()); + EXPECT_TRUE(iter.IsWord()); + EXPECT_EQ(very_wide_word, iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16(" "), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_TRUE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("a"), iter.GetString()); + EXPECT_FALSE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end. + EXPECT_FALSE(iter.IsWord()); +} + +TEST(BreakIteratorTest, BreakSpaceEmpty) { + string16 empty; + BreakIterator iter(empty, BreakIterator::BREAK_SPACE); + ASSERT_TRUE(iter.Init()); + EXPECT_FALSE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end. + EXPECT_FALSE(iter.IsWord()); +} + +TEST(BreakIteratorTest, BreakSpace) { + string16 str(UTF8ToUTF16(" foo bar! \npouet boom")); + BreakIterator iter(str, BreakIterator::BREAK_SPACE); + ASSERT_TRUE(iter.Init()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16(" "), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("foo "), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("bar! \n"), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("pouet "), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("boom"), iter.GetString()); + EXPECT_FALSE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end. + EXPECT_FALSE(iter.IsWord()); +} + +TEST(BreakIteratorTest, BreakSpaceSP) { + string16 str(UTF8ToUTF16(" foo bar! \npouet boom ")); + BreakIterator iter(str, BreakIterator::BREAK_SPACE); + ASSERT_TRUE(iter.Init()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16(" "), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("foo "), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("bar! \n"), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("pouet "), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("boom "), iter.GetString()); + EXPECT_FALSE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end. + EXPECT_FALSE(iter.IsWord()); +} + +TEST(BreakIteratorTest, BreakSpacekWide16) { + // Two Greek words. + const string16 str(WideToUTF16( + L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9" + L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2")); + const string16 word1(str.substr(0, 11)); + const string16 word2(str.substr(11, 5)); + BreakIterator iter(str, BreakIterator::BREAK_SPACE); + ASSERT_TRUE(iter.Init()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(word1, iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(word2, iter.GetString()); + EXPECT_FALSE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end. + EXPECT_FALSE(iter.IsWord()); +} + +TEST(BreakIteratorTest, BreakSpaceWide32) { + // U+1D49C MATHEMATICAL SCRIPT CAPITAL A + const char* very_wide_char = "\xF0\x9D\x92\x9C"; + const string16 str( + UTF8ToUTF16(base::StringPrintf("%s a", very_wide_char))); + const string16 very_wide_word(str.substr(0, 3)); + + BreakIterator iter(str, BreakIterator::BREAK_SPACE); + ASSERT_TRUE(iter.Init()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(very_wide_word, iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("a"), iter.GetString()); + EXPECT_FALSE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end. + EXPECT_FALSE(iter.IsWord()); +} + +TEST(BreakIteratorTest, BreakLineEmpty) { + string16 empty; + BreakIterator iter(empty, BreakIterator::BREAK_NEWLINE); + ASSERT_TRUE(iter.Init()); + EXPECT_FALSE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end. + EXPECT_FALSE(iter.IsWord()); +} + +TEST(BreakIteratorTest, BreakLine) { + string16 nl(UTF8ToUTF16("\n")); + string16 str(UTF8ToUTF16("\nfoo bar!\n\npouet boom")); + BreakIterator iter(str, BreakIterator::BREAK_NEWLINE); + ASSERT_TRUE(iter.Init()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(nl, iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("foo bar!\n"), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(nl, iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("pouet boom"), iter.GetString()); + EXPECT_FALSE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end. + EXPECT_FALSE(iter.IsWord()); +} + +TEST(BreakIteratorTest, BreakLineNL) { + string16 nl(UTF8ToUTF16("\n")); + string16 str(UTF8ToUTF16("\nfoo bar!\n\npouet boom\n")); + BreakIterator iter(str, BreakIterator::BREAK_NEWLINE); + ASSERT_TRUE(iter.Init()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(nl, iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("foo bar!\n"), iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(nl, iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("pouet boom\n"), iter.GetString()); + EXPECT_FALSE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end. + EXPECT_FALSE(iter.IsWord()); +} + +TEST(BreakIteratorTest, BreakLineWide16) { + // Two Greek words separated by newline. + const string16 str(WideToUTF16( + L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9" + L"\x03bf\x03c2\x000a\x0399\x03c3\x03c4\x03cc\x03c2")); + const string16 line1(str.substr(0, 11)); + const string16 line2(str.substr(11, 5)); + BreakIterator iter(str, BreakIterator::BREAK_NEWLINE); + ASSERT_TRUE(iter.Init()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(line1, iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(line2, iter.GetString()); + EXPECT_FALSE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end. + EXPECT_FALSE(iter.IsWord()); +} + +TEST(BreakIteratorTest, BreakLineWide32) { + // U+1D49C MATHEMATICAL SCRIPT CAPITAL A + const char* very_wide_char = "\xF0\x9D\x92\x9C"; + const string16 str( + UTF8ToUTF16(base::StringPrintf("%s\na", very_wide_char))); + const string16 very_wide_line(str.substr(0, 3)); + BreakIterator iter(str, BreakIterator::BREAK_NEWLINE); + ASSERT_TRUE(iter.Init()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(very_wide_line, iter.GetString()); + EXPECT_TRUE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_EQ(UTF8ToUTF16("a"), iter.GetString()); + EXPECT_FALSE(iter.Advance()); + EXPECT_FALSE(iter.IsWord()); + EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end. + EXPECT_FALSE(iter.IsWord()); +} + +TEST(BreakIteratorTest, BreakCharacter) { + static const wchar_t* kCharacters[] = { + // An English word consisting of four ASCII characters. + L"w", L"o", L"r", L"d", L" ", + // A Hindi word (which means "Hindi") consisting of three Devanagari + // characters. + L"\x0939\x093F", L"\x0928\x094D", L"\x0926\x0940", L" ", + // A Thai word (which means "feel") consisting of three Thai characters. + L"\x0E23\x0E39\x0E49", L"\x0E2A\x0E36", L"\x0E01", L" ", + }; + std::vector<string16> characters; + string16 text; + for (size_t i = 0; i < arraysize(kCharacters); ++i) { + characters.push_back(WideToUTF16(kCharacters[i])); + text.append(characters.back()); + } + BreakIterator iter(text, BreakIterator::BREAK_CHARACTER); + ASSERT_TRUE(iter.Init()); + for (size_t i = 0; i < arraysize(kCharacters); ++i) { + EXPECT_TRUE(iter.Advance()); + EXPECT_EQ(characters[i], iter.GetString()); + } +} + +} // namespace i18n +} // namespace base
diff --git a/src/base/i18n/case_conversion.cc b/src/base/i18n/case_conversion.cc new file mode 100644 index 0000000..d3b06c9 --- /dev/null +++ b/src/base/i18n/case_conversion.cc
@@ -0,0 +1,26 @@ +// 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/i18n/case_conversion.h" + +#include "base/string16.h" +#include "unicode/unistr.h" + +namespace base { +namespace i18n { + +string16 ToLower(const StringPiece16& string) { + icu::UnicodeString unicode_string(string.data(), string.size()); + unicode_string.toLower(); + return string16(unicode_string.getBuffer(), unicode_string.length()); +} + +string16 ToUpper(const StringPiece16& string) { + icu::UnicodeString unicode_string(string.data(), string.size()); + unicode_string.toUpper(); + return string16(unicode_string.getBuffer(), unicode_string.length()); +} + +} // namespace i18n +} // namespace base
diff --git a/src/base/i18n/case_conversion.h b/src/base/i18n/case_conversion.h new file mode 100644 index 0000000..14fef00 --- /dev/null +++ b/src/base/i18n/case_conversion.h
@@ -0,0 +1,24 @@ +// 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. + +#ifndef BASE_I18N_CASE_CONVERSION_H_ +#define BASE_I18N_CASE_CONVERSION_H_ + +#include "base/i18n/base_i18n_export.h" +#include "base/string16.h" +#include "base/string_piece.h" + +namespace base { +namespace i18n { + +// Returns the lower case equivalent of string. Uses ICU's default locale. +BASE_I18N_EXPORT string16 ToLower(const StringPiece16& string); + +// Returns the upper case equivalent of string. Uses ICU's default locale. +BASE_I18N_EXPORT string16 ToUpper(const StringPiece16& string); + +} // namespace i18n +} // namespace base + +#endif // BASE_I18N_CASE_CONVERSION_H_
diff --git a/src/base/i18n/case_conversion_unittest.cc b/src/base/i18n/case_conversion_unittest.cc new file mode 100644 index 0000000..63900ec --- /dev/null +++ b/src/base/i18n/case_conversion_unittest.cc
@@ -0,0 +1,26 @@ +// 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/i18n/case_conversion.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Test upper and lower case string conversion. +TEST(CaseConversionTest, UpperLower) { + string16 mixed(ASCIIToUTF16("Text with UPPer & lowER casE.")); + const string16 expected_lower(ASCIIToUTF16("text with upper & lower case.")); + const string16 expected_upper(ASCIIToUTF16("TEXT WITH UPPER & LOWER CASE.")); + + string16 result = base::i18n::ToLower(mixed); + EXPECT_EQ(expected_lower, result); + + result = base::i18n::ToUpper(mixed); + EXPECT_EQ(expected_upper, result); +} + +// TODO(jshin): More tests are needed, especially with non-ASCII characters. + +} // namespace
diff --git a/src/base/i18n/char_iterator.cc b/src/base/i18n/char_iterator.cc new file mode 100644 index 0000000..ce4d513 --- /dev/null +++ b/src/base/i18n/char_iterator.cc
@@ -0,0 +1,82 @@ +// 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/i18n/char_iterator.h" + +#include "unicode/utf8.h" +#include "unicode/utf16.h" + +namespace base { +namespace i18n { + +UTF8CharIterator::UTF8CharIterator(const std::string* str) + : str_(reinterpret_cast<const uint8_t*>(str->data())), + len_(str->size()), + array_pos_(0), + next_pos_(0), + char_pos_(0), + char_(0) { + if (len_) + U8_NEXT(str_, next_pos_, len_, char_); +} + +UTF8CharIterator::~UTF8CharIterator() { +} + +bool UTF8CharIterator::Advance() { + if (array_pos_ >= len_) + return false; + + array_pos_ = next_pos_; + char_pos_++; + if (next_pos_ < len_) + U8_NEXT(str_, next_pos_, len_, char_); + + return true; +} + +UTF16CharIterator::UTF16CharIterator(const string16* str) + : str_(reinterpret_cast<const char16*>(str->data())), + len_(str->size()), + array_pos_(0), + next_pos_(0), + char_pos_(0), + char_(0) { + if (len_) + ReadChar(); +} + +UTF16CharIterator::UTF16CharIterator(const char16* str, size_t str_len) + : str_(str), + len_(str_len), + array_pos_(0), + next_pos_(0), + char_pos_(0), + char_(0) { + if (len_) + ReadChar(); +} + +UTF16CharIterator::~UTF16CharIterator() { +} + +bool UTF16CharIterator::Advance() { + if (array_pos_ >= len_) + return false; + + array_pos_ = next_pos_; + char_pos_++; + if (next_pos_ < len_) + ReadChar(); + + return true; +} + +void UTF16CharIterator::ReadChar() { + // This is actually a huge macro, so is worth having in a separate function. + U16_NEXT(str_, next_pos_, len_, char_); +} + +} // namespace i18n +} // namespace base
diff --git a/src/base/i18n/char_iterator.h b/src/base/i18n/char_iterator.h new file mode 100644 index 0000000..431e350 --- /dev/null +++ b/src/base/i18n/char_iterator.h
@@ -0,0 +1,130 @@ +// 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. + +#ifndef BASE_I18N_CHAR_ITERATOR_H_ +#define BASE_I18N_CHAR_ITERATOR_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/i18n/base_i18n_export.h" +#include "base/string16.h" + +// The CharIterator classes iterate through the characters in UTF8 and +// UTF16 strings. Example usage: +// +// UTF8CharIterator iter(&str); +// while (!iter.End()) { +// VLOG(1) << iter.get(); +// iter.Advance(); +// } + +#if defined(OS_WIN) +typedef unsigned char uint8_t; +#endif + +namespace base { +namespace i18n { + +class BASE_I18N_EXPORT UTF8CharIterator { + public: + // Requires |str| to live as long as the UTF8CharIterator does. + UTF8CharIterator(const std::string* str); + ~UTF8CharIterator(); + + // Return the starting array index of the current character within the + // string. + int32 array_pos() const { return array_pos_; } + + // Return the logical index of the current character, independent of the + // number of bytes each character takes. + int32 char_pos() const { return char_pos_; } + + // Return the current char. + int32 get() const { return char_; } + + // Returns true if we're at the end of the string. + bool end() const { return array_pos_ == len_; } + + // Advance to the next actual character. Returns false if we're at the + // end of the string. + bool Advance(); + + private: + // The string we're iterating over. + const uint8_t* str_; + + // The length of the encoded string. + int32 len_; + + // Array index. + int32 array_pos_; + + // The next array index. + int32 next_pos_; + + // Character index. + int32 char_pos_; + + // The current character. + int32 char_; + + DISALLOW_COPY_AND_ASSIGN(UTF8CharIterator); +}; + +class BASE_I18N_EXPORT UTF16CharIterator { + public: + // Requires |str| to live as long as the UTF16CharIterator does. + UTF16CharIterator(const string16* str); + UTF16CharIterator(const char16* str, size_t str_len); + ~UTF16CharIterator(); + + // Return the starting array index of the current character within the + // string. + int32 array_pos() const { return array_pos_; } + + // Return the logical index of the current character, independent of the + // number of codewords each character takes. + int32 char_pos() const { return char_pos_; } + + // Return the current char. + int32 get() const { return char_; } + + // Returns true if we're at the end of the string. + bool end() const { return array_pos_ == len_; } + + // Advance to the next actual character. Returns false if we're at the + // end of the string. + bool Advance(); + + private: + // Fills in the current character we found and advances to the next + // character, updating all flags as necessary. + void ReadChar(); + + // The string we're iterating over. + const char16* str_; + + // The length of the encoded string. + int32 len_; + + // Array index. + int32 array_pos_; + + // The next array index. + int32 next_pos_; + + // Character index. + int32 char_pos_; + + // The current character. + int32 char_; + + DISALLOW_COPY_AND_ASSIGN(UTF16CharIterator); +}; + +} // namespace i18n +} // namespace base + +#endif // BASE_I18N_CHAR_ITERATOR_H_
diff --git a/src/base/i18n/char_iterator_unittest.cc b/src/base/i18n/char_iterator_unittest.cc new file mode 100644 index 0000000..6d1294e --- /dev/null +++ b/src/base/i18n/char_iterator_unittest.cc
@@ -0,0 +1,101 @@ +// 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/i18n/char_iterator.h" + +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace i18n { + +TEST(CharIteratorsTest, TestUTF8) { + std::string empty(""); + UTF8CharIterator empty_iter(&empty); + ASSERT_TRUE(empty_iter.end()); + ASSERT_EQ(0, empty_iter.array_pos()); + ASSERT_EQ(0, empty_iter.char_pos()); + ASSERT_FALSE(empty_iter.Advance()); + + std::string str("s\303\273r"); // [u with circumflex] + UTF8CharIterator iter(&str); + ASSERT_FALSE(iter.end()); + ASSERT_EQ(0, iter.array_pos()); + ASSERT_EQ(0, iter.char_pos()); + ASSERT_EQ('s', iter.get()); + ASSERT_TRUE(iter.Advance()); + + ASSERT_FALSE(iter.end()); + ASSERT_EQ(1, iter.array_pos()); + ASSERT_EQ(1, iter.char_pos()); + ASSERT_EQ(251, iter.get()); + ASSERT_TRUE(iter.Advance()); + + ASSERT_FALSE(iter.end()); + ASSERT_EQ(3, iter.array_pos()); + ASSERT_EQ(2, iter.char_pos()); + ASSERT_EQ('r', iter.get()); + ASSERT_TRUE(iter.Advance()); + + ASSERT_TRUE(iter.end()); + ASSERT_EQ(4, iter.array_pos()); + ASSERT_EQ(3, iter.char_pos()); + + // Don't care what it returns, but this shouldn't crash + iter.get(); + + ASSERT_FALSE(iter.Advance()); +} + +TEST(CharIteratorsTest, TestUTF16) { + string16 empty = UTF8ToUTF16(""); + UTF16CharIterator empty_iter(&empty); + ASSERT_TRUE(empty_iter.end()); + ASSERT_EQ(0, empty_iter.array_pos()); + ASSERT_EQ(0, empty_iter.char_pos()); + ASSERT_FALSE(empty_iter.Advance()); + + // This test string contains 4 characters: + // x + // u with circumflex - 2 bytes in UTF8, 1 codeword in UTF16 + // math double-struck A - 4 bytes in UTF8, 2 codewords in UTF16 + // z + string16 str = UTF8ToUTF16("x\303\273\360\235\224\270z"); + UTF16CharIterator iter(&str); + ASSERT_FALSE(iter.end()); + ASSERT_EQ(0, iter.array_pos()); + ASSERT_EQ(0, iter.char_pos()); + ASSERT_EQ('x', iter.get()); + ASSERT_TRUE(iter.Advance()); + + ASSERT_FALSE(iter.end()); + ASSERT_EQ(1, iter.array_pos()); + ASSERT_EQ(1, iter.char_pos()); + ASSERT_EQ(251, iter.get()); + ASSERT_TRUE(iter.Advance()); + + ASSERT_FALSE(iter.end()); + ASSERT_EQ(2, iter.array_pos()); + ASSERT_EQ(2, iter.char_pos()); + ASSERT_EQ(120120, iter.get()); + ASSERT_TRUE(iter.Advance()); + + ASSERT_FALSE(iter.end()); + ASSERT_EQ(4, iter.array_pos()); + ASSERT_EQ(3, iter.char_pos()); + ASSERT_EQ('z', iter.get()); + ASSERT_TRUE(iter.Advance()); + + ASSERT_TRUE(iter.end()); + ASSERT_EQ(5, iter.array_pos()); + ASSERT_EQ(4, iter.char_pos()); + + // Don't care what it returns, but this shouldn't crash + iter.get(); + + ASSERT_FALSE(iter.Advance()); +} + +} // namespace i18n +} // namespace base
diff --git a/src/base/i18n/file_util_icu.cc b/src/base/i18n/file_util_icu.cc new file mode 100644 index 0000000..fc07d13 --- /dev/null +++ b/src/base/i18n/file_util_icu.cc
@@ -0,0 +1,214 @@ +// 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. + +// File utilities that use the ICU library go in this file. + +#include "base/i18n/file_util_icu.h" + +#include "base/file_path.h" +#include "base/i18n/icu_string_conversions.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/sys_string_conversions.h" +#include "build/build_config.h" +#include "unicode/coll.h" +#include "unicode/uniset.h" + +namespace { + +class IllegalCharacters { + public: + static IllegalCharacters* GetInstance() { + return Singleton<IllegalCharacters>::get(); + } + + bool contains(UChar32 ucs4) { + return !!set->contains(ucs4); + } + + bool containsNone(const string16 &s) { + return !!set->containsNone(icu::UnicodeString(s.c_str(), s.size())); + } + + private: + friend class Singleton<IllegalCharacters>; + friend struct DefaultSingletonTraits<IllegalCharacters>; + + IllegalCharacters(); + ~IllegalCharacters() { } + + scoped_ptr<icu::UnicodeSet> set; + + DISALLOW_COPY_AND_ASSIGN(IllegalCharacters); +}; + +IllegalCharacters::IllegalCharacters() { + UErrorCode status = U_ZERO_ERROR; + // Control characters, formatting characters, non-characters, and + // some printable ASCII characters regarded as dangerous ('"*/:<>?\\'). + // See http://blogs.msdn.com/michkap/archive/2006/11/03/941420.aspx + // and http://msdn2.microsoft.com/en-us/library/Aa365247.aspx + // TODO(jungshik): Revisit the set. ZWJ and ZWNJ are excluded because they + // are legitimate in Arabic and some S/SE Asian scripts. However, when used + // elsewhere, they can be confusing/problematic. + // Also, consider wrapping the set with our Singleton class to create and + // freeze it only once. Note that there's a trade-off between memory and + // speed. +#if defined(WCHAR_T_IS_UTF16) + set.reset(new icu::UnicodeSet(icu::UnicodeString( + L"[[\"*/:<>?\\\\|][:Cc:][:Cf:] - [\u200c\u200d]]"), status)); +#else + set.reset(new icu::UnicodeSet(UNICODE_STRING_SIMPLE( + "[[\"*/:<>?\\\\|][:Cc:][:Cf:] - [\\u200c\\u200d]]").unescape(), + status)); +#endif + DCHECK(U_SUCCESS(status)); + // Add non-characters. If this becomes a performance bottleneck by + // any chance, do not add these to |set| and change IsFilenameLegal() + // to check |ucs4 & 0xFFFEu == 0xFFFEu|, in addiition to calling + // containsNone(). + set->add(0xFDD0, 0xFDEF); + for (int i = 0; i <= 0x10; ++i) { + int plane_base = 0x10000 * i; + set->add(plane_base + 0xFFFE, plane_base + 0xFFFF); + } + set->freeze(); +} + +class LocaleAwareComparator { + public: + static LocaleAwareComparator* GetInstance() { + return Singleton<LocaleAwareComparator, + LeakySingletonTraits<LocaleAwareComparator> >::get(); + } + + // Note: A similar function is available in l10n_util. + // We cannot use it because base should not depend on l10n_util. + // TODO(yuzo): Move some of l10n_util to base. + int Compare(const string16& a, const string16& b) { + // We are not sure if Collator::compare is thread-safe. + // Use an AutoLock just in case. + base::AutoLock auto_lock(lock_); + + UErrorCode error_code = U_ZERO_ERROR; + UCollationResult result = collator_->compare( + static_cast<const UChar*>(a.c_str()), + static_cast<int>(a.length()), + static_cast<const UChar*>(b.c_str()), + static_cast<int>(b.length()), + error_code); + DCHECK(U_SUCCESS(error_code)); + return result; + } + + private: + friend struct DefaultSingletonTraits<LocaleAwareComparator>; + + LocaleAwareComparator() { + UErrorCode error_code = U_ZERO_ERROR; + // Use the default collator. The default locale should have been properly + // set by the time this constructor is called. + collator_.reset(icu::Collator::createInstance(error_code)); + DCHECK(U_SUCCESS(error_code)); + // Make it case-sensitive. + collator_->setStrength(icu::Collator::TERTIARY); + // Note: We do not set UCOL_NORMALIZATION_MODE attribute. In other words, we + // do not pay performance penalty to guarantee sort order correctness for + // non-FCD (http://unicode.org/notes/tn5/#FCD) file names. This should be a + // reasonable tradeoff because such file names should be rare and the sort + // order doesn't change much anyway. + } + + scoped_ptr<icu::Collator> collator_; + base::Lock lock_; + + DISALLOW_COPY_AND_ASSIGN(LocaleAwareComparator); +}; + +} // namespace + +namespace file_util { + +bool IsFilenameLegal(const string16& file_name) { + return IllegalCharacters::GetInstance()->containsNone(file_name); +} + +void ReplaceIllegalCharactersInPath(FilePath::StringType* file_name, + char replace_char) { + DCHECK(file_name); + + DCHECK(!(IllegalCharacters::GetInstance()->contains(replace_char))); + + // Remove leading and trailing whitespace. + TrimWhitespace(*file_name, TRIM_ALL, file_name); + + IllegalCharacters* illegal = IllegalCharacters::GetInstance(); + int cursor = 0; // The ICU macros expect an int. + while (cursor < static_cast<int>(file_name->size())) { + int char_begin = cursor; + uint32 code_point; +#if defined(OS_MACOSX) + // Mac uses UTF-8 encoding for filenames. + U8_NEXT(file_name->data(), cursor, static_cast<int>(file_name->length()), + code_point); +#elif defined(OS_WIN) + // Windows uses UTF-16 encoding for filenames. + U16_NEXT(file_name->data(), cursor, static_cast<int>(file_name->length()), + code_point); +#elif defined(OS_POSIX) || defined(OS_STARBAORD) + // Linux doesn't actually define an encoding. It basically allows anything + // except for a few special ASCII characters. + unsigned char cur_char = static_cast<unsigned char>((*file_name)[cursor++]); + if (cur_char >= 0x80) + continue; + code_point = cur_char; +#else + NOTREACHED(); +#endif + + if (illegal->contains(code_point)) { + file_name->replace(char_begin, cursor - char_begin, 1, replace_char); + // We just made the potentially multi-byte/word char into one that only + // takes one byte/word, so need to adjust the cursor to point to the next + // character again. + cursor = char_begin + 1; + } + } +} + +bool LocaleAwareCompareFilenames(const FilePath& a, const FilePath& b) { +#if defined(OS_WIN) + return LocaleAwareComparator::GetInstance()->Compare(a.value().c_str(), + b.value().c_str()) < 0; + +#elif defined(OS_POSIX) || defined(OS_STARBOARD) + // On linux, the file system encoding is not defined. We assume + // SysNativeMBToWide takes care of it. + // + // ICU's collator can take strings in OS native encoding. But we convert the + // strings to UTF-16 ourselves to ensure conversion consistency. + // TODO(yuzo): Perhaps we should define SysNativeMBToUTF16? + return LocaleAwareComparator::GetInstance()->Compare( + WideToUTF16(base::SysNativeMBToWide(a.value().c_str())), + WideToUTF16(base::SysNativeMBToWide(b.value().c_str()))) < 0; +#else + #error Not implemented on your system +#endif +} + +void NormalizeFileNameEncoding(FilePath* file_name) { +#if defined(OS_CHROMEOS) + std::string normalized_str; + if (base::ConvertToUtf8AndNormalize(file_name->BaseName().value(), + base::kCodepageUTF8, + &normalized_str)) { + *file_name = file_name->DirName().Append(FilePath(normalized_str)); + } +#endif +} + +} // namespace
diff --git a/src/base/i18n/file_util_icu.h b/src/base/i18n/file_util_icu.h new file mode 100644 index 0000000..4672135 --- /dev/null +++ b/src/base/i18n/file_util_icu.h
@@ -0,0 +1,43 @@ +// 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_I18N_FILE_UTIL_ICU_H_ +#define BASE_I18N_FILE_UTIL_ICU_H_ + +// File utilities that use the ICU library go in this file. + +#include "base/file_path.h" +#include "base/i18n/base_i18n_export.h" +#include "base/string16.h" + +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); + +// 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 +// file name component (since slashes are considered illegal). Any leading or +// trailing whitespace in 'file_name' is removed. +// Example: +// file_name == "bad:file*name?.txt", changed to: "bad-file-name-.txt" when +// 'replace_char' is '-'. +BASE_I18N_EXPORT void ReplaceIllegalCharactersInPath( + FilePath::StringType* file_name, + char replace_char); + +// Compares two filenames using the current locale information. This can be +// used to sort directory listings. It behaves like "operator<" for use in +// std::sort. +BASE_I18N_EXPORT bool LocaleAwareCompareFilenames(const FilePath& a, + const FilePath& b); + +// Calculates the canonical file-system representation of |file_name| base name. +// Modifies |file_name| in place. No-op if not on ChromeOS. +BASE_I18N_EXPORT void NormalizeFileNameEncoding(FilePath* file_name); + +} // namespace file_util + +#endif // BASE_I18N_FILE_UTIL_ICU_H_
diff --git a/src/base/i18n/file_util_icu_unittest.cc b/src/base/i18n/file_util_icu_unittest.cc new file mode 100644 index 0000000..dfc4943 --- /dev/null +++ b/src/base/i18n/file_util_icu_unittest.cc
@@ -0,0 +1,106 @@ +// 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/i18n/file_util_icu.h" + +#include "base/file_util.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +// file_util winds up using autoreleased objects on the Mac, so this needs +// to be a PlatformTest +class FileUtilICUTest : public PlatformTest { +}; + +#if defined(OS_POSIX) && !defined(OS_MACOSX) + +// Linux disallows some evil ASCII characters, but passes all non-ASCII. +static const struct goodbad_pair { + const char* bad_name; + const char* good_name; +} kIllegalCharacterCases[] = { + {"bad*file:name?.jpg", "bad-file-name-.jpg"}, + {"**********::::.txt", "--------------.txt"}, + {"\xe9\xf0zzzz.\xff", "\xe9\xf0zzzz.\xff"}, +}; + +TEST_F(FileUtilICUTest, ReplaceIllegalCharacersInPathLinuxTest) { + for (size_t i = 0; i < arraysize(kIllegalCharacterCases); ++i) { + std::string bad_name(kIllegalCharacterCases[i].bad_name); + file_util::ReplaceIllegalCharactersInPath(&bad_name, '-'); + EXPECT_EQ(kIllegalCharacterCases[i].good_name, bad_name); + } +} + +#else + +// For Mac & Windows, which both do Unicode validation on filenames. These +// characters are given as wide strings since its more convenient to specify +// unicode characters. For Mac they should be converted to UTF-8. +static const struct goodbad_pair { + const wchar_t* bad_name; + const wchar_t* good_name; +} kIllegalCharacterCases[] = { + {L"bad*file:name?.jpg", L"bad-file-name-.jpg"}, + {L"**********::::.txt", L"--------------.txt"}, + // We can't use UCNs (universal character names) for C0/C1 characters and + // U+007F, but \x escape is interpreted by MSVC and gcc as we intend. + {L"bad\x0003\x0091 file\u200E\u200Fname.png", L"bad-- file--name.png"}, +#if defined(OS_WIN) + {L"bad*file\\name.jpg", L"bad-file-name.jpg"}, + {L"\t bad*file\\name/.jpg ", L"bad-file-name-.jpg"}, +#elif defined(OS_MACOSX) + {L"bad*file?name.jpg", L"bad-file-name.jpg"}, + {L"\t bad*file?name/.jpg ", L"bad-file-name-.jpg"}, +#endif + {L"this_file_name is okay!.mp3", L"this_file_name is okay!.mp3"}, + {L"\u4E00\uAC00.mp3", L"\u4E00\uAC00.mp3"}, + {L"\u0635\u200C\u0644.mp3", L"\u0635\u200C\u0644.mp3"}, + {L"\U00010330\U00010331.mp3", L"\U00010330\U00010331.mp3"}, + // Unassigned codepoints are ok. + {L"\u0378\U00040001.mp3", L"\u0378\U00040001.mp3"}, + // Non-characters are not allowed. + {L"bad\uFFFFfile\U0010FFFEname.jpg ", L"bad-file-name.jpg"}, + {L"bad\uFDD0file\uFDEFname.jpg ", L"bad-file-name.jpg"}, +}; + +TEST_F(FileUtilICUTest, ReplaceIllegalCharactersInPathTest) { + for (size_t i = 0; i < arraysize(kIllegalCharacterCases); ++i) { +#if defined(OS_WIN) + std::wstring bad_name(kIllegalCharacterCases[i].bad_name); + file_util::ReplaceIllegalCharactersInPath(&bad_name, '-'); + EXPECT_EQ(kIllegalCharacterCases[i].good_name, bad_name); +#elif defined(OS_MACOSX) + std::string bad_name(WideToUTF8(kIllegalCharacterCases[i].bad_name)); + file_util::ReplaceIllegalCharactersInPath(&bad_name, '-'); + EXPECT_EQ(WideToUTF8(kIllegalCharacterCases[i].good_name), bad_name); +#endif + } +} + +#endif + +#if defined(OS_CHROMEOS) +static const struct normalize_name_encoding_test_cases { + const char* original_path; + const char* normalized_path; +} kNormalizeFileNameEncodingTestCases[] = { + { "foo_na\xcc\x88me.foo", "foo_n\xc3\xa4me.foo"}, + { "foo_dir_na\xcc\x88me/foo_na\xcc\x88me.foo", + "foo_dir_na\xcc\x88me/foo_n\xc3\xa4me.foo"}, + { "", ""}, + { "foo_dir_na\xcc\x88me/", "foo_dir_n\xc3\xa4me"} +}; + +TEST_F(FileUtilICUTest, NormalizeFileNameEncoding) { + for (size_t i = 0; i < arraysize(kNormalizeFileNameEncodingTestCases); i++) { + FilePath path(kNormalizeFileNameEncodingTestCases[i].original_path); + file_util::NormalizeFileNameEncoding(&path); + EXPECT_EQ(FilePath(kNormalizeFileNameEncodingTestCases[i].normalized_path), + path); + } +} + +#endif
diff --git a/src/base/i18n/icu_encoding_detection.cc b/src/base/i18n/icu_encoding_detection.cc new file mode 100644 index 0000000..2081c7a --- /dev/null +++ b/src/base/i18n/icu_encoding_detection.cc
@@ -0,0 +1,104 @@ +// 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/i18n/icu_encoding_detection.h" + +#include <set> + +#include "base/string_util.h" +#include "unicode/ucsdet.h" + +namespace base { + +bool DetectEncoding(const std::string& text, std::string* encoding) { + if (IsStringASCII(text)) { + *encoding = std::string(); + return true; + } + + UErrorCode status = U_ZERO_ERROR; + UCharsetDetector* detector = ucsdet_open(&status); + ucsdet_setText(detector, text.data(), static_cast<int32_t>(text.length()), + &status); + const UCharsetMatch* match = ucsdet_detect(detector, &status); + if (match == NULL) + return false; + const char* detected_encoding = ucsdet_getName(match, &status); + ucsdet_close(detector); + + if (U_FAILURE(status)) + return false; + + *encoding = detected_encoding; + return true; +} + +bool DetectAllEncodings(const std::string& text, + std::vector<std::string>* encodings) { + UErrorCode status = U_ZERO_ERROR; + UCharsetDetector* detector = ucsdet_open(&status); + ucsdet_setText(detector, text.data(), static_cast<int32_t>(text.length()), + &status); + int matches_count = 0; + const UCharsetMatch** matches = ucsdet_detectAll(detector, + &matches_count, + &status); + if (U_FAILURE(status)) { + ucsdet_close(detector); + return false; + } + + // ICU has some heuristics for encoding detection, such that the more likely + // encodings should be returned first. However, it doesn't always return + // all encodings that properly decode |text|, so we'll append more encodings + // later. To make that efficient, keep track of encodings sniffed in this + // first phase. + std::set<std::string> sniffed_encodings; + + encodings->clear(); + for (int i = 0; i < matches_count; i++) { + UErrorCode get_name_status = U_ZERO_ERROR; + const char* encoding_name = ucsdet_getName(matches[i], &get_name_status); + + // If we failed to get the encoding's name, ignore the error. + if (U_FAILURE(get_name_status)) + continue; + + int32_t confidence = ucsdet_getConfidence(matches[i], &get_name_status); + + // We also treat this error as non-fatal. + if (U_FAILURE(get_name_status)) + continue; + + // A confidence level >= 10 means that the encoding is expected to properly + // decode the text. Drop all encodings with lower confidence level. + if (confidence < 10) + continue; + + encodings->push_back(encoding_name); + sniffed_encodings.insert(encoding_name); + } + + // Append all encodings not included earlier, in arbitrary order. + // TODO(jshin): This shouldn't be necessary, possible ICU bug. + // See also http://crbug.com/65917. + UEnumeration* detectable_encodings = ucsdet_getAllDetectableCharsets(detector, + &status); + int detectable_count = uenum_count(detectable_encodings, &status); + for (int i = 0; i < detectable_count; i++) { + int name_length; + const char* name_raw = uenum_next(detectable_encodings, + &name_length, + &status); + std::string name(name_raw, name_length); + if (sniffed_encodings.find(name) == sniffed_encodings.end()) + encodings->push_back(name); + } + uenum_close(detectable_encodings); + + ucsdet_close(detector); + return !encodings->empty(); +} + +} // namespace base
diff --git a/src/base/i18n/icu_encoding_detection.h b/src/base/i18n/icu_encoding_detection.h new file mode 100644 index 0000000..6d1e71c --- /dev/null +++ b/src/base/i18n/icu_encoding_detection.h
@@ -0,0 +1,30 @@ +// 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. + +#ifndef BASE_I18N_ICU_ENCODING_DETECTION_H_ +#define BASE_I18N_ICU_ENCODING_DETECTION_H_ + +#include <string> +#include <vector> + +#include "base/i18n/base_i18n_export.h" + +namespace base { + +// Detect encoding of |text| and put the name of encoding (as returned by ICU) +// in |encoding|. For ASCII texts |encoding| will be set to an empty string. +// Returns true on success. +BASE_I18N_EXPORT bool DetectEncoding(const std::string& text, + std::string* encoding); + +// Detect all possible encodings of |text| and put their names +// (as returned by ICU) in |encodings|. Returns true on success. +// Note: this function may return encodings that may fail to decode |text|, +// the caller is responsible for handling that. +BASE_I18N_EXPORT bool DetectAllEncodings(const std::string& text, + std::vector<std::string>* encodings); + +} // namespace base + +#endif // BASE_I18N_ICU_ENCODING_DETECTION_H_
diff --git a/src/base/i18n/icu_string_conversions.cc b/src/base/i18n/icu_string_conversions.cc new file mode 100644 index 0000000..5ebadf3 --- /dev/null +++ b/src/base/i18n/icu_string_conversions.cc
@@ -0,0 +1,297 @@ +// 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/i18n/icu_string_conversions.h" + +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "unicode/ucnv.h" +#include "unicode/ucnv_cb.h" +#include "unicode/ucnv_err.h" +#include "unicode/unorm.h" +#include "unicode/ustring.h" + +namespace base { + +namespace { +// ToUnicodeCallbackSubstitute() is based on UCNV_TO_U_CALLBACK_SUBSTITUTE +// in source/common/ucnv_err.c. + +// Copyright (c) 1995-2006 International Business Machines Corporation +// and others +// +// All rights reserved. +// + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, and/or +// sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, provided that the above copyright notice(s) and +// this permission notice appear in all copies of the Software and that +// both the above copyright notice(s) and this permission notice appear in +// supporting documentation. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +// OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS +// INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT +// OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE +// OR PERFORMANCE OF THIS SOFTWARE. +// +// Except as contained in this notice, the name of a copyright holder +// shall not be used in advertising or otherwise to promote the sale, use +// or other dealings in this Software without prior written authorization +// of the copyright holder. + +// ___________________________________________________________________________ +// +// All trademarks and registered trademarks mentioned herein are the property +// of their respective owners. + +void ToUnicodeCallbackSubstitute(const void* context, + UConverterToUnicodeArgs *to_args, + const char* code_units, + int32_t length, + UConverterCallbackReason reason, + UErrorCode * err) { + static const UChar kReplacementChar = 0xFFFD; + if (reason <= UCNV_IRREGULAR) { + if (context == NULL || + (*(reinterpret_cast<const char*>(context)) == 'i' && + reason == UCNV_UNASSIGNED)) { + *err = U_ZERO_ERROR; + ucnv_cbToUWriteUChars(to_args, &kReplacementChar, 1, 0, err); + } + // else the caller must have set the error code accordingly. + } + // else ignore the reset, close and clone calls. +} + +bool ConvertFromUTF16(UConverter* converter, const UChar* uchar_src, + int uchar_len, OnStringConversionError::Type on_error, + std::string* encoded) { + int encoded_max_length = UCNV_GET_MAX_BYTES_FOR_STRING(uchar_len, + ucnv_getMaxCharSize(converter)); + encoded->resize(encoded_max_length); + + UErrorCode status = U_ZERO_ERROR; + + // Setup our error handler. + switch (on_error) { + case OnStringConversionError::FAIL: + ucnv_setFromUCallBack(converter, UCNV_FROM_U_CALLBACK_STOP, 0, + NULL, NULL, &status); + break; + case OnStringConversionError::SKIP: + case OnStringConversionError::SUBSTITUTE: + ucnv_setFromUCallBack(converter, UCNV_FROM_U_CALLBACK_SKIP, 0, + NULL, NULL, &status); + break; + default: + NOTREACHED(); + } + + // ucnv_fromUChars returns size not including terminating null + int actual_size = ucnv_fromUChars(converter, &(*encoded)[0], + encoded_max_length, uchar_src, uchar_len, &status); + encoded->resize(actual_size); + ucnv_close(converter); + if (U_SUCCESS(status)) + return true; + encoded->clear(); // Make sure the output is empty on error. + return false; +} + +// Set up our error handler for ToUTF-16 converters +void SetUpErrorHandlerForToUChars(OnStringConversionError::Type on_error, + UConverter* converter, UErrorCode* status) { + switch (on_error) { + case OnStringConversionError::FAIL: + ucnv_setToUCallBack(converter, UCNV_TO_U_CALLBACK_STOP, 0, + NULL, NULL, status); + break; + case OnStringConversionError::SKIP: + ucnv_setToUCallBack(converter, UCNV_TO_U_CALLBACK_SKIP, 0, + NULL, NULL, status); + break; + case OnStringConversionError::SUBSTITUTE: + ucnv_setToUCallBack(converter, ToUnicodeCallbackSubstitute, 0, + NULL, NULL, status); + break; + default: + NOTREACHED(); + } +} + +inline UConverterType utf32_platform_endian() { +#if U_IS_BIG_ENDIAN + return UCNV_UTF32_BigEndian; +#else + return UCNV_UTF32_LittleEndian; +#endif +} + +} // namespace + +const char kCodepageLatin1[] = "ISO-8859-1"; +const char kCodepageUTF8[] = "UTF-8"; +const char kCodepageUTF16BE[] = "UTF-16BE"; +const char kCodepageUTF16LE[] = "UTF-16LE"; + +// Codepage <-> Wide/UTF-16 --------------------------------------------------- + +bool UTF16ToCodepage(const string16& utf16, + const char* codepage_name, + OnStringConversionError::Type on_error, + std::string* encoded) { + encoded->clear(); + + UErrorCode status = U_ZERO_ERROR; + UConverter* converter = ucnv_open(codepage_name, &status); + if (!U_SUCCESS(status)) + return false; + + return ConvertFromUTF16(converter, utf16.c_str(), + static_cast<int>(utf16.length()), on_error, encoded); +} + +bool CodepageToUTF16(const std::string& encoded, + const char* codepage_name, + OnStringConversionError::Type on_error, + string16* utf16) { + utf16->clear(); + + UErrorCode status = U_ZERO_ERROR; + UConverter* converter = ucnv_open(codepage_name, &status); + if (!U_SUCCESS(status)) + return false; + + // Even in the worst case, the maximum length in 2-byte units of UTF-16 + // output would be at most the same as the number of bytes in input. There + // is no single-byte encoding in which a character is mapped to a + // non-BMP character requiring two 2-byte units. + // + // Moreover, non-BMP characters in legacy multibyte encodings + // (e.g. EUC-JP, GB18030) take at least 2 bytes. The only exceptions are + // BOCU and SCSU, but we don't care about them. + size_t uchar_max_length = encoded.length() + 1; + + SetUpErrorHandlerForToUChars(on_error, converter, &status); + scoped_array<char16> buffer(new char16[uchar_max_length]); + int actual_size = ucnv_toUChars(converter, buffer.get(), + static_cast<int>(uchar_max_length), encoded.data(), + static_cast<int>(encoded.length()), &status); + ucnv_close(converter); + if (!U_SUCCESS(status)) { + utf16->clear(); // Make sure the output is empty on error. + return false; + } + + utf16->assign(buffer.get(), actual_size); + return true; +} + +bool WideToCodepage(const std::wstring& wide, + const char* codepage_name, + OnStringConversionError::Type on_error, + std::string* encoded) { +#if defined(WCHAR_T_IS_UTF16) + return UTF16ToCodepage(wide, codepage_name, on_error, encoded); +#elif defined(WCHAR_T_IS_UTF32) + encoded->clear(); + + UErrorCode status = U_ZERO_ERROR; + UConverter* converter = ucnv_open(codepage_name, &status); + if (!U_SUCCESS(status)) + return false; + + int utf16_len; + // When wchar_t is wider than UChar (16 bits), transform |wide| into a + // UChar* string. Size the UChar* buffer to be large enough to hold twice + // as many UTF-16 code units (UChar's) as there are Unicode code points, + // in case each code points translates to a UTF-16 surrogate pair, + // and leave room for a NUL terminator. + std::vector<UChar> utf16(wide.length() * 2 + 1); + u_strFromUTF32(&utf16[0], utf16.size(), &utf16_len, + reinterpret_cast<const UChar32*>(wide.c_str()), + wide.length(), &status); + DCHECK(U_SUCCESS(status)) << "failed to convert wstring to UChar*"; + + return ConvertFromUTF16(converter, &utf16[0], utf16_len, on_error, encoded); +#endif // defined(WCHAR_T_IS_UTF32) +} + +bool CodepageToWide(const std::string& encoded, + const char* codepage_name, + OnStringConversionError::Type on_error, + std::wstring* wide) { +#if defined(WCHAR_T_IS_UTF16) + return CodepageToUTF16(encoded, codepage_name, on_error, wide); +#elif defined(WCHAR_T_IS_UTF32) + wide->clear(); + + UErrorCode status = U_ZERO_ERROR; + UConverter* converter = ucnv_open(codepage_name, &status); + if (!U_SUCCESS(status)) + return false; + + // The maximum length in 4 byte unit of UTF-32 output would be + // at most the same as the number of bytes in input. In the worst + // case of GB18030 (excluding escaped-based encodings like ISO-2022-JP), + // this can be 4 times larger than actually needed. + size_t wchar_max_length = encoded.length() + 1; + + SetUpErrorHandlerForToUChars(on_error, converter, &status); + scoped_array<wchar_t> buffer(new wchar_t[wchar_max_length]); + int actual_size = ucnv_toAlgorithmic(utf32_platform_endian(), converter, + reinterpret_cast<char*>(buffer.get()), + static_cast<int>(wchar_max_length) * sizeof(wchar_t), encoded.data(), + static_cast<int>(encoded.length()), &status); + ucnv_close(converter); + if (!U_SUCCESS(status)) { + wide->clear(); // Make sure the output is empty on error. + return false; + } + + // actual_size is # of bytes. + wide->assign(buffer.get(), actual_size / sizeof(wchar_t)); + return true; +#endif // defined(WCHAR_T_IS_UTF32) +} + +bool ConvertToUtf8AndNormalize(const std::string& text, + const std::string& charset, + std::string* result) { + result->clear(); + string16 utf16; + if (!CodepageToUTF16( + text, charset.c_str(), OnStringConversionError::FAIL, &utf16)) + return false; + + UErrorCode status = U_ZERO_ERROR; + size_t max_length = utf16.length() + 1; + string16 normalized_utf16; + scoped_array<char16> buffer(new char16[max_length]); + int actual_length = unorm_normalize( + utf16.c_str(), utf16.length(), UNORM_NFC, 0, + buffer.get(), static_cast<int>(max_length), &status); + if (!U_SUCCESS(status)) + return false; + normalized_utf16.assign(buffer.get(), actual_length); + + return UTF16ToUTF8(normalized_utf16.data(), + normalized_utf16.length(), result); +} + +} // namespace base
diff --git a/src/base/i18n/icu_string_conversions.h b/src/base/i18n/icu_string_conversions.h new file mode 100644 index 0000000..31a57df --- /dev/null +++ b/src/base/i18n/icu_string_conversions.h
@@ -0,0 +1,75 @@ +// 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. + +#ifndef BASE_I18N_ICU_STRING_CONVERSIONS_H_ +#define BASE_I18N_ICU_STRING_CONVERSIONS_H_ + +#include <string> + +#include "base/i18n/base_i18n_export.h" +#include "base/string16.h" + +namespace base { + +// Defines the error handling modes of UTF16ToCodepage, CodepageToUTF16, +// WideToCodepage and CodepageToWide. +class OnStringConversionError { + public: + enum Type { + // The function will return failure. The output buffer will be empty. + FAIL, + + // The offending characters are skipped and the conversion will proceed as + // if they did not exist. + SKIP, + + // When converting to Unicode, the offending byte sequences are substituted + // by Unicode replacement character (U+FFFD). When converting from Unicode, + // this is the same as SKIP. + SUBSTITUTE, + }; + + private: + OnStringConversionError(); +}; + +// Names of codepages (charsets) understood by icu. +BASE_I18N_EXPORT extern const char kCodepageLatin1[]; // a.k.a. ISO 8859-1 +BASE_I18N_EXPORT extern const char kCodepageUTF8[]; +BASE_I18N_EXPORT extern const char kCodepageUTF16BE[]; +BASE_I18N_EXPORT extern const char kCodepageUTF16LE[]; + +// Converts between UTF-16 strings and the encoding specified. If the +// encoding doesn't exist or the encoding fails (when on_error is FAIL), +// returns false. +BASE_I18N_EXPORT bool UTF16ToCodepage(const string16& utf16, + const char* codepage_name, + OnStringConversionError::Type on_error, + std::string* encoded); +BASE_I18N_EXPORT bool CodepageToUTF16(const std::string& encoded, + const char* codepage_name, + OnStringConversionError::Type on_error, + string16* utf16); + +// Converts between wide strings and the encoding specified. If the +// encoding doesn't exist or the encoding fails (when on_error is FAIL), +// returns false. +BASE_I18N_EXPORT bool WideToCodepage(const std::wstring& wide, + const char* codepage_name, + OnStringConversionError::Type on_error, + std::string* encoded); +BASE_I18N_EXPORT bool CodepageToWide(const std::string& encoded, + const char* codepage_name, + OnStringConversionError::Type on_error, + std::wstring* wide); + +// Converts from any codepage to UTF-8 and ensures the resulting UTF-8 is +// normalized. +BASE_I18N_EXPORT bool ConvertToUtf8AndNormalize(const std::string& text, + const std::string& charset, + std::string* result); + +} // namespace base + +#endif // BASE_I18N_ICU_STRING_CONVERSIONS_H_
diff --git a/src/base/i18n/icu_string_conversions_unittest.cc b/src/base/i18n/icu_string_conversions_unittest.cc new file mode 100644 index 0000000..5401759 --- /dev/null +++ b/src/base/i18n/icu_string_conversions_unittest.cc
@@ -0,0 +1,386 @@ +// 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 <math.h> +#include <stdarg.h> + +#include <limits> +#include <sstream> + +#include "base/basictypes.h" +#include "base/format_macros.h" +#include "base/i18n/icu_string_conversions.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "base/string_piece.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +// Given a null-terminated string of wchar_t with each wchar_t representing +// a UTF-16 code unit, returns a string16 made up of wchar_t's in the input. +// Each wchar_t should be <= 0xFFFF and a non-BMP character (> U+FFFF) +// should be represented as a surrogate pair (two UTF-16 units) +// *even* where wchar_t is 32-bit (Linux and Mac). +// +// This is to help write tests for functions with string16 params until +// the C++ 0x UTF-16 literal is well-supported by compilers. +string16 BuildString16(const wchar_t* s) { +#if defined(WCHAR_T_IS_UTF16) + return string16(s); +#elif defined(WCHAR_T_IS_UTF32) + string16 u16; + while (*s != 0) { + DCHECK_LE(static_cast<unsigned int>(*s), 0xFFFFu); + u16.push_back(*s++); + } + return u16; +#endif +} + +const wchar_t* const kConvertRoundtripCases[] = { + L"Google Video", + // "网页 图片 资讯更多 »" + L"\x7f51\x9875\x0020\x56fe\x7247\x0020\x8d44\x8baf\x66f4\x591a\x0020\x00bb", + // "Παγκόσμιος Ιστός" + L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9" + L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2", + // "Поиск страниц на русском" + L"\x041f\x043e\x0438\x0441\x043a\x0020\x0441\x0442" + L"\x0440\x0430\x043d\x0438\x0446\x0020\x043d\x0430" + L"\x0020\x0440\x0443\x0441\x0441\x043a\x043e\x043c", + // "전체서비스" + L"\xc804\xccb4\xc11c\xbe44\xc2a4", + + // Test characters that take more than 16 bits. This will depend on whether + // wchar_t is 16 or 32 bits. +#if defined(WCHAR_T_IS_UTF16) + L"\xd800\xdf00", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44", +#elif defined(WCHAR_T_IS_UTF32) + L"\x10300", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\x11d40\x11d41\x11d42\x11d43\x11d44", +#endif +}; + +} // namespace + +TEST(ICUStringConversionsTest, ConvertCodepageUTF8) { + // Make sure WideToCodepage works like WideToUTF8. + for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %ls", + i, kConvertRoundtripCases[i])); + + std::string expected(WideToUTF8(kConvertRoundtripCases[i])); + std::string utf8; + EXPECT_TRUE(WideToCodepage(kConvertRoundtripCases[i], kCodepageUTF8, + OnStringConversionError::SKIP, &utf8)); + EXPECT_EQ(expected, utf8); + } +} + +// kConverterCodepageCases is not comprehensive. There are a number of cases +// to add if we really want to have a comprehensive coverage of various +// codepages and their 'idiosyncrasies'. Currently, the only implementation +// for CodepageTo* and *ToCodepage uses ICU, which has a very extensive +// set of tests for the charset conversion. So, we can get away with a +// relatively small number of cases listed below. +// +// Note about |u16_wide| in the following struct. +// On Windows, the field is always identical to |wide|. On Mac and Linux, +// it's identical as long as there's no character outside the +// BMP (<= U+FFFF). When there is, it is different from |wide| and +// is not a real wide string (UTF-32 string) in that each wchar_t in +// the string is a UTF-16 code unit zero-extended to be 32-bit +// even when the code unit belongs to a surrogate pair. +// For instance, a Unicode string (U+0041 U+010000) is represented as +// L"\x0041\xD800\xDC00" instead of L"\x0041\x10000". +// To avoid the clutter, |u16_wide| will be set to NULL +// if it's identical to |wide| on *all* platforms. + +static const struct { + const char* codepage_name; + const char* encoded; + OnStringConversionError::Type on_error; + bool success; + const wchar_t* wide; + const wchar_t* u16_wide; +} kConvertCodepageCases[] = { + // Test a case where the input cannot be decoded, using SKIP, FAIL + // and SUBSTITUTE error handling rules. "A7 41" is valid, but "A6" isn't. + {"big5", + "\xA7\x41\xA6", + OnStringConversionError::FAIL, + false, + L"", + NULL}, + {"big5", + "\xA7\x41\xA6", + OnStringConversionError::SKIP, + true, + L"\x4F60", + NULL}, + {"big5", + "\xA7\x41\xA6", + OnStringConversionError::SUBSTITUTE, + true, + L"\x4F60\xFFFD", + NULL}, + // Arabic (ISO-8859) + {"iso-8859-6", + "\xC7\xEE\xE4\xD3\xF1\xEE\xE4\xC7\xE5\xEF" " " + "\xD9\xEE\xE4\xEE\xEA\xF2\xE3\xEF\xE5\xF2", + OnStringConversionError::FAIL, + true, + L"\x0627\x064E\x0644\x0633\x0651\x064E\x0644\x0627\x0645\x064F" L" " + L"\x0639\x064E\x0644\x064E\x064A\x0652\x0643\x064F\x0645\x0652", + NULL}, + // Chinese Simplified (GB2312) + {"gb2312", + "\xC4\xE3\xBA\xC3", + OnStringConversionError::FAIL, + true, + L"\x4F60\x597D", + NULL}, + // Chinese (GB18030) : 4 byte sequences mapped to BMP characters + {"gb18030", + "\x81\x30\x84\x36\xA1\xA7", + OnStringConversionError::FAIL, + true, + L"\x00A5\x00A8", + NULL}, + // Chinese (GB18030) : A 4 byte sequence mapped to plane 2 (U+20000) + {"gb18030", + "\x95\x32\x82\x36\xD2\xBB", + OnStringConversionError::FAIL, + true, +#if defined(WCHAR_T_IS_UTF16) + L"\xD840\xDC00\x4E00", +#elif defined(WCHAR_T_IS_UTF32) + L"\x20000\x4E00", +#endif + L"\xD840\xDC00\x4E00"}, + {"big5", + "\xA7\x41\xA6\x6E", + OnStringConversionError::FAIL, + true, + L"\x4F60\x597D", + NULL}, + // Greek (ISO-8859) + {"iso-8859-7", + "\xE3\xE5\xE9\xDC" " " "\xF3\xEF\xF5", + OnStringConversionError::FAIL, + true, + L"\x03B3\x03B5\x03B9\x03AC" L" " L"\x03C3\x03BF\x03C5", + NULL}, + // Hebrew (Windows) + {"windows-1255", + "\xF9\xD1\xC8\xEC\xE5\xC9\xED", + OnStringConversionError::FAIL, + true, + L"\x05E9\x05C1\x05B8\x05DC\x05D5\x05B9\x05DD", + NULL}, + // Hindi Devanagari (ISCII) + {"iscii-dev", + "\xEF\x42" "\xC6\xCC\xD7\xE8\xB3\xDA\xCF", + OnStringConversionError::FAIL, + true, + L"\x0928\x092E\x0938\x094D\x0915\x093E\x0930", + NULL}, + // Korean (EUC) + {"euc-kr", + "\xBE\xC8\xB3\xE7\xC7\xCF\xBC\xBC\xBF\xE4", + OnStringConversionError::FAIL, + true, + L"\xC548\xB155\xD558\xC138\xC694", + NULL}, + // Japanese (EUC) + {"euc-jp", + "\xA4\xB3\xA4\xF3\xA4\xCB\xA4\xC1\xA4\xCF\xB0\xEC\x8E\xA6", + OnStringConversionError::FAIL, + true, + L"\x3053\x3093\x306B\x3061\x306F\x4E00\xFF66", + NULL}, + // Japanese (ISO-2022) + {"iso-2022-jp", + "\x1B$B" "\x24\x33\x24\x73\x24\x4B\x24\x41\x24\x4F\x30\x6C" "\x1B(B" + "ab" "\x1B(J" "\x5C\x7E#$" "\x1B(B", + OnStringConversionError::FAIL, + true, + L"\x3053\x3093\x306B\x3061\x306F\x4E00" L"ab\x00A5\x203E#$", + NULL}, + // Japanese (Shift-JIS) + {"sjis", + "\x82\xB1\x82\xF1\x82\xC9\x82\xBF\x82\xCD\x88\xEA\xA6", + OnStringConversionError::FAIL, + true, + L"\x3053\x3093\x306B\x3061\x306F\x4E00\xFF66", + NULL}, + // Russian (KOI8) + {"koi8-r", + "\xDA\xC4\xD2\xC1\xD7\xD3\xD4\xD7\xD5\xCA\xD4\xC5", + OnStringConversionError::FAIL, + true, + L"\x0437\x0434\x0440\x0430\x0432\x0441\x0442\x0432" + L"\x0443\x0439\x0442\x0435", + NULL}, + // Thai (windows-874) + {"windows-874", + "\xCA\xC7\xD1\xCA\xB4\xD5" "\xA4\xC3\xD1\xBA", + OnStringConversionError::FAIL, + true, + L"\x0E2A\x0E27\x0E31\x0E2A\x0E14\x0E35" + L"\x0E04\x0E23\x0e31\x0E1A", + NULL}, + // Empty text + {"iscii-dev", + "", + OnStringConversionError::FAIL, + true, + L"", + NULL}, +}; + +TEST(ICUStringConversionsTest, ConvertBetweenCodepageAndWide) { + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kConvertCodepageCases); ++i) { + SCOPED_TRACE(base::StringPrintf( + "Test[%" PRIuS "]: <encoded: %s> <codepage: %s>", i, + kConvertCodepageCases[i].encoded, + kConvertCodepageCases[i].codepage_name)); + + std::wstring wide; + bool success = CodepageToWide(kConvertCodepageCases[i].encoded, + kConvertCodepageCases[i].codepage_name, + kConvertCodepageCases[i].on_error, + &wide); + EXPECT_EQ(kConvertCodepageCases[i].success, success); + EXPECT_EQ(kConvertCodepageCases[i].wide, wide); + + // When decoding was successful and nothing was skipped, we also check the + // reverse conversion. Not all conversions are round-trippable, but + // kConverterCodepageCases does not have any one-way conversion at the + // moment. + if (success && + kConvertCodepageCases[i].on_error == + OnStringConversionError::FAIL) { + std::string encoded; + success = WideToCodepage(wide, kConvertCodepageCases[i].codepage_name, + kConvertCodepageCases[i].on_error, &encoded); + EXPECT_EQ(kConvertCodepageCases[i].success, success); + EXPECT_EQ(kConvertCodepageCases[i].encoded, encoded); + } + } + + // The above cases handled codepage->wide errors, but not wide->codepage. + // Test that here. + std::string encoded("Temp data"); // Make sure the string gets cleared. + + // First test going to an encoding that can not represent that character. + EXPECT_FALSE(WideToCodepage(L"Chinese\xff27", "iso-8859-1", + OnStringConversionError::FAIL, &encoded)); + EXPECT_TRUE(encoded.empty()); + EXPECT_TRUE(WideToCodepage(L"Chinese\xff27", "iso-8859-1", + OnStringConversionError::SKIP, &encoded)); + EXPECT_STREQ("Chinese", encoded.c_str()); + // From Unicode, SUBSTITUTE is the same as SKIP for now. + EXPECT_TRUE(WideToCodepage(L"Chinese\xff27", "iso-8859-1", + OnStringConversionError::SUBSTITUTE, + &encoded)); + EXPECT_STREQ("Chinese", encoded.c_str()); + +#if defined(WCHAR_T_IS_UTF16) + // When we're in UTF-16 mode, test an invalid UTF-16 character in the input. + EXPECT_FALSE(WideToCodepage(L"a\xd800z", "iso-8859-1", + OnStringConversionError::FAIL, &encoded)); + EXPECT_TRUE(encoded.empty()); + EXPECT_TRUE(WideToCodepage(L"a\xd800z", "iso-8859-1", + OnStringConversionError::SKIP, &encoded)); + EXPECT_STREQ("az", encoded.c_str()); +#endif // WCHAR_T_IS_UTF16 + + // Invalid characters should fail. + EXPECT_TRUE(WideToCodepage(L"a\xffffz", "iso-8859-1", + OnStringConversionError::SKIP, &encoded)); + EXPECT_STREQ("az", encoded.c_str()); + + // Invalid codepages should fail. + EXPECT_FALSE(WideToCodepage(L"Hello, world", "awesome-8571-2", + OnStringConversionError::SKIP, &encoded)); +} + +TEST(ICUStringConversionsTest, ConvertBetweenCodepageAndUTF16) { + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kConvertCodepageCases); ++i) { + SCOPED_TRACE(base::StringPrintf( + "Test[%" PRIuS "]: <encoded: %s> <codepage: %s>", i, + kConvertCodepageCases[i].encoded, + kConvertCodepageCases[i].codepage_name)); + + string16 utf16; + bool success = CodepageToUTF16(kConvertCodepageCases[i].encoded, + kConvertCodepageCases[i].codepage_name, + kConvertCodepageCases[i].on_error, + &utf16); + string16 utf16_expected; + if (kConvertCodepageCases[i].u16_wide == NULL) + utf16_expected = BuildString16(kConvertCodepageCases[i].wide); + else + utf16_expected = BuildString16(kConvertCodepageCases[i].u16_wide); + EXPECT_EQ(kConvertCodepageCases[i].success, success); + EXPECT_EQ(utf16_expected, utf16); + + // When decoding was successful and nothing was skipped, we also check the + // reverse conversion. See also the corresponding comment in + // ConvertBetweenCodepageAndWide. + if (success && + kConvertCodepageCases[i].on_error == OnStringConversionError::FAIL) { + std::string encoded; + success = UTF16ToCodepage(utf16, kConvertCodepageCases[i].codepage_name, + kConvertCodepageCases[i].on_error, &encoded); + EXPECT_EQ(kConvertCodepageCases[i].success, success); + EXPECT_EQ(kConvertCodepageCases[i].encoded, encoded); + } + } +} + +static const struct { + const char* encoded; + const char* codepage_name; + bool expected_success; + const char* expected_value; +} kConvertAndNormalizeCases[] = { + {"foo-\xe4.html", "iso-8859-1", true, "foo-\xc3\xa4.html"}, + {"foo-\xe4.html", "iso-8859-7", true, "foo-\xce\xb4.html"}, + {"foo-\xe4.html", "foo-bar", false, ""}, + {"foo-\xff.html", "ascii", false, ""}, + {"foo.html", "ascii", true, "foo.html"}, + {"foo-a\xcc\x88.html", "utf-8", true, "foo-\xc3\xa4.html"}, + {"\x95\x32\x82\x36\xD2\xBB", "gb18030", true, "\xF0\xA0\x80\x80\xE4\xB8\x80"}, + {"\xA7\x41\xA6\x6E", "big5", true, "\xE4\xBD\xA0\xE5\xA5\xBD"}, + // Windows-1258 does have a combining character at xD2 (which is U+0309). + // The sequence of (U+00E2, U+0309) is also encoded as U+1EA9. + {"foo\xE2\xD2", "windows-1258", true, "foo\xE1\xBA\xA9"}, + {"", "iso-8859-1", true, ""}, +}; +TEST(ICUStringConversionsTest, ConvertToUtf8AndNormalize) { + std::string result; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kConvertAndNormalizeCases); ++i) { + SCOPED_TRACE(base::StringPrintf( + "Test[%" PRIuS "]: <encoded: %s> <codepage: %s>", i, + kConvertAndNormalizeCases[i].encoded, + kConvertAndNormalizeCases[i].codepage_name)); + + bool success = ConvertToUtf8AndNormalize( + kConvertAndNormalizeCases[i].encoded, + kConvertAndNormalizeCases[i].codepage_name, &result); + EXPECT_EQ(kConvertAndNormalizeCases[i].expected_success, success); + EXPECT_EQ(kConvertAndNormalizeCases[i].expected_value, result); + } +} + +} // namespace base
diff --git a/src/base/i18n/icu_util.cc b/src/base/i18n/icu_util.cc new file mode 100644 index 0000000..8c48b36 --- /dev/null +++ b/src/base/i18n/icu_util.cc
@@ -0,0 +1,156 @@ +// 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/i18n/icu_util.h" + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +#include <string> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "unicode/putil.h" +#include "unicode/udata.h" + +#if defined(OS_MACOSX) +#include "base/mac/foundation_util.h" +#endif + +#if defined(OS_STARBOARD) +#include "starboard/client_porting/icu_init/icu_init.h" +#endif + +#define ICU_UTIL_DATA_FILE 0 +#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) || defined(__LB_SHELL__) || defined(OS_STARBOARD) +#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 +#define ICU_UTIL_DATA_SYMBOL "icudt" U_ICU_VERSION_SHORT "_dat" +#if defined(OS_WIN) +#define ICU_UTIL_DATA_SHARED_MODULE_NAME "icudt.dll" +#endif +#endif + +namespace icu_util { + +bool Initialize() { +#ifndef NDEBUG + // Assert that we are not called more than once. Even though calling this + // function isn't harmful (ICU can handle it), being called twice probably + // indicates a programming error. + static bool called_once = false; + DCHECK(!called_once); + called_once = true; +#endif + +#if (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_SHARED) + // We expect to find the ICU data module alongside the current module. + FilePath data_path; + PathService::Get(base::DIR_MODULE, &data_path); + data_path = data_path.AppendASCII(ICU_UTIL_DATA_SHARED_MODULE_NAME); + + HMODULE module = LoadLibrary(data_path.value().c_str()); + if (!module) { + DLOG(ERROR) << "Failed to load " << ICU_UTIL_DATA_SHARED_MODULE_NAME; + return false; + } + + FARPROC addr = GetProcAddress(module, ICU_UTIL_DATA_SYMBOL); + if (!addr) { + DLOG(ERROR) << ICU_UTIL_DATA_SYMBOL << ": not found in " + << ICU_UTIL_DATA_SHARED_MODULE_NAME; + return false; + } + + UErrorCode err = U_ZERO_ERROR; + 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. + return true; +#elif (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE) +#if defined(__LB_SHELL__) + // Locate the data directory. + FilePath data_path; + bool path_ok = PathService::Get(base::DIR_EXE, &data_path); + DCHECK(path_ok); + data_path = data_path.Append("icu"); +#if U_IS_BIG_ENDIAN + data_path = data_path.Append("icudt46b"); +#else + data_path = data_path.Append("icudt46l"); +#endif + // set this as the data directory. + u_setDataDirectory(data_path.value().c_str()); + UErrorCode err = U_ZERO_ERROR; + udata_setFileAccess(UDATA_FILES_FIRST, &err); + return err == U_ZERO_ERROR; +#elif defined(OS_STARBOARD) + SbIcuInit(); + return true; +#elif !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 + // cause any problems. + + // Chrome doesn't normally shut down ICU, so the mapped data shouldn't ever + // be released. + static file_util::MemoryMappedFile mapped_file; + if (!mapped_file.IsValid()) { + // Assume it is in the framework bundle's Resources directory. + 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; + } + if (!mapped_file.Initialize(data_path)) { + DLOG(ERROR) << "Couldn't mmap " << data_path.value(); + return false; + } + } + UErrorCode err = U_ZERO_ERROR; + udata_setCommonData(const_cast<uint8*>(mapped_file.data()), &err); + return err == U_ZERO_ERROR; +#endif // OS check +#endif +} + +} // namespace icu_util
diff --git a/src/base/i18n/icu_util.h b/src/base/i18n/icu_util.h new file mode 100644 index 0000000..f6356f1 --- /dev/null +++ b/src/base/i18n/icu_util.h
@@ -0,0 +1,18 @@ +// 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. + +#ifndef BASE_I18N_ICU_UTIL_H_ +#define BASE_I18N_ICU_UTIL_H_ + +#include "base/i18n/base_i18n_export.h" + +namespace icu_util { + +// Call this function to load ICU's data tables for the current process. This +// function should be called before ICU is used. +BASE_I18N_EXPORT bool Initialize(); + +} // namespace icu_util + +#endif // BASE_I18N_ICU_UTIL_H_
diff --git a/src/base/i18n/icu_util_nacl_win64.cc b/src/base/i18n/icu_util_nacl_win64.cc new file mode 100644 index 0000000..6e0bb6b --- /dev/null +++ b/src/base/i18n/icu_util_nacl_win64.cc
@@ -0,0 +1,13 @@ +// Copyright (c) 2009 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/icu_util.h" + +namespace icu_util { + +bool Initialize() { + return true; +} + +} // namespace icu_util
diff --git a/src/base/i18n/number_formatting.cc b/src/base/i18n/number_formatting.cc new file mode 100644 index 0000000..35ff08d --- /dev/null +++ b/src/base/i18n/number_formatting.cc
@@ -0,0 +1,87 @@ +// 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/i18n/number_formatting.h" + +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/lazy_instance.h" +#include "base/memory/scoped_ptr.h" +#include "base/stringprintf.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "unicode/numfmt.h" +#include "unicode/ustring.h" + +namespace base { + +namespace { + +// A simple wrapper around icu::NumberFormat that allows for resetting it +// (as LazyInstance does not). +struct NumberFormatWrapper { + NumberFormatWrapper() { + Reset(); + } + + void Reset() { + // There's no ICU call to destroy a NumberFormat object other than + // operator delete, so use the default Delete, which calls operator delete. + // This can cause problems if a different allocator is used by this file + // than by ICU. + UErrorCode status = U_ZERO_ERROR; + number_format.reset(icu::NumberFormat::createInstance(status)); + DCHECK(U_SUCCESS(status)); + } + + scoped_ptr<icu::NumberFormat> number_format; +}; + +LazyInstance<NumberFormatWrapper> g_number_format_int = + LAZY_INSTANCE_INITIALIZER; +LazyInstance<NumberFormatWrapper> g_number_format_float = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +string16 FormatNumber(int64 number) { + icu::NumberFormat* number_format = + g_number_format_int.Get().number_format.get(); + + if (!number_format) { + // As a fallback, just return the raw number in a string. + return UTF8ToUTF16(StringPrintf("%" PRId64, number)); + } + icu::UnicodeString ustr; + number_format->format(number, ustr); + + return string16(ustr.getBuffer(), static_cast<size_t>(ustr.length())); +} + +string16 FormatDouble(double number, int fractional_digits) { + icu::NumberFormat* number_format = + g_number_format_float.Get().number_format.get(); + + if (!number_format) { + // As a fallback, just return the raw number in a string. + return UTF8ToUTF16(StringPrintf("%f", number)); + } + number_format->setMaximumFractionDigits(fractional_digits); + number_format->setMinimumFractionDigits(fractional_digits); + icu::UnicodeString ustr; + number_format->format(number, ustr); + + return string16(ustr.getBuffer(), static_cast<size_t>(ustr.length())); +} + +namespace testing { + +void ResetFormatters() { + g_number_format_int.Get().Reset(); + g_number_format_float.Get().Reset(); +} + +} // namespace testing + +} // namespace base
diff --git a/src/base/i18n/number_formatting.h b/src/base/i18n/number_formatting.h new file mode 100644 index 0000000..cfc4e12 --- /dev/null +++ b/src/base/i18n/number_formatting.h
@@ -0,0 +1,34 @@ +// 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. + +#ifndef BASE_I18N_NUMBER_FORMATTING_H_ +#define BASE_I18N_NUMBER_FORMATTING_H_ + +#include "base/basictypes.h" +#include "base/i18n/base_i18n_export.h" +#include "base/string16.h" + +namespace base { + +// Return a number formatted with separators in the user's locale. +// Ex: FormatNumber(1234567) +// => "1,234,567" in English, "1.234.567" in German +BASE_I18N_EXPORT string16 FormatNumber(int64 number); + +// Return a number formatted with separators in the user's locale. +// Ex: FormatDouble(1234567.8, 1) +// => "1,234,567.8" in English, "1.234.567,8" in German +BASE_I18N_EXPORT string16 FormatDouble(double number, int fractional_digits); + +namespace testing { + +// Causes cached formatters to be discarded and recreated. Only useful for +// testing. +BASE_I18N_EXPORT void ResetFormatters(); + +} // namespace testing + +} // namespace base + +#endif // BASE_I18N_NUMBER_FORMATTING_H_
diff --git a/src/base/i18n/number_formatting_unittest.cc b/src/base/i18n/number_formatting_unittest.cc new file mode 100644 index 0000000..e35eea0 --- /dev/null +++ b/src/base/i18n/number_formatting_unittest.cc
@@ -0,0 +1,88 @@ +// 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 <limits> + +#include "base/i18n/number_formatting.h" +#include "base/i18n/rtl.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +TEST(NumberFormattingTest, FormatNumber) { + static const struct { + int64 number; + const char* expected_english; + const char* expected_german; + } cases[] = { + {0, "0", "0"}, + {1024, "1,024", "1.024"}, + {std::numeric_limits<int64>::max(), + "9,223,372,036,854,775,807", "9.223.372.036.854.775.807"}, + {std::numeric_limits<int64>::min(), + "-9,223,372,036,854,775,808", "-9.223.372.036.854.775.808"}, + {-42, "-42", "-42"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + i18n::SetICUDefaultLocale("en"); + testing::ResetFormatters(); + EXPECT_EQ(cases[i].expected_english, + UTF16ToUTF8(FormatNumber(cases[i].number))); + i18n::SetICUDefaultLocale("de"); + testing::ResetFormatters(); + EXPECT_EQ(cases[i].expected_german, + UTF16ToUTF8(FormatNumber(cases[i].number))); + } +} + +TEST(NumberFormattingTest, FormatDouble) { + static const struct { + double number; + int frac_digits; + const char* expected_english; + const char* expected_german; + } cases[] = { + {0.0, 0, "0", "0"}, +#if !defined(OS_ANDROID) && !defined(__LB_ANDROID__) + // Bionic can't printf negative zero correctly. + {-0.0, 4, "-0.0000", "-0,0000"}, +#endif + {1024.2, 0, "1,024", "1.024"}, + {-1024.223, 2, "-1,024.22", "-1.024,22"}, + {std::numeric_limits<double>::max(), 6, + "179,769,313,486,232,000,000,000,000,000,000,000,000,000,000,000,000," + "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000," + "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000," + "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000," + "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000," + "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000," + "000.000000", + "179.769.313.486.232.000.000.000.000.000.000.000.000.000.000.000.000." + "000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000." + "000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000." + "000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000." + "000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000." + "000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000." + "000,000000"}, + {std::numeric_limits<double>::min(), 2, "0.00", "0,00"}, + {-42.7, 3, "-42.700", "-42,700"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + i18n::SetICUDefaultLocale("en"); + testing::ResetFormatters(); + EXPECT_EQ(cases[i].expected_english, + UTF16ToUTF8(FormatDouble(cases[i].number, cases[i].frac_digits))); + i18n::SetICUDefaultLocale("de"); + testing::ResetFormatters(); + EXPECT_EQ(cases[i].expected_german, + UTF16ToUTF8(FormatDouble(cases[i].number, cases[i].frac_digits))); + } +} + +} // namespace +} // namespace base
diff --git a/src/base/i18n/rtl.cc b/src/base/i18n/rtl.cc new file mode 100644 index 0000000..65f6a53 --- /dev/null +++ b/src/base/i18n/rtl.cc
@@ -0,0 +1,362 @@ +// 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/i18n/rtl.h" + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/sys_string_conversions.h" +#include "unicode/coll.h" +#include "unicode/locid.h" +#include "unicode/uchar.h" +#include "unicode/uscript.h" + +#if defined(TOOLKIT_GTK) +#include <gtk/gtk.h> +#endif + +namespace { + +// Extract language, country and variant, but ignore keywords. For example, +// en-US, ca@valencia, ca-ES@valencia. +std::string GetLocaleString(const icu::Locale& locale) { + const char* language = locale.getLanguage(); + const char* country = locale.getCountry(); + const char* variant = locale.getVariant(); + + std::string result = + (language != NULL && *language != '\0') ? language : "und"; + + if (country != NULL && *country != '\0') { + result += '-'; + result += country; + } + + if (variant != NULL && *variant != '\0') { + std::string variant_str(variant); + StringToLowerASCII(&variant_str); + result += '@' + variant_str; + } + + return result; +} + +} // namespace + +namespace base { +namespace i18n { + +// Represents the locale-specific ICU text direction. +static TextDirection g_icu_text_direction = UNKNOWN_DIRECTION; + +// Convert the ICU default locale to a string. +std::string GetConfiguredLocale() { + return GetLocaleString(icu::Locale::getDefault()); +} + +// Convert the ICU canonicalized locale to a string. +std::string GetCanonicalLocale(const char* locale) { + return GetLocaleString(icu::Locale::createCanonical(locale)); +} + +// Convert Chrome locale name to ICU locale name +std::string ICULocaleName(const std::string& locale_string) { + // If not Spanish, just return it. + if (locale_string.substr(0, 2) != "es") + return locale_string; + // Expand es to es-ES. + if (LowerCaseEqualsASCII(locale_string, "es")) + return "es-ES"; + // Map es-419 (Latin American Spanish) to es-FOO depending on the system + // locale. If it's es-RR other than es-ES, map to es-RR. Otherwise, map + // to es-MX (the most populous in Spanish-speaking Latin America). + if (LowerCaseEqualsASCII(locale_string, "es-419")) { + const icu::Locale& locale = icu::Locale::getDefault(); + std::string language = locale.getLanguage(); + const char* country = locale.getCountry(); + if (LowerCaseEqualsASCII(language, "es") && + !LowerCaseEqualsASCII(country, "es")) { + language += '-'; + language += country; + return language; + } + return "es-MX"; + } + // Currently, Chrome has only "es" and "es-419", but later we may have + // more specific "es-RR". + return locale_string; +} + +void SetICUDefaultLocale(const std::string& locale_string) { + icu::Locale locale(ICULocaleName(locale_string).c_str()); + UErrorCode error_code = U_ZERO_ERROR; + icu::Locale::setDefault(locale, error_code); + // This return value is actually bogus because Locale object is + // an ID and setDefault seems to always succeed (regardless of the + // presence of actual locale data). However, + // it does not hurt to have it as a sanity check. + DCHECK(U_SUCCESS(error_code)); + g_icu_text_direction = UNKNOWN_DIRECTION; +} + +bool IsRTL() { +#if defined(TOOLKIT_GTK) + GtkTextDirection gtk_dir = gtk_widget_get_default_direction(); + return gtk_dir == GTK_TEXT_DIR_RTL; +#else + return ICUIsRTL(); +#endif +} + +bool ICUIsRTL() { + if (g_icu_text_direction == UNKNOWN_DIRECTION) { + const icu::Locale& locale = icu::Locale::getDefault(); + g_icu_text_direction = GetTextDirectionForLocale(locale.getName()); + } + return g_icu_text_direction == RIGHT_TO_LEFT; +} + +TextDirection GetTextDirectionForLocale(const char* locale_name) { +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) + // lbshell does not have the icu tables needed to determine RTL-ness. + // Rather than beef up our icu tables, hard-code the list of RTL languages + // that Chrome supports. RTL layout is implemented by other components, + // so this does not affect our ability to do RTL layout nor RTL text. + return (!strncmp(locale_name, "he", 2) || + !strncmp(locale_name, "ar", 2) || + !strncmp(locale_name, "iw", 2) || + !strncmp(locale_name, "fa", 2) || + !strncmp(locale_name, "ur", 2)) + ? RIGHT_TO_LEFT : LEFT_TO_RIGHT; +#else + UErrorCode status = U_ZERO_ERROR; + ULayoutType layout_dir = uloc_getCharacterOrientation(locale_name, &status); + DCHECK(U_SUCCESS(status)); + // Treat anything other than RTL as LTR. + return (layout_dir != ULOC_LAYOUT_RTL) ? LEFT_TO_RIGHT : RIGHT_TO_LEFT; +#endif +} + +TextDirection GetFirstStrongCharacterDirection(const string16& text) { + const UChar* string = text.c_str(); + size_t length = text.length(); + size_t position = 0; + while (position < length) { + UChar32 character; + size_t next_position = position; + U16_NEXT(string, next_position, length, character); + + // Now that we have the character, we use ICU in order to query for the + // appropriate Unicode BiDi character type. + int32_t property = u_getIntPropertyValue(character, UCHAR_BIDI_CLASS); + if ((property == U_RIGHT_TO_LEFT) || + (property == U_RIGHT_TO_LEFT_ARABIC) || + (property == U_RIGHT_TO_LEFT_EMBEDDING) || + (property == U_RIGHT_TO_LEFT_OVERRIDE)) { + return RIGHT_TO_LEFT; + } else if ((property == U_LEFT_TO_RIGHT) || + (property == U_LEFT_TO_RIGHT_EMBEDDING) || + (property == U_LEFT_TO_RIGHT_OVERRIDE)) { + return LEFT_TO_RIGHT; + } + + position = next_position; + } + + return LEFT_TO_RIGHT; +} + +#if defined(OS_WIN) +bool AdjustStringForLocaleDirection(string16* text) { + if (!IsRTL() || text->empty()) + return false; + + // Marking the string as LTR if the locale is RTL and the string does not + // contain strong RTL characters. Otherwise, mark the string as RTL. + bool has_rtl_chars = StringContainsStrongRTLChars(*text); + if (!has_rtl_chars) + WrapStringWithLTRFormatting(text); + else + WrapStringWithRTLFormatting(text); + + return true; +} + +bool UnadjustStringForLocaleDirection(string16* text) { + if (!IsRTL() || text->empty()) + return false; + + *text = StripWrappingBidiControlCharacters(*text); + return true; +} +#else +bool AdjustStringForLocaleDirection(string16* text) { + // On OS X & GTK the directionality of a label is determined by the first + // strongly directional character. + // However, we want to make sure that in an LTR-language-UI all strings are + // left aligned and vice versa. + // A problem can arise if we display a string which starts with user input. + // User input may be of the opposite directionality to the UI. So the whole + // string will be displayed in the opposite directionality, e.g. if we want to + // display in an LTR UI [such as US English]: + // + // EMAN_NOISNETXE is now installed. + // + // Since EXTENSION_NAME begins with a strong RTL char, the label's + // directionality will be set to RTL and the string will be displayed visually + // as: + // + // .is now installed EMAN_NOISNETXE + // + // In order to solve this issue, we prepend an LRM to the string. An LRM is a + // strongly directional LTR char. + // We also append an LRM at the end, which ensures that we're in an LTR + // context. + + // Unlike Windows, Linux and OS X can correctly display RTL glyphs out of the + // box so there is no issue with displaying zero-width bidi control characters + // on any system. Thus no need for the !IsRTL() check here. + if (text->empty()) + return false; + + bool ui_direction_is_rtl = IsRTL(); + + bool has_rtl_chars = StringContainsStrongRTLChars(*text); + if (!ui_direction_is_rtl && has_rtl_chars) { + WrapStringWithRTLFormatting(text); + text->insert(0U, 1U, kLeftToRightMark); + text->push_back(kLeftToRightMark); + } else if (ui_direction_is_rtl && has_rtl_chars) { + WrapStringWithRTLFormatting(text); + text->insert(0U, 1U, kRightToLeftMark); + text->push_back(kRightToLeftMark); + } else if (ui_direction_is_rtl) { + WrapStringWithLTRFormatting(text); + text->insert(0U, 1U, kRightToLeftMark); + text->push_back(kRightToLeftMark); + } else { + return false; + } + + return true; +} + +bool UnadjustStringForLocaleDirection(string16* text) { + if (text->empty()) + return false; + + size_t begin_index = 0; + char16 begin = text->at(begin_index); + if (begin == kLeftToRightMark || + begin == kRightToLeftMark) { + ++begin_index; + } + + size_t end_index = text->length() - 1; + char16 end = text->at(end_index); + if (end == kLeftToRightMark || + end == kRightToLeftMark) { + --end_index; + } + + string16 unmarked_text = + text->substr(begin_index, end_index - begin_index + 1); + *text = StripWrappingBidiControlCharacters(unmarked_text); + return true; +} + +#endif // !OS_WIN + +bool StringContainsStrongRTLChars(const string16& text) { + const UChar* string = text.c_str(); + size_t length = text.length(); + size_t position = 0; + while (position < length) { + UChar32 character; + size_t next_position = position; + U16_NEXT(string, next_position, length, character); + + // Now that we have the character, we use ICU in order to query for the + // appropriate Unicode BiDi character type. + int32_t property = u_getIntPropertyValue(character, UCHAR_BIDI_CLASS); + if ((property == U_RIGHT_TO_LEFT) || (property == U_RIGHT_TO_LEFT_ARABIC)) + return true; + + position = next_position; + } + + return false; +} + +void WrapStringWithLTRFormatting(string16* text) { + if (text->empty()) + return; + + // Inserting an LRE (Left-To-Right Embedding) mark as the first character. + text->insert(0U, 1U, kLeftToRightEmbeddingMark); + + // Inserting a PDF (Pop Directional Formatting) mark as the last character. + text->push_back(kPopDirectionalFormatting); +} + +void WrapStringWithRTLFormatting(string16* text) { + if (text->empty()) + return; + + // Inserting an RLE (Right-To-Left Embedding) mark as the first character. + text->insert(0U, 1U, kRightToLeftEmbeddingMark); + + // Inserting a PDF (Pop Directional Formatting) mark as the last character. + text->push_back(kPopDirectionalFormatting); +} + +void WrapPathWithLTRFormatting(const FilePath& path, + string16* rtl_safe_path) { + // Wrap the overall path with LRE-PDF pair which essentialy marks the + // string as a Left-To-Right string. + // Inserting an LRE (Left-To-Right Embedding) mark as the first character. + rtl_safe_path->push_back(kLeftToRightEmbeddingMark); +#if defined(OS_MACOSX) + rtl_safe_path->append(UTF8ToUTF16(path.value())); +#elif defined(OS_WIN) + rtl_safe_path->append(path.value()); +#else // defined(OS_POSIX) && !defined(OS_MACOSX) + std::wstring wide_path = base::SysNativeMBToWide(path.value()); + rtl_safe_path->append(WideToUTF16(wide_path)); +#endif + // Inserting a PDF (Pop Directional Formatting) mark as the last character. + rtl_safe_path->push_back(kPopDirectionalFormatting); +} + +string16 GetDisplayStringInLTRDirectionality(const string16& text) { + // Always wrap the string in RTL UI (it may be appended to RTL string). + // Also wrap strings with an RTL first strong character direction in LTR UI. + if (IsRTL() || GetFirstStrongCharacterDirection(text) == RIGHT_TO_LEFT) { + string16 text_mutable(text); + WrapStringWithLTRFormatting(&text_mutable); + return text_mutable; + } + return text; +} + +string16 StripWrappingBidiControlCharacters(const string16& text) { + if (text.empty()) + return text; + size_t begin_index = 0; + char16 begin = text[begin_index]; + if (begin == kLeftToRightEmbeddingMark || + begin == kRightToLeftEmbeddingMark || + begin == kLeftToRightOverride || + begin == kRightToLeftOverride) + ++begin_index; + size_t end_index = text.length() - 1; + if (text[end_index] == kPopDirectionalFormatting) + --end_index; + return text.substr(begin_index, end_index - begin_index + 1); +} + +} // namespace i18n +} // namespace base
diff --git a/src/base/i18n/rtl.h b/src/base/i18n/rtl.h new file mode 100644 index 0000000..202a126 --- /dev/null +++ b/src/base/i18n/rtl.h
@@ -0,0 +1,136 @@ +// 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. + +#ifndef BASE_I18N_RTL_H_ +#define BASE_I18N_RTL_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "base/i18n/base_i18n_export.h" +#include "base/string16.h" +#include "build/build_config.h" + +class FilePath; + +namespace base { +namespace i18n { + +const char16 kRightToLeftMark = 0x200F; +const char16 kLeftToRightMark = 0x200E; +const char16 kLeftToRightEmbeddingMark = 0x202A; +const char16 kRightToLeftEmbeddingMark = 0x202B; +const char16 kPopDirectionalFormatting = 0x202C; +const char16 kLeftToRightOverride = 0x202D; +const char16 kRightToLeftOverride = 0x202E; + +enum TextDirection { + UNKNOWN_DIRECTION, + RIGHT_TO_LEFT, + LEFT_TO_RIGHT, +}; + +// Get the locale that the currently running process has been configured to use. +// The return value is of the form language[-country] (e.g., en-US) where the +// language is the 2 or 3 letter code from ISO-639. +BASE_I18N_EXPORT std::string GetConfiguredLocale(); + +// Canonicalize a string (eg. a POSIX locale string) to a Chrome locale name. +BASE_I18N_EXPORT std::string GetCanonicalLocale(const char* locale); + +// Sets the default locale of ICU. +// Once the application locale of Chrome in GetApplicationLocale is determined, +// the default locale of ICU need to be changed to match the application locale +// so that ICU functions work correctly in a locale-dependent manner. +// This is handy in that we don't have to call GetApplicationLocale() +// everytime we call locale-dependent ICU APIs as long as we make sure +// that this is called before any locale-dependent API is called. +BASE_I18N_EXPORT void SetICUDefaultLocale(const std::string& locale_string); + +// Returns true if the application text direction is right-to-left. +BASE_I18N_EXPORT bool IsRTL(); + +// Returns whether the text direction for the default ICU locale is RTL. This +// assumes that SetICUDefaultLocale has been called to set the default locale to +// the UI locale of Chrome. +// NOTE: Generally, you should call IsRTL() instead of this. +BASE_I18N_EXPORT bool ICUIsRTL(); + +// Returns the text direction for |locale_name|. +BASE_I18N_EXPORT TextDirection GetTextDirectionForLocale( + const char* locale_name); + +// Given the string in |text|, returns the directionality of the first +// character with strong directionality in the string. If no character in the +// text has strong directionality, LEFT_TO_RIGHT is returned. The Bidi +// character types L, LRE, LRO, R, AL, RLE, and RLO are considered as strong +// directionality characters. Please refer to http://unicode.org/reports/tr9/ +// for more information. +BASE_I18N_EXPORT TextDirection GetFirstStrongCharacterDirection( + const string16& text); + +// Given the string in |text|, this function modifies the string in place with +// the appropriate Unicode formatting marks that mark the string direction +// (either left-to-right or right-to-left). The function checks both the current +// locale and the contents of the string in order to determine the direction of +// the returned string. The function returns true if the string in |text| was +// properly adjusted. +// +// Certain LTR strings are not rendered correctly when the context is RTL. For +// example, the string "Foo!" will appear as "!Foo" if it is rendered as is in +// an RTL context. Calling this function will make sure the returned localized +// string is always treated as a right-to-left string. This is done by +// inserting certain Unicode formatting marks into the returned string. +// +// ** Notes about the Windows version of this function: +// TODO(idana) bug 6806: this function adjusts the string in question only +// if the current locale is right-to-left. The function does not take care of +// the opposite case (an RTL string displayed in an LTR context) since +// adjusting the string involves inserting Unicode formatting characters that +// Windows does not handle well unless right-to-left language support is +// installed. Since the English version of Windows doesn't have right-to-left +// language support installed by default, inserting the direction Unicode mark +// results in Windows displaying squares. +BASE_I18N_EXPORT bool AdjustStringForLocaleDirection(string16* text); + +// Undoes the actions of the above function (AdjustStringForLocaleDirection). +BASE_I18N_EXPORT bool UnadjustStringForLocaleDirection(string16* text); + +// Returns true if the string contains at least one character with strong right +// to left directionality; that is, a character with either R or AL Unicode +// BiDi character type. +BASE_I18N_EXPORT bool StringContainsStrongRTLChars(const string16& text); + +// Wraps a string with an LRE-PDF pair which essentialy marks the string as a +// Left-To-Right string. Doing this is useful in order to make sure LTR +// strings are rendered properly in an RTL context. +BASE_I18N_EXPORT void WrapStringWithLTRFormatting(string16* text); + +// Wraps a string with an RLE-PDF pair which essentialy marks the string as a +// Right-To-Left string. Doing this is useful in order to make sure RTL +// strings are rendered properly in an LTR context. +BASE_I18N_EXPORT void WrapStringWithRTLFormatting(string16* text); + +// Wraps file path to get it to display correctly in RTL UI. All filepaths +// should be passed through this function before display in UI for RTL locales. +BASE_I18N_EXPORT void WrapPathWithLTRFormatting(const FilePath& path, + string16* rtl_safe_path); + +// Return the string in |text| wrapped with LRE (Left-To-Right Embedding) and +// PDF (Pop Directional Formatting) marks, if needed for UI display purposes. +BASE_I18N_EXPORT string16 GetDisplayStringInLTRDirectionality( + const string16& text) WARN_UNUSED_RESULT; + +// Strip the beginning (U+202A..U+202B, U+202D..U+202E) and/or ending (U+202C) +// explicit bidi control characters from |text|, if there are any. Otherwise, +// return the text itself. Explicit bidi control characters display and have +// semantic effect. They can be deleted so they might not always appear in a +// pair. +BASE_I18N_EXPORT string16 StripWrappingBidiControlCharacters( + const string16& text) WARN_UNUSED_RESULT; + +} // namespace i18n +} // namespace base + +#endif // BASE_I18N_RTL_H_
diff --git a/src/base/i18n/rtl_unittest.cc b/src/base/i18n/rtl_unittest.cc new file mode 100644 index 0000000..176715a --- /dev/null +++ b/src/base/i18n/rtl_unittest.cc
@@ -0,0 +1,307 @@ +// 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/i18n/rtl.h" + +#include <algorithm> + +#include "base/file_path.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/sys_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" +#include "unicode/usearch.h" + +#if defined(TOOLKIT_GTK) +#include <gtk/gtk.h> +#endif + +namespace base { +namespace i18n { + +namespace { + +// A test utility function to set the application default text direction. +void SetRTL(bool rtl) { + // Override the current locale/direction. + SetICUDefaultLocale(rtl ? "he" : "en"); +#if defined(TOOLKIT_GTK) + // Do the same for GTK, which does not rely on the ICU default locale. + gtk_widget_set_default_direction(rtl ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR); +#endif + EXPECT_EQ(rtl, IsRTL()); +} + +} // namespace + +class RTLTest : public PlatformTest { +}; + +TEST_F(RTLTest, GetFirstStrongCharacterDirection) { + struct { + const wchar_t* text; + TextDirection direction; + } cases[] = { + // Test pure LTR string. + { L"foo bar", LEFT_TO_RIGHT }, + // Test bidi string in which the first character with strong directionality + // is a character with type L. + { L"foo \x05d0 bar", LEFT_TO_RIGHT }, + // Test bidi string in which the first character with strong directionality + // is a character with type R. + { L"\x05d0 foo bar", RIGHT_TO_LEFT }, + // Test bidi string which starts with a character with weak directionality + // and in which the first character with strong directionality is a + // character with type L. + { L"!foo \x05d0 bar", LEFT_TO_RIGHT }, + // Test bidi string which starts with a character with weak directionality + // and in which the first character with strong directionality is a + // character with type R. + { L",\x05d0 foo bar", RIGHT_TO_LEFT }, + // Test bidi string in which the first character with strong directionality + // is a character with type LRE. + { L"\x202a \x05d0 foo bar", LEFT_TO_RIGHT }, + // Test bidi string in which the first character with strong directionality + // is a character with type LRO. + { L"\x202d \x05d0 foo bar", LEFT_TO_RIGHT }, + // Test bidi string in which the first character with strong directionality + // is a character with type RLE. + { L"\x202b foo \x05d0 bar", RIGHT_TO_LEFT }, + // Test bidi string in which the first character with strong directionality + // is a character with type RLO. + { L"\x202e foo \x05d0 bar", RIGHT_TO_LEFT }, + // Test bidi string in which the first character with strong directionality + // is a character with type AL. + { L"\x0622 foo \x05d0 bar", RIGHT_TO_LEFT }, + // Test a string without strong directionality characters. + { L",!.{}", LEFT_TO_RIGHT }, + // Test empty string. + { L"", LEFT_TO_RIGHT }, + // Test characters in non-BMP (e.g. Phoenician letters. Please refer to + // http://demo.icu-project.org/icu-bin/ubrowse?scr=151&b=10910 for more + // information). + { +#if defined(WCHAR_T_IS_UTF32) + L" ! \x10910" L"abc 123", +#elif defined(WCHAR_T_IS_UTF16) + L" ! \xd802\xdd10" L"abc 123", +#else +#error wchar_t should be either UTF-16 or UTF-32 +#endif + RIGHT_TO_LEFT }, + { +#if defined(WCHAR_T_IS_UTF32) + L" ! \x10401" L"abc 123", +#elif defined(WCHAR_T_IS_UTF16) + L" ! \xd801\xdc01" L"abc 123", +#else +#error wchar_t should be either UTF-16 or UTF-32 +#endif + LEFT_TO_RIGHT }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) + EXPECT_EQ(cases[i].direction, + GetFirstStrongCharacterDirection(WideToUTF16(cases[i].text))); +} + +TEST_F(RTLTest, WrapPathWithLTRFormatting) { + const wchar_t* cases[] = { + // Test common path, such as "c:\foo\bar". + L"c:/foo/bar", + // Test path with file name, such as "c:\foo\bar\test.jpg". + L"c:/foo/bar/test.jpg", + // Test path ending with punctuation, such as "c:\(foo)\bar.". + L"c:/(foo)/bar.", + // Test path ending with separator, such as "c:\foo\bar\". + L"c:/foo/bar/", + // Test path with RTL character. + L"c:/\x05d0", + // Test path with 2 level RTL directory names. + L"c:/\x05d0/\x0622", + // Test path with mixed RTL/LTR directory names and ending with punctuation. + L"c:/\x05d0/\x0622/(foo)/b.a.r.", + // Test path without driver name, such as "/foo/bar/test/jpg". + L"/foo/bar/test.jpg", + // Test path start with current directory, such as "./foo". + L"./foo", + // Test path start with parent directory, such as "../foo/bar.jpg". + L"../foo/bar.jpg", + // Test absolute path, such as "//foo/bar.jpg". + L"//foo/bar.jpg", + // Test path with mixed RTL/LTR directory names. + L"c:/foo/\x05d0/\x0622/\x05d1.jpg", + // Test empty path. + L"" + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath path; +#if defined(OS_WIN) + std::wstring win_path(cases[i]); + std::replace(win_path.begin(), win_path.end(), '/', '\\'); + path = FilePath(win_path); + std::wstring wrapped_expected = + std::wstring(L"\x202a") + win_path + L"\x202c"; +#else + path = FilePath(base::SysWideToNativeMB(cases[i])); + std::wstring wrapped_expected = + std::wstring(L"\x202a") + cases[i] + L"\x202c"; +#endif + string16 localized_file_path_string; + WrapPathWithLTRFormatting(path, &localized_file_path_string); + + std::wstring wrapped_actual = UTF16ToWide(localized_file_path_string); + EXPECT_EQ(wrapped_expected, wrapped_actual); + } +} + +TEST_F(RTLTest, WrapString) { + const wchar_t* cases[] = { + L" . ", + L"abc", + L"a" L"\x5d0\x5d1", + L"a" L"\x5d1" L"b", + L"\x5d0\x5d1\x5d2", + L"\x5d0\x5d1" L"a", + L"\x5d0" L"a" L"\x5d1", + }; + + const bool was_rtl = IsRTL(); + + for (size_t i = 0; i < 2; ++i) { + // Toggle the application default text direction (to try each direction). + SetRTL(!IsRTL()); + + string16 empty; + WrapStringWithLTRFormatting(&empty); + EXPECT_TRUE(empty.empty()); + WrapStringWithRTLFormatting(&empty); + EXPECT_TRUE(empty.empty()); + + for (size_t i = 0; i < arraysize(cases); ++i) { + string16 input = WideToUTF16(cases[i]); + string16 ltr_wrap = input; + WrapStringWithLTRFormatting(<r_wrap); + EXPECT_EQ(ltr_wrap[0], kLeftToRightEmbeddingMark); + EXPECT_EQ(ltr_wrap.substr(1, ltr_wrap.length() - 2), input); + EXPECT_EQ(ltr_wrap[ltr_wrap.length() -1], kPopDirectionalFormatting); + + string16 rtl_wrap = input; + WrapStringWithRTLFormatting(&rtl_wrap); + EXPECT_EQ(rtl_wrap[0], kRightToLeftEmbeddingMark); + EXPECT_EQ(rtl_wrap.substr(1, rtl_wrap.length() - 2), input); + EXPECT_EQ(rtl_wrap[rtl_wrap.length() -1], kPopDirectionalFormatting); + } + } + + EXPECT_EQ(was_rtl, IsRTL()); +} + +TEST_F(RTLTest, GetDisplayStringInLTRDirectionality) { + struct { + const wchar_t* path; + bool wrap_ltr; + bool wrap_rtl; + } cases[] = { + { L"test", false, true }, + { L"test.html", false, true }, + { L"\x05d0\x05d1\x05d2", true, true }, + { L"\x05d0\x05d1\x05d2.txt", true, true }, + { L"\x05d0" L"abc", true, true }, + { L"\x05d0" L"abc.txt", true, true }, + { L"abc\x05d0\x05d1", false, true }, + { L"abc\x05d0\x05d1.jpg", false, true }, + }; + + const bool was_rtl = IsRTL(); + + for (size_t i = 0; i < 2; ++i) { + // Toggle the application default text direction (to try each direction). + SetRTL(!IsRTL()); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + string16 input = WideToUTF16(cases[i].path); + string16 output = GetDisplayStringInLTRDirectionality(input); + // Test the expected wrapping behavior for the current UI directionality. + if (IsRTL() ? cases[i].wrap_rtl : cases[i].wrap_ltr) + EXPECT_NE(output, input); + else + EXPECT_EQ(output, input); + } + } + + EXPECT_EQ(was_rtl, IsRTL()); +} + +TEST_F(RTLTest, GetTextDirection) { + EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("ar")); + EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("ar_EG")); + EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("he")); + EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("he_IL")); + // iw is an obsolete code for Hebrew. + EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("iw")); + // Although we're not yet localized to Farsi and Urdu, we + // do have the text layout direction information for them. + EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("fa")); + EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("ur")); +#if 0 + // Enable these when we include the minimal locale data for Azerbaijani + // written in Arabic and Dhivehi. At the moment, our copy of + // ICU data does not have entries for them. + EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("az_Arab")); + // Dhivehi that uses Thaana script. + EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("dv")); +#endif + EXPECT_EQ(LEFT_TO_RIGHT, GetTextDirectionForLocale("en")); + // Chinese in China with '-'. + EXPECT_EQ(LEFT_TO_RIGHT, GetTextDirectionForLocale("zh-CN")); + // Filipino : 3-letter code + EXPECT_EQ(LEFT_TO_RIGHT, GetTextDirectionForLocale("fil")); + // Russian + EXPECT_EQ(LEFT_TO_RIGHT, GetTextDirectionForLocale("ru")); + // Japanese that uses multiple scripts + EXPECT_EQ(LEFT_TO_RIGHT, GetTextDirectionForLocale("ja")); +} + +TEST_F(RTLTest, UnadjustStringForLocaleDirection) { + // These test strings are borrowed from WrapPathWithLTRFormatting + const wchar_t* cases[] = { + L"foo bar", + L"foo \x05d0 bar", + L"\x05d0 foo bar", + L"!foo \x05d0 bar", + L",\x05d0 foo bar", + L"\x202a \x05d0 foo bar", + L"\x202d \x05d0 foo bar", + L"\x202b foo \x05d0 bar", + L"\x202e foo \x05d0 bar", + L"\x0622 foo \x05d0 bar", + }; + + const bool was_rtl = IsRTL(); + + for (size_t i = 0; i < 2; ++i) { + // Toggle the application default text direction (to try each direction). + SetRTL(!IsRTL()); + + for (size_t i = 0; i < arraysize(cases); ++i) { + string16 test_case = WideToUTF16(cases[i]); + string16 adjusted_string = test_case; + + if (!AdjustStringForLocaleDirection(&adjusted_string)) + continue; + + EXPECT_NE(test_case, adjusted_string); + EXPECT_TRUE(UnadjustStringForLocaleDirection(&adjusted_string)); + EXPECT_EQ(test_case, adjusted_string) << " for test case [" << test_case + << "] with IsRTL() == " << IsRTL(); + } + } + + EXPECT_EQ(was_rtl, IsRTL()); +} + +} // namespace i18n +} // namespace base
diff --git a/src/base/i18n/string_search.cc b/src/base/i18n/string_search.cc new file mode 100644 index 0000000..9dc84ca --- /dev/null +++ b/src/base/i18n/string_search.cc
@@ -0,0 +1,78 @@ +// 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/i18n/string_search.h" +#include "base/logging.h" + +#include "unicode/usearch.h" + +namespace { + +bool CollationSensitiveStringSearch(const string16& find_this, + const string16& in_this, + UCollationStrength strength, + size_t* match_index, + size_t* match_length) { + UErrorCode status = U_ZERO_ERROR; + + UStringSearch* search = usearch_open(find_this.data(), -1, + in_this.data(), -1, + uloc_getDefault(), + NULL, // breakiter + &status); + + // Default to basic substring search if usearch fails. According to + // http://icu-project.org/apiref/icu4c/usearch_8h.html, usearch_open will fail + // if either |find_this| or |in_this| are empty. In either case basic + // substring search will give the correct return value. + if (!U_SUCCESS(status)) { + size_t index = in_this.find(find_this); + if (index == string16::npos) { + return false; + } else { + if (match_index) + *match_index = index; + if (match_length) + *match_length = find_this.size(); + return true; + } + } + + UCollator* collator = usearch_getCollator(search); + ucol_setStrength(collator, strength); + usearch_reset(search); + + int32_t index = usearch_first(search, &status); + if (!U_SUCCESS(status) || index == USEARCH_DONE) { + usearch_close(search); + return false; + } + + if (match_index) + *match_index = static_cast<size_t>(index); + if (match_length) + *match_length = static_cast<size_t>(usearch_getMatchedLength(search)); + + usearch_close(search); + return true; +} + +} // namespace + +namespace base { +namespace i18n { + +bool StringSearchIgnoringCaseAndAccents(const string16& find_this, + const string16& in_this, + size_t* match_index, + size_t* match_length) { + return CollationSensitiveStringSearch(find_this, + in_this, + UCOL_PRIMARY, + match_index, + match_length); +} + +} // namespace i18n +} // namespace base
diff --git a/src/base/i18n/string_search.h b/src/base/i18n/string_search.h new file mode 100644 index 0000000..2069b0f --- /dev/null +++ b/src/base/i18n/string_search.h
@@ -0,0 +1,30 @@ +// 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. + +#ifndef BASE_I18N_STRING_SEARCH_H_ +#define BASE_I18N_STRING_SEARCH_H_ + +#include "base/i18n/base_i18n_export.h" +#include "base/string16.h" + +namespace base { +namespace i18n { + +// Returns true if |in_this| contains |find_this|. If |match_index| or +// |match_length| are non-NULL, they are assigned the start position and total +// length of the match. +// +// Only differences between base letters are taken into consideration. Case and +// accent differences are ignored. Please refer to 'primary level' in +// http://userguide.icu-project.org/collation/concepts for additional details. +BASE_I18N_EXPORT + bool StringSearchIgnoringCaseAndAccents(const string16& find_this, + const string16& in_this, + size_t* match_index, + size_t* match_length); + +} // namespace i18n +} // namespace base + +#endif // BASE_I18N_STRING_SEARCH_H_
diff --git a/src/base/i18n/string_search_unittest.cc b/src/base/i18n/string_search_unittest.cc new file mode 100644 index 0000000..e6ca1c5 --- /dev/null +++ b/src/base/i18n/string_search_unittest.cc
@@ -0,0 +1,203 @@ +// 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 <string> + +#include "base/i18n/rtl.h" +#include "base/i18n/string_search.h" +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "unicode/usearch.h" + +namespace base { +namespace i18n { + +// Note on setting default locale for testing: The current default locale on +// the Mac trybot is en_US_POSIX, with which primary-level collation strength +// string search is case-sensitive, when normally it should be +// case-insensitive. In other locales (including en_US which English speakers +// in the U.S. use), this search would be case-insensitive as expected. + +TEST(StringSearchTest, ASCII) { + std::string default_locale(uloc_getDefault()); + bool locale_is_posix = (default_locale == "en_US_POSIX"); + if (locale_is_posix) + SetICUDefaultLocale("en_US"); + + size_t index = 0; + size_t length = 0; + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + ASCIIToUTF16("hello"), ASCIIToUTF16("hello world"), &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(5U, length); + + EXPECT_FALSE(StringSearchIgnoringCaseAndAccents( + ASCIIToUTF16("h e l l o"), ASCIIToUTF16("h e l l o"), + &index, &length)); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + ASCIIToUTF16("aabaaa"), ASCIIToUTF16("aaabaabaaa"), &index, &length)); + EXPECT_EQ(4U, index); + EXPECT_EQ(6U, length); + + EXPECT_FALSE(StringSearchIgnoringCaseAndAccents( + ASCIIToUTF16("searching within empty string"), string16(), + &index, &length)); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + string16(), ASCIIToUTF16("searching for empty string"), &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(0U, length); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + ASCIIToUTF16("case insensitivity"), ASCIIToUTF16("CaSe InSeNsItIvItY"), + &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(18U, length); + + if (locale_is_posix) + SetICUDefaultLocale(default_locale.data()); +} + +TEST(StringSearchTest, UnicodeLocaleIndependent) { + // Base characters + const string16 e_base = WideToUTF16(L"e"); + const string16 E_base = WideToUTF16(L"E"); + const string16 a_base = WideToUTF16(L"a"); + + // Composed characters + const string16 e_with_acute_accent = WideToUTF16(L"\u00e9"); + const string16 E_with_acute_accent = WideToUTF16(L"\u00c9"); + const string16 e_with_grave_accent = WideToUTF16(L"\u00e8"); + const string16 E_with_grave_accent = WideToUTF16(L"\u00c8"); + const string16 a_with_acute_accent = WideToUTF16(L"\u00e1"); + + // Decomposed characters + const string16 e_with_acute_combining_mark = WideToUTF16(L"e\u0301"); + const string16 E_with_acute_combining_mark = WideToUTF16(L"E\u0301"); + const string16 e_with_grave_combining_mark = WideToUTF16(L"e\u0300"); + const string16 E_with_grave_combining_mark = WideToUTF16(L"E\u0300"); + const string16 a_with_acute_combining_mark = WideToUTF16(L"a\u0301"); + + std::string default_locale(uloc_getDefault()); + bool locale_is_posix = (default_locale == "en_US_POSIX"); + if (locale_is_posix) + SetICUDefaultLocale("en_US"); + + size_t index = 0; + size_t length = 0; + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + e_base, e_with_acute_accent, &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(e_with_acute_accent.size(), length); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + e_with_acute_accent, e_base, &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(e_base.size(), length); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + e_base, e_with_acute_combining_mark, &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(e_with_acute_combining_mark.size(), length); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + e_with_acute_combining_mark, e_base, &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(e_base.size(), length); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + e_with_acute_combining_mark, e_with_acute_accent, + &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(e_with_acute_accent.size(), length); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + e_with_acute_accent, e_with_acute_combining_mark, + &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(e_with_acute_combining_mark.size(), length); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + e_with_acute_combining_mark, e_with_grave_combining_mark, + &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(e_with_grave_combining_mark.size(), length); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + e_with_grave_combining_mark, e_with_acute_combining_mark, + &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(e_with_acute_combining_mark.size(), length); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + e_with_acute_combining_mark, e_with_grave_accent, &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(e_with_grave_accent.size(), length); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + e_with_grave_accent, e_with_acute_combining_mark, &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(e_with_acute_combining_mark.size(), length); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + E_with_acute_accent, e_with_acute_accent, &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(e_with_acute_accent.size(), length); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + E_with_grave_accent, e_with_acute_accent, &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(e_with_acute_accent.size(), length); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + E_with_acute_combining_mark, e_with_grave_accent, &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(e_with_grave_accent.size(), length); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + E_with_grave_combining_mark, e_with_acute_accent, &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(e_with_acute_accent.size(), length); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + E_base, e_with_grave_accent, &index, &length)); + EXPECT_EQ(0U, index); + EXPECT_EQ(e_with_grave_accent.size(), length); + + EXPECT_FALSE(StringSearchIgnoringCaseAndAccents( + a_with_acute_accent, e_with_acute_accent, &index, &length)); + + EXPECT_FALSE(StringSearchIgnoringCaseAndAccents( + a_with_acute_combining_mark, e_with_acute_combining_mark, + &index, &length)); + + if (locale_is_posix) + SetICUDefaultLocale(default_locale.data()); +} + +TEST(StringSearchTest, UnicodeLocaleDependent) { + // Base characters + const string16 a_base = WideToUTF16(L"a"); + + // Composed characters + const string16 a_with_ring = WideToUTF16(L"\u00e5"); + + EXPECT_TRUE(StringSearchIgnoringCaseAndAccents( + a_base, a_with_ring, NULL, NULL)); + + const char* default_locale = uloc_getDefault(); + SetICUDefaultLocale("da"); + + EXPECT_FALSE(StringSearchIgnoringCaseAndAccents( + a_base, a_with_ring, NULL, NULL)); + + SetICUDefaultLocale(default_locale); +} + +} // namespace i18n +} // namespace base
diff --git a/src/base/i18n/time_formatting.cc b/src/base/i18n/time_formatting.cc new file mode 100644 index 0000000..9906dba --- /dev/null +++ b/src/base/i18n/time_formatting.cc
@@ -0,0 +1,164 @@ +// 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/i18n/time_formatting.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/utf_string_conversions.h" +#include "base/time.h" +#include "unicode/datefmt.h" +#include "unicode/dtptngen.h" +#include "unicode/smpdtfmt.h" + +using base::Time; + +namespace { + +string16 TimeFormat(const icu::DateFormat* formatter, + const Time& time) { + DCHECK(formatter); + icu::UnicodeString date_string; + + formatter->format(static_cast<UDate>(time.ToDoubleT() * 1000), date_string); + return string16(date_string.getBuffer(), + static_cast<size_t>(date_string.length())); +} + +string16 TimeFormatWithoutAmPm(const icu::DateFormat* formatter, + const Time& time) { + DCHECK(formatter); + icu::UnicodeString time_string; + + icu::FieldPosition ampm_field(icu::DateFormat::kAmPmField); + formatter->format( + static_cast<UDate>(time.ToDoubleT() * 1000), time_string, ampm_field); + int ampm_length = ampm_field.getEndIndex() - ampm_field.getBeginIndex(); + if (ampm_length) { + int begin = ampm_field.getBeginIndex(); + // Doesn't include any spacing before the field. + if (begin) + begin--; + time_string.removeBetween(begin, ampm_field.getEndIndex()); + } + return string16(time_string.getBuffer(), + static_cast<size_t>(time_string.length())); +} + +} // namespace + +namespace base { + +string16 TimeFormatTimeOfDay(const Time& time) { + // We can omit the locale parameter because the default should match + // Chrome's application locale. + scoped_ptr<icu::DateFormat> formatter( + icu::DateFormat::createTimeInstance(icu::DateFormat::kShort)); + return TimeFormat(formatter.get(), time); +} + +string16 TimeFormatTimeOfDayWithHourClockType(const Time& time, + HourClockType type, + AmPmClockType ampm) { + // Just redirect to the normal function if the default type matches the + // given type. + HourClockType default_type = GetHourClockType(); + if (default_type == type && (type == k24HourClock || ampm == kKeepAmPm)) { + return TimeFormatTimeOfDay(time); + } + + // Generate a locale-dependent format pattern. The generator will take + // care of locale-dependent formatting issues like which separator to + // use (some locales use '.' instead of ':'), and where to put the am/pm + // marker. + UErrorCode status = U_ZERO_ERROR; + scoped_ptr<icu::DateTimePatternGenerator> generator( + icu::DateTimePatternGenerator::createInstance(status)); + DCHECK(U_SUCCESS(status)); + const char* base_pattern = (type == k12HourClock ? "ahm" : "Hm"); + icu::UnicodeString generated_pattern = + generator->getBestPattern(icu::UnicodeString(base_pattern), status); + DCHECK(U_SUCCESS(status)); + + // Then, format the time using the generated pattern. + icu::SimpleDateFormat formatter(generated_pattern, status); + DCHECK(U_SUCCESS(status)); + if (ampm == kKeepAmPm) { + return TimeFormat(&formatter, time); + } else { + return TimeFormatWithoutAmPm(&formatter, time); + } +} + +string16 TimeFormatShortDate(const Time& time) { + scoped_ptr<icu::DateFormat> formatter( + icu::DateFormat::createDateInstance(icu::DateFormat::kMedium)); + return TimeFormat(formatter.get(), time); +} + +string16 TimeFormatShortDateNumeric(const Time& time) { + scoped_ptr<icu::DateFormat> formatter( + icu::DateFormat::createDateInstance(icu::DateFormat::kShort)); + return TimeFormat(formatter.get(), time); +} + +string16 TimeFormatShortDateAndTime(const Time& time) { + scoped_ptr<icu::DateFormat> formatter( + icu::DateFormat::createDateTimeInstance(icu::DateFormat::kShort)); + return TimeFormat(formatter.get(), time); +} + +string16 TimeFormatFriendlyDateAndTime(const Time& time) { + scoped_ptr<icu::DateFormat> formatter( + icu::DateFormat::createDateTimeInstance(icu::DateFormat::kFull)); + return TimeFormat(formatter.get(), time); +} + +string16 TimeFormatFriendlyDate(const Time& time) { + scoped_ptr<icu::DateFormat> formatter(icu::DateFormat::createDateInstance( + icu::DateFormat::kFull)); + return TimeFormat(formatter.get(), time); +} + +HourClockType GetHourClockType() { + // TODO(satorux,jshin): Rework this with ures_getByKeyWithFallback() + // once it becomes public. The short time format can be found at + // "calendar/gregorian/DateTimePatterns/3" in the resources. + scoped_ptr<icu::SimpleDateFormat> formatter( + static_cast<icu::SimpleDateFormat*>( + icu::DateFormat::createTimeInstance(icu::DateFormat::kShort))); + // Retrieve the short time format. + icu::UnicodeString pattern_unicode; + formatter->toPattern(pattern_unicode); + + // Determine what hour clock type the current locale uses, by checking + // "a" (am/pm marker) in the short time format. This is reliable as "a" + // is used by all of 12-hour clock formats, but not any of 24-hour clock + // formats, as shown below. + // + // % grep -A4 DateTimePatterns third_party/icu/source/data/locales/*.txt | + // grep -B1 -- -- |grep -v -- '--' | + // perl -nle 'print $1 if /^\S+\s+"(.*)"/' |sort -u + // + // H.mm + // H:mm + // HH.mm + // HH:mm + // a h:mm + // ah:mm + // ahh:mm + // h-mm a + // h:mm a + // hh:mm a + // + // See http://userguide.icu-project.org/formatparse/datetime for details + // about the date/time format syntax. + if (pattern_unicode.indexOf('a') == -1) { + return k24HourClock; + } else { + return k12HourClock; + } +} + +} // namespace base
diff --git a/src/base/i18n/time_formatting.h b/src/base/i18n/time_formatting.h new file mode 100644 index 0000000..91b79c6 --- /dev/null +++ b/src/base/i18n/time_formatting.h
@@ -0,0 +1,66 @@ +// 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. + +// Basic time formatting methods. These methods use the current locale +// formatting for displaying the time. + +#ifndef BASE_I18N_TIME_FORMATTING_H_ +#define BASE_I18N_TIME_FORMATTING_H_ + +#include "base/i18n/base_i18n_export.h" +#include "base/string16.h" + +namespace base { + +class Time; + +// Argument type used to specify the hour clock type. +enum HourClockType { + k12HourClock, // Uses 1-12. e.g., "3:07 PM" + k24HourClock, // Uses 0-23. e.g., "15:07" +}; + +// Argument type used to specify whether or not to include AM/PM sign. +enum AmPmClockType { + kDropAmPm, // Drops AM/PM sign. e.g., "3:07" + kKeepAmPm, // Keeps AM/PM sign. e.g., "3:07 PM" +}; + +// Returns the time of day, e.g., "3:07 PM". +BASE_I18N_EXPORT string16 TimeFormatTimeOfDay(const Time& time); + +// Returns the time of day in the specified hour clock type. e.g. +// "3:07 PM" (type == k12HourClock, ampm == kKeepAmPm). +// "3:07" (type == k12HourClock, ampm == kDropAmPm). +// "15:07" (type == k24HourClock). +BASE_I18N_EXPORT string16 TimeFormatTimeOfDayWithHourClockType( + const Time& time, + HourClockType type, + AmPmClockType ampm); + +// Returns a shortened date, e.g. "Nov 7, 2007" +BASE_I18N_EXPORT string16 TimeFormatShortDate(const Time& time); + +// Returns a numeric date such as 12/13/52. +BASE_I18N_EXPORT string16 TimeFormatShortDateNumeric(const Time& time); + +// Returns a numeric date and time such as "12/13/52 2:44:30 PM". +BASE_I18N_EXPORT string16 TimeFormatShortDateAndTime(const Time& time); + +// Formats a time in a friendly sentence format, e.g. +// "Monday, March 6, 2008 2:44:30 PM". +BASE_I18N_EXPORT string16 TimeFormatFriendlyDateAndTime(const Time& time); + +// Formats a time in a friendly sentence format, e.g. +// "Monday, March 6, 2008". +BASE_I18N_EXPORT string16 TimeFormatFriendlyDate(const Time& time); + +// Gets the hour clock type of the current locale. e.g. +// k12HourClock (en-US). +// k24HourClock (en-GB). +BASE_I18N_EXPORT HourClockType GetHourClockType(); + +} // namespace base + +#endif // BASE_I18N_TIME_FORMATTING_H_
diff --git a/src/base/i18n/time_formatting_unittest.cc b/src/base/i18n/time_formatting_unittest.cc new file mode 100644 index 0000000..89b5c55 --- /dev/null +++ b/src/base/i18n/time_formatting_unittest.cc
@@ -0,0 +1,181 @@ +// 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/i18n/time_formatting.h" + +#include "base/i18n/rtl.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +#if defined(__LB_SHELL__) + +// Some legacy platforms' local time calculations always use the current +// Daylight Savings Time status when exploding/unexploding time ticks, no matter +// what the time to explode is. On Starboard platforms, we use ICU instead as it +// is platform-independent and considerably more correct. +#define MAYBE_TimeFormatTimeOfDayDefault12h \ + DISABLED_TimeFormatTimeOfDayDefault12h +#define MAYBE_TimeFormatTimeOfDayDefault24h \ + DISABLED_TimeFormatTimeOfDayDefault24h +#define MAYBE_TimeFormatTimeOfDayJP \ + DISABLED_TimeFormatTimeOfDayJP +#define MAYBE_TimeFormatDateUS \ + DISABLED_TimeFormatDateUS +#define MAYBE_TimeFormatDateGB \ + DISABLED_TimeFormatDateGB + +#else + +#define MAYBE_TimeFormatTimeOfDayDefault12h TimeFormatTimeOfDayDefault12h +#define MAYBE_TimeFormatTimeOfDayDefault24h TimeFormatTimeOfDayDefault24h +#define MAYBE_TimeFormatTimeOfDayJP TimeFormatTimeOfDayJP +#define MAYBE_TimeFormatDateUS TimeFormatDateUS +#define MAYBE_TimeFormatDateGB TimeFormatDateGB + +#endif + +const Time::Exploded kTestDateTimeExploded = { + 2011, 4, 6, 30, // Sat, Apr 30, 2011 + 15, 42, 7, 0 // 15:42:07.000 +}; + +TEST(TimeFormattingTest, MAYBE_TimeFormatTimeOfDayDefault12h) { + // Test for a locale defaulted to 12h clock. + // As an instance, we use third_party/icu/source/data/locales/en.txt. + i18n::SetICUDefaultLocale("en_US"); + + Time time(Time::FromLocalExploded(kTestDateTimeExploded)); + string16 clock24h(ASCIIToUTF16("15:42")); + string16 clock12h_pm(ASCIIToUTF16("3:42 PM")); + string16 clock12h(ASCIIToUTF16("3:42")); + + // The default is 12h clock. + EXPECT_EQ(clock12h_pm, TimeFormatTimeOfDay(time)); + EXPECT_EQ(k12HourClock, GetHourClockType()); + // k{Keep,Drop}AmPm should not affect for 24h clock. + EXPECT_EQ(clock24h, + TimeFormatTimeOfDayWithHourClockType(time, + k24HourClock, + kKeepAmPm)); + EXPECT_EQ(clock24h, + TimeFormatTimeOfDayWithHourClockType(time, + k24HourClock, + kDropAmPm)); + // k{Keep,Drop}AmPm affects for 12h clock. + EXPECT_EQ(clock12h_pm, + TimeFormatTimeOfDayWithHourClockType(time, + k12HourClock, + kKeepAmPm)); + EXPECT_EQ(clock12h, + TimeFormatTimeOfDayWithHourClockType(time, + k12HourClock, + kDropAmPm)); +} + +TEST(TimeFormattingTest, MAYBE_TimeFormatTimeOfDayDefault24h) { + // Test for a locale defaulted to 24h clock. + // As an instance, we use third_party/icu/source/data/locales/en_GB.txt. + i18n::SetICUDefaultLocale("en_GB"); + + Time time(Time::FromLocalExploded(kTestDateTimeExploded)); + string16 clock24h(ASCIIToUTF16("15:42")); + string16 clock12h_pm(ASCIIToUTF16("3:42 PM")); + string16 clock12h(ASCIIToUTF16("3:42")); + + // The default is 24h clock. + EXPECT_EQ(clock24h, TimeFormatTimeOfDay(time)); + EXPECT_EQ(k24HourClock, GetHourClockType()); + // k{Keep,Drop}AmPm should not affect for 24h clock. + EXPECT_EQ(clock24h, + TimeFormatTimeOfDayWithHourClockType(time, + k24HourClock, + kKeepAmPm)); + EXPECT_EQ(clock24h, + TimeFormatTimeOfDayWithHourClockType(time, + k24HourClock, + kDropAmPm)); + // k{Keep,Drop}AmPm affects for 12h clock. + EXPECT_EQ(clock12h_pm, + TimeFormatTimeOfDayWithHourClockType(time, + k12HourClock, + kKeepAmPm)); + EXPECT_EQ(clock12h, + TimeFormatTimeOfDayWithHourClockType(time, + k12HourClock, + kDropAmPm)); +} + +TEST(TimeFormattingTest, MAYBE_TimeFormatTimeOfDayJP) { + // Test for a locale that uses different mark than "AM" and "PM". + // As an instance, we use third_party/icu/source/data/locales/ja.txt. + i18n::SetICUDefaultLocale("ja_JP"); + + Time time(Time::FromLocalExploded(kTestDateTimeExploded)); + string16 clock24h(ASCIIToUTF16("15:42")); + string16 clock12h_pm(WideToUTF16(L"\x5348\x5f8c" L"3:42")); + string16 clock12h(ASCIIToUTF16("3:42")); + + // The default is 24h clock. + EXPECT_EQ(clock24h, TimeFormatTimeOfDay(time)); + EXPECT_EQ(k24HourClock, GetHourClockType()); + // k{Keep,Drop}AmPm should not affect for 24h clock. + EXPECT_EQ(clock24h, + TimeFormatTimeOfDayWithHourClockType(time, + k24HourClock, + kKeepAmPm)); + EXPECT_EQ(clock24h, + TimeFormatTimeOfDayWithHourClockType(time, + k24HourClock, + kDropAmPm)); + // k{Keep,Drop}AmPm affects for 12h clock. + EXPECT_EQ(clock12h_pm, + TimeFormatTimeOfDayWithHourClockType(time, + k12HourClock, + kKeepAmPm)); + EXPECT_EQ(clock12h, + TimeFormatTimeOfDayWithHourClockType(time, + k12HourClock, + kDropAmPm)); +} + +TEST(TimeFormattingTest, MAYBE_TimeFormatDateUS) { + // See third_party/icu/source/data/locales/en.txt. + // The date patterns are "EEEE, MMMM d, y", "MMM d, y", and "M/d/yy". + i18n::SetICUDefaultLocale("en_US"); + + Time time(Time::FromLocalExploded(kTestDateTimeExploded)); + + EXPECT_EQ(ASCIIToUTF16("Apr 30, 2011"), TimeFormatShortDate(time)); + EXPECT_EQ(ASCIIToUTF16("4/30/11"), TimeFormatShortDateNumeric(time)); + EXPECT_EQ(ASCIIToUTF16("4/30/11 3:42:07 PM"), + TimeFormatShortDateAndTime(time)); + EXPECT_EQ(ASCIIToUTF16("Saturday, April 30, 2011 3:42:07 PM"), + TimeFormatFriendlyDateAndTime(time)); + EXPECT_EQ(ASCIIToUTF16("Saturday, April 30, 2011"), + TimeFormatFriendlyDate(time)); +} + +TEST(TimeFormattingTest, MAYBE_TimeFormatDateGB) { + // See third_party/icu/source/data/locales/en_GB.txt. + // The date patterns are "EEEE, d MMMM y", "d MMM y", and "dd/MM/yyyy". + i18n::SetICUDefaultLocale("en_GB"); + + Time time(Time::FromLocalExploded(kTestDateTimeExploded)); + + EXPECT_EQ(ASCIIToUTF16("30 Apr 2011"), TimeFormatShortDate(time)); + EXPECT_EQ(ASCIIToUTF16("30/04/2011"), TimeFormatShortDateNumeric(time)); + EXPECT_EQ(ASCIIToUTF16("30/04/2011 15:42:07"), + TimeFormatShortDateAndTime(time)); + EXPECT_EQ(ASCIIToUTF16("Saturday, 30 April 2011 15:42:07"), + TimeFormatFriendlyDateAndTime(time)); + EXPECT_EQ(ASCIIToUTF16("Saturday, 30 April 2011"), + TimeFormatFriendlyDate(time)); +} + +} // namespace +} // namespace base
diff --git a/src/base/id_map.h b/src/base/id_map.h new file mode 100644 index 0000000..d3fc7b8 --- /dev/null +++ b/src/base/id_map.h
@@ -0,0 +1,257 @@ +// 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. + +#ifndef BASE_ID_MAP_H_ +#define BASE_ID_MAP_H_ + +#include <set> + +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "base/logging.h" +#include "base/threading/non_thread_safe.h" + +// Ownership semantics - own pointer means the pointer is deleted in Remove() +// & during destruction +enum IDMapOwnershipSemantics { + IDMapExternalPointer, + IDMapOwnPointer +}; + +// This object maintains a list of IDs that can be quickly converted to +// pointers to objects. It is implemented as a hash table, optimized for +// relatively small data sets (in the common case, there will be exactly one +// item in the list). +// +// Items can be inserted into the container with arbitrary ID, but the caller +// must ensure they are unique. Inserting IDs and relying on automatically +// generated ones is not allowed because they can collide. +// +// This class does not have a virtual destructor, do not inherit from it when +// ownership semantics are set to own because pointers will leak. +template<typename T, IDMapOwnershipSemantics OS = IDMapExternalPointer> +class IDMap : public base::NonThreadSafe { + private: + typedef int32 KeyType; + typedef base::hash_map<KeyType, T*> HashTable; + + public: + IDMap() : iteration_depth_(0), next_id_(1), check_on_null_data_(false) { + // A number of consumers of IDMap create it on one thread but always access + // it from a different, but consitent, thread post-construction. + DetachFromThread(); + } + + ~IDMap() { + // Many IDMap's are static, and hence will be destroyed on the main thread. + // However, all the accesses may take place on another thread, such as the + // IO thread. Detaching again to clean this up. + DetachFromThread(); + Releaser<OS, 0>::release_all(&data_); + } + + // Sets whether Add should CHECK if passed in NULL data. Default is false. + void set_check_on_null_data(bool value) { check_on_null_data_ = value; } + + // Adds a view with an automatically generated unique ID. See AddWithID. + KeyType Add(T* data) { + DCHECK(CalledOnValidThread()); + CHECK(!check_on_null_data_ || data); + KeyType this_id = next_id_; + DCHECK(data_.find(this_id) == data_.end()) << "Inserting duplicate item"; + data_[this_id] = data; + next_id_++; + return this_id; + } + + // Adds a new data member with the specified ID. The ID must not be in + // the list. The caller either must generate all unique IDs itself and use + // this function, or allow this object to generate IDs and call Add. These + // two methods may not be mixed, or duplicate IDs may be generated + void AddWithID(T* data, KeyType id) { + DCHECK(CalledOnValidThread()); + CHECK(!check_on_null_data_ || data); + DCHECK(data_.find(id) == data_.end()) << "Inserting duplicate item"; + data_[id] = data; + } + + void Remove(KeyType id) { + DCHECK(CalledOnValidThread()); + typename HashTable::iterator i = data_.find(id); + if (i == data_.end()) { + NOTREACHED() << "Attempting to remove an item not in the list"; + return; + } + + if (iteration_depth_ == 0) { + Releaser<OS, 0>::release(i->second); + data_.erase(i); + } else { + removed_ids_.insert(id); + } + } + + void Clear() { + DCHECK(CalledOnValidThread()); + if (iteration_depth_ == 0) { + Releaser<OS, 0>::release_all(&data_); + } else { + for (typename HashTable::iterator i = data_.begin(); + i != data_.end(); ++i) + removed_ids_.insert(i->first); + } + } + + bool IsEmpty() const { + DCHECK(CalledOnValidThread()); + return size() == 0u; + } + + T* Lookup(KeyType id) const { + DCHECK(CalledOnValidThread()); + typename HashTable::const_iterator i = data_.find(id); + if (i == data_.end()) + return NULL; + return i->second; + } + + size_t size() const { + DCHECK(CalledOnValidThread()); + return data_.size() - removed_ids_.size(); + } + +#if defined(UNIT_TEST) + int iteration_depth() const { + return iteration_depth_; + } +#endif // defined(UNIT_TEST) + + // It is safe to remove elements from the map during iteration. All iterators + // will remain valid. + template<class ReturnType> + class Iterator { + public: + Iterator(IDMap<T, OS>* map) + : map_(map), + iter_(map_->data_.begin()) { + Init(); + } + + Iterator(const Iterator& iter) + : map_(iter.map_), + iter_(iter.iter_) { + Init(); + } + + const Iterator& operator=(const Iterator& iter) { + map_ = iter.map; + iter_ = iter.iter; + Init(); + return *this; + } + + ~Iterator() { + DCHECK(map_->CalledOnValidThread()); + + // We're going to decrement iteration depth. Make sure it's greater than + // zero so that it doesn't become negative. + DCHECK_LT(0, map_->iteration_depth_); + + if (--map_->iteration_depth_ == 0) + map_->Compact(); + } + + bool IsAtEnd() const { + DCHECK(map_->CalledOnValidThread()); + return iter_ == map_->data_.end(); + } + + KeyType GetCurrentKey() const { + DCHECK(map_->CalledOnValidThread()); + return iter_->first; + } + + ReturnType* GetCurrentValue() const { + DCHECK(map_->CalledOnValidThread()); + return iter_->second; + } + + void Advance() { + DCHECK(map_->CalledOnValidThread()); + ++iter_; + SkipRemovedEntries(); + } + + private: + void Init() { + DCHECK(map_->CalledOnValidThread()); + ++map_->iteration_depth_; + SkipRemovedEntries(); + } + + void SkipRemovedEntries() { + while (iter_ != map_->data_.end() && + map_->removed_ids_.find(iter_->first) != + map_->removed_ids_.end()) { + ++iter_; + } + } + + IDMap<T, OS>* map_; + typename HashTable::const_iterator iter_; + }; + + typedef Iterator<T> iterator; + typedef Iterator<const T> const_iterator; + + private: + + // The dummy parameter is there because C++ standard does not allow + // explicitly specialized templates inside classes + template<IDMapOwnershipSemantics OI, int dummy> struct Releaser { + static inline void release(T* ptr) {} + static inline void release_all(HashTable* table) {} + }; + + template<int dummy> struct Releaser<IDMapOwnPointer, dummy> { + static inline void release(T* ptr) { delete ptr;} + static inline void release_all(HashTable* table) { + for (typename HashTable::iterator i = table->begin(); + i != table->end(); ++i) { + delete i->second; + } + table->clear(); + } + }; + + void Compact() { + DCHECK_EQ(0, iteration_depth_); + for (std::set<KeyType>::const_iterator i = removed_ids_.begin(); + i != removed_ids_.end(); ++i) { + Remove(*i); + } + removed_ids_.clear(); + } + + // Keep track of how many iterators are currently iterating on us to safely + // handle removing items during iteration. + int iteration_depth_; + + // Keep set of IDs that should be removed after the outermost iteration has + // finished. This way we manage to not invalidate the iterator when an element + // is removed. + std::set<KeyType> removed_ids_; + + // The next ID that we will return from Add() + KeyType next_id_; + + HashTable data_; + + // See description above setter. + bool check_on_null_data_; + + DISALLOW_COPY_AND_ASSIGN(IDMap); +}; + +#endif // BASE_ID_MAP_H_
diff --git a/src/base/id_map_unittest.cc b/src/base/id_map_unittest.cc new file mode 100644 index 0000000..53ca656 --- /dev/null +++ b/src/base/id_map_unittest.cc
@@ -0,0 +1,341 @@ +// 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/id_map.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class TestObject { +}; + +class DestructorCounter { + public: + explicit DestructorCounter(int* counter) : counter_(counter) {} + ~DestructorCounter() { ++(*counter_); } + + private: + int* counter_; +}; + +TEST(IDMapTest, Basic) { + IDMap<TestObject> map; + EXPECT_TRUE(map.IsEmpty()); + EXPECT_EQ(0U, map.size()); + + TestObject obj1; + TestObject obj2; + + int32 id1 = map.Add(&obj1); + EXPECT_FALSE(map.IsEmpty()); + EXPECT_EQ(1U, map.size()); + EXPECT_EQ(&obj1, map.Lookup(id1)); + + int32 id2 = map.Add(&obj2); + EXPECT_FALSE(map.IsEmpty()); + EXPECT_EQ(2U, map.size()); + + EXPECT_EQ(&obj1, map.Lookup(id1)); + EXPECT_EQ(&obj2, map.Lookup(id2)); + + map.Remove(id1); + EXPECT_FALSE(map.IsEmpty()); + EXPECT_EQ(1U, map.size()); + + map.Remove(id2); + EXPECT_TRUE(map.IsEmpty()); + EXPECT_EQ(0U, map.size()); + + map.AddWithID(&obj1, 1); + map.AddWithID(&obj2, 2); + EXPECT_EQ(&obj1, map.Lookup(1)); + EXPECT_EQ(&obj2, map.Lookup(2)); + + EXPECT_EQ(0, map.iteration_depth()); +} + +TEST(IDMapTest, IteratorRemainsValidWhenRemovingCurrentElement) { + IDMap<TestObject> map; + + TestObject obj1; + TestObject obj2; + TestObject obj3; + + map.Add(&obj1); + map.Add(&obj2); + map.Add(&obj3); + + { + IDMap<TestObject>::const_iterator iter(&map); + + EXPECT_EQ(1, map.iteration_depth()); + + while (!iter.IsAtEnd()) { + map.Remove(iter.GetCurrentKey()); + iter.Advance(); + } + + // Test that while an iterator is still in scope, we get the map emptiness + // right (http://crbug.com/35571). + EXPECT_TRUE(map.IsEmpty()); + EXPECT_EQ(0U, map.size()); + } + + EXPECT_TRUE(map.IsEmpty()); + EXPECT_EQ(0U, map.size()); + + EXPECT_EQ(0, map.iteration_depth()); +} + +TEST(IDMapTest, IteratorRemainsValidWhenRemovingOtherElements) { + IDMap<TestObject> map; + + const int kCount = 5; + TestObject obj[kCount]; + int32 ids[kCount]; + + for (int i = 0; i < kCount; i++) + ids[i] = map.Add(&obj[i]); + + int counter = 0; + for (IDMap<TestObject>::const_iterator iter(&map); + !iter.IsAtEnd(); iter.Advance()) { + EXPECT_EQ(1, map.iteration_depth()); + + switch (counter) { + case 0: + EXPECT_EQ(ids[0], iter.GetCurrentKey()); + EXPECT_EQ(&obj[0], iter.GetCurrentValue()); + map.Remove(ids[1]); + break; + case 1: + EXPECT_EQ(ids[2], iter.GetCurrentKey()); + EXPECT_EQ(&obj[2], iter.GetCurrentValue()); + map.Remove(ids[3]); + break; + case 2: + EXPECT_EQ(ids[4], iter.GetCurrentKey()); + EXPECT_EQ(&obj[4], iter.GetCurrentValue()); + map.Remove(ids[0]); + break; + default: + FAIL() << "should not have that many elements"; + break; + } + + counter++; + } + + EXPECT_EQ(0, map.iteration_depth()); +} + +TEST(IDMapTest, CopyIterator) { + IDMap<TestObject> map; + + TestObject obj1; + TestObject obj2; + TestObject obj3; + + map.Add(&obj1); + map.Add(&obj2); + map.Add(&obj3); + + EXPECT_EQ(0, map.iteration_depth()); + + { + IDMap<TestObject>::const_iterator iter1(&map); + EXPECT_EQ(1, map.iteration_depth()); + + // Make sure that copying the iterator correctly increments + // map's iteration depth. + IDMap<TestObject>::const_iterator iter2(iter1); + EXPECT_EQ(2, map.iteration_depth()); + } + + // Make sure after destroying all iterators the map's iteration depth + // returns to initial state. + EXPECT_EQ(0, map.iteration_depth()); +} + +TEST(IDMapTest, AssignIterator) { + IDMap<TestObject> map; + + TestObject obj1; + TestObject obj2; + TestObject obj3; + + map.Add(&obj1); + map.Add(&obj2); + map.Add(&obj3); + + EXPECT_EQ(0, map.iteration_depth()); + + { + IDMap<TestObject>::const_iterator iter1(&map); + EXPECT_EQ(1, map.iteration_depth()); + + IDMap<TestObject>::const_iterator iter2(&map); + EXPECT_EQ(2, map.iteration_depth()); + + // Make sure that assigning the iterator correctly updates + // map's iteration depth (-1 for destruction, +1 for assignment). + EXPECT_EQ(2, map.iteration_depth()); + } + + // Make sure after destroying all iterators the map's iteration depth + // returns to initial state. + EXPECT_EQ(0, map.iteration_depth()); +} + +// This test relies on specific ordering of items in a hash map to expect a +// given ID at a given iteration point. This is a bad expectation for a +// hash_map, and one that does not hold on lbshell platforms. This test +// should be rewritten. +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) +TEST(IDMapTest, IteratorRemainsValidWhenClearing) { + IDMap<TestObject> map; + + const int kCount = 5; + TestObject obj[kCount]; + int32 ids[kCount]; + + for (int i = 0; i < kCount; i++) + ids[i] = map.Add(&obj[i]); + + int counter = 0; + for (IDMap<TestObject>::const_iterator iter(&map); + !iter.IsAtEnd(); iter.Advance()) { + switch (counter) { + case 0: + EXPECT_EQ(ids[0], iter.GetCurrentKey()); + EXPECT_EQ(&obj[0], iter.GetCurrentValue()); + break; + case 1: + EXPECT_EQ(ids[1], iter.GetCurrentKey()); + EXPECT_EQ(&obj[1], iter.GetCurrentValue()); + map.Clear(); + EXPECT_TRUE(map.IsEmpty()); + EXPECT_EQ(0U, map.size()); + break; + default: + FAIL() << "should not have that many elements"; + break; + } + counter++; + } + + EXPECT_TRUE(map.IsEmpty()); + EXPECT_EQ(0U, map.size()); +} +#endif + +TEST(IDMapTest, OwningPointersDeletesThemOnRemove) { + const int kCount = 3; + + int external_del_count = 0; + DestructorCounter* external_obj[kCount]; + int map_external_ids[kCount]; + + int owned_del_count = 0; + DestructorCounter* owned_obj[kCount]; + int map_owned_ids[kCount]; + + IDMap<DestructorCounter> map_external; + IDMap<DestructorCounter, IDMapOwnPointer> map_owned; + + for (int i = 0; i < kCount; ++i) { + external_obj[i] = new DestructorCounter(&external_del_count); + map_external_ids[i] = map_external.Add(external_obj[i]); + + owned_obj[i] = new DestructorCounter(&owned_del_count); + map_owned_ids[i] = map_owned.Add(owned_obj[i]); + } + + for (int i = 0; i < kCount; ++i) { + EXPECT_EQ(external_del_count, 0); + EXPECT_EQ(owned_del_count, i); + + map_external.Remove(map_external_ids[i]); + map_owned.Remove(map_owned_ids[i]); + } + + for (int i = 0; i < kCount; ++i) { + delete external_obj[i]; + } + + EXPECT_EQ(external_del_count, kCount); + EXPECT_EQ(owned_del_count, kCount); +} + +TEST(IDMapTest, OwningPointersDeletesThemOnClear) { + const int kCount = 3; + + int external_del_count = 0; + DestructorCounter* external_obj[kCount]; + + int owned_del_count = 0; + DestructorCounter* owned_obj[kCount]; + + IDMap<DestructorCounter> map_external; + IDMap<DestructorCounter, IDMapOwnPointer> map_owned; + + for (int i = 0; i < kCount; ++i) { + external_obj[i] = new DestructorCounter(&external_del_count); + map_external.Add(external_obj[i]); + + owned_obj[i] = new DestructorCounter(&owned_del_count); + map_owned.Add(owned_obj[i]); + } + + EXPECT_EQ(external_del_count, 0); + EXPECT_EQ(owned_del_count, 0); + + map_external.Clear(); + map_owned.Clear(); + + EXPECT_EQ(external_del_count, 0); + EXPECT_EQ(owned_del_count, kCount); + + for (int i = 0; i < kCount; ++i) { + delete external_obj[i]; + } + + EXPECT_EQ(external_del_count, kCount); + EXPECT_EQ(owned_del_count, kCount); +} + +TEST(IDMapTest, OwningPointersDeletesThemOnDestruct) { + const int kCount = 3; + + int external_del_count = 0; + DestructorCounter* external_obj[kCount]; + + int owned_del_count = 0; + DestructorCounter* owned_obj[kCount]; + + { + IDMap<DestructorCounter> map_external; + IDMap<DestructorCounter, IDMapOwnPointer> map_owned; + + for (int i = 0; i < kCount; ++i) { + external_obj[i] = new DestructorCounter(&external_del_count); + map_external.Add(external_obj[i]); + + owned_obj[i] = new DestructorCounter(&owned_del_count); + map_owned.Add(owned_obj[i]); + } + } + + EXPECT_EQ(external_del_count, 0); + + for (int i = 0; i < kCount; ++i) { + delete external_obj[i]; + } + + EXPECT_EQ(external_del_count, kCount); + EXPECT_EQ(owned_del_count, kCount); +} + +} // namespace
diff --git a/src/base/ios/device_util.h b/src/base/ios/device_util.h new file mode 100644 index 0000000..fe4833d --- /dev/null +++ b/src/base/ios/device_util.h
@@ -0,0 +1,65 @@ +// 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_IOS_DEVICE_UTIL_H_ +#define BASE_IOS_DEVICE_UTIL_H_ + +#include <string> + +namespace ios { +namespace device_util { + +// Returns the hardware version of the device the app is running on. +// +// The returned string is the string returned by sysctlbyname() with name +// "hw.machine". Possible (known) values include: +// +// iPhone1,1 -> iPhone 1G +// iPhone1,2 -> iPhone 3G +// iPhone2,1 -> iPhone 3GS +// iPhone3,1 -> iPhone 4/AT&T +// iPhone3,2 -> iPhone 4/Other Carrier? +// iPhone3,3 -> iPhone 4/Other Carrier? +// iPhone4,1 -> iPhone 4S +// +// iPod1,1 -> iPod touch 1G +// iPod2,1 -> iPod touch 2G +// iPod2,2 -> ? +// iPod3,1 -> iPod touch 3G +// iPod4,1 -> iPod touch 4G +// iPod5,1 -> ? +// +// iPad1,1 -> iPad 1G, WiFi +// iPad1,? -> iPad 1G, 3G <- needs 3G owner to test +// iPad2,1 -> iPad 2G, WiFi +// +// AppleTV2,1 -> AppleTV 2 +// +// i386 -> Simulator +// x86_64 -> Simulator +std::string GetPlatform(); + +// Returns true if the application is running on a high-ram device. (>=250M). +bool IsRunningOnHighRamDevice(); + +// Returns true if the device has only one core. +bool IsSingleCoreDevice(); + +// Returns the MAC address of the interface with name |interface_name|. +std::string GetMacAddress(const std::string& interface_name); + +// Returns a random UUID. +std::string GetRandomId(); + +// Returns an identifier for the device, using the given |salt|. A global +// identifier is generated the first time this method is called, and the salt +// is used to be able to generate distinct identifiers for the same device. If +// |salt| is NULL, a default value is used. Unless you are using this value for +// something that should be anonymous, you should probably pass NULL. +std::string GetDeviceIdentifier(const char* salt); + +} // namespace device_util +} // namespace ios + +#endif // BASE_IOS_DEVICE_UTIL_H_
diff --git a/src/base/ios/device_util.mm b/src/base/ios/device_util.mm new file mode 100644 index 0000000..b538ea8 --- /dev/null +++ b/src/base/ios/device_util.mm
@@ -0,0 +1,151 @@ +// 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/ios/device_util.h" + +#include <CommonCrypto/CommonDigest.h> +#import <UIKit/UIKit.h> + +#include <ifaddrs.h> +#include <net/if_dl.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/sysctl.h> + +#include "base/ios/ios_util.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/memory/scoped_ptr.h" +#include "base/sys_string_conversions.h" + +namespace { + +// Client ID key in the user preferences. +NSString* const kLegacyClientIdPreferenceKey = @"ChromiumClientID"; +NSString* const kClientIdPreferenceKey = @"ChromeClientID"; +// Default salt for device ids. +const char kDefaultSalt[] = "Salt"; +// Zero UUID returned on buggy iOS devices. +NSString* const kZeroUUID = @"00000000-0000-0000-0000-000000000000"; + +NSString* GenerateClientId() { + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + + // Try to migrate from legacy client id. + NSString* client_id = [defaults stringForKey:kLegacyClientIdPreferenceKey]; + + // Some iOS6 devices return a buggy identifierForVendor: + // http://openradar.appspot.com/12377282. If this is the case, revert to + // generating a new one. + if (!client_id || [client_id isEqualToString:kZeroUUID]) { + if (base::ios::IsRunningOnIOS6OrLater()) { + client_id = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; + if ([client_id isEqualToString:kZeroUUID]) + client_id = base::SysUTF8ToNSString(ios::device_util::GetRandomId()); + } else { + client_id = base::SysUTF8ToNSString(ios::device_util::GetRandomId()); + } + } + return client_id; +} + +} // namespace + +namespace ios { +namespace device_util { + +std::string GetPlatform() { + std::string platform; + size_t size = 0; + sysctlbyname("hw.machine", NULL, &size, NULL, 0); + sysctlbyname("hw.machine", WriteInto(&platform, size), &size, NULL, 0); + return platform; +} + +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; + } + return false; +} + +bool IsSingleCoreDevice() { + uint64_t cpu_number = 0; + size_t sizes = sizeof(cpu_number); + sysctlbyname("hw.physicalcpu", &cpu_number, &sizes, NULL, 0); + return cpu_number == 1; +} + +std::string GetMacAddress(const std::string& interface_name) { + std::string mac_string; + struct ifaddrs* addresses; + if (getifaddrs(&addresses) == 0) { + for (struct ifaddrs* address = addresses; address; + address = address->ifa_next) { + if ((address->ifa_addr->sa_family == AF_LINK) && + strcmp(interface_name.c_str(), address->ifa_name) == 0) { + const struct sockaddr_dl* found_address_struct = + reinterpret_cast<const struct sockaddr_dl*>(address->ifa_addr); + + // |found_address_struct->sdl_data| contains the interface name followed + // by the interface address. The address part can be accessed based on + // the length of the name, that is, |found_address_struct->sdl_nlen|. + const unsigned char* found_address = + reinterpret_cast<const unsigned char*>( + &found_address_struct->sdl_data[ + found_address_struct->sdl_nlen]); + + int found_address_length = found_address_struct->sdl_alen; + for (int i = 0; i < found_address_length; ++i) { + if (i != 0) + mac_string.push_back(':'); + base::StringAppendF(&mac_string, "%02X", found_address[i]); + } + break; + } + } + freeifaddrs(addresses); + } + return mac_string; +} + +std::string GetRandomId() { + base::mac::ScopedCFTypeRef<CFUUIDRef> + uuid_object(CFUUIDCreate(kCFAllocatorDefault)); + base::mac::ScopedCFTypeRef<CFStringRef> uuid_string( + CFUUIDCreateString(kCFAllocatorDefault, uuid_object)); + return base::SysCFStringRefToUTF8(uuid_string); +} + +std::string GetDeviceIdentifier(const char* salt) { + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + NSString* client_id = [defaults stringForKey:kClientIdPreferenceKey]; + + if (!client_id) { + client_id = GenerateClientId(); + [defaults setObject:client_id forKey:kClientIdPreferenceKey]; + [defaults synchronize]; + } + + NSData* hash_data = [[NSString stringWithFormat:@"%@%s", client_id, + salt ? salt : kDefaultSalt] dataUsingEncoding:NSUTF8StringEncoding]; + + unsigned char hash[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256([hash_data bytes], [hash_data length], hash); + CFUUIDBytes* uuid_bytes = reinterpret_cast<CFUUIDBytes*>(hash); + + base::mac::ScopedCFTypeRef<CFUUIDRef> + uuid_object(CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, *uuid_bytes)); + base::mac::ScopedCFTypeRef<CFStringRef> device_id( + CFUUIDCreateString(kCFAllocatorDefault, uuid_object)); + return base::SysCFStringRefToUTF8(device_id); +} + +} // namespace device_util +} // namespace ios
diff --git a/src/base/ios/device_util_unittest.mm b/src/base/ios/device_util_unittest.mm new file mode 100644 index 0000000..12bfe09 --- /dev/null +++ b/src/base/ios/device_util_unittest.mm
@@ -0,0 +1,105 @@ +// 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. + +#import <UIKit/UIKit.h> + +#include "base/ios/device_util.h" +#include "base/ios/ios_util.h" +#include "base/sys_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gtest_mac.h" +#include "testing/platform_test.h" + +namespace { +// The behavior of most of these utility functions depends on what they are run +// on, so there is not much to unittest them. The APIs are run to make sure they +// don't choke. Additional checks are added for particular APIs when needed. + +typedef PlatformTest DeviceUtilTest; + +void CleanNSUserDefaultsForDeviceId() { + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + [defaults removeObjectForKey:@"ChromeClientID"]; + [defaults removeObjectForKey:@"ChromiumClientID"]; + [defaults synchronize]; +} + +TEST_F(DeviceUtilTest, GetPlatform) { + GTEST_ASSERT_GT(ios::device_util::GetPlatform().length(), 0U); +} + +TEST_F(DeviceUtilTest, IsRunningOnHighRamDevice) { + ios::device_util::IsRunningOnHighRamDevice(); +} + +TEST_F(DeviceUtilTest, IsSingleCoreDevice) { + ios::device_util::IsSingleCoreDevice(); +} + +TEST_F(DeviceUtilTest, GetMacAddress) { + GTEST_ASSERT_GT(ios::device_util::GetMacAddress("en0").length(), 0U); +} + +TEST_F(DeviceUtilTest, GetRandomId) { + GTEST_ASSERT_GT(ios::device_util::GetRandomId().length(), 0U); +} + +TEST_F(DeviceUtilTest, GetDeviceIdentifier) { + CleanNSUserDefaultsForDeviceId(); + + std::string default_id = ios::device_util::GetDeviceIdentifier(NULL); + std::string other_id = ios::device_util::GetDeviceIdentifier("ForTest"); + EXPECT_NE(default_id, other_id); + + CleanNSUserDefaultsForDeviceId(); + + std::string new_default_id = ios::device_util::GetDeviceIdentifier(NULL); + if (base::ios::IsRunningOnIOS6OrLater() && + ![[[[UIDevice currentDevice] identifierForVendor] UUIDString] + isEqualToString:@"00000000-0000-0000-0000-000000000000"]) { + EXPECT_EQ(default_id, new_default_id); + } else { + EXPECT_NE(default_id, new_default_id); + } + + CleanNSUserDefaultsForDeviceId(); +} + +TEST_F(DeviceUtilTest, CheckMigration) { + CleanNSUserDefaultsForDeviceId(); + + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + [defaults setObject:@"10000000-0000-0000-0000-000000000000" + forKey:@"ChromeClientID"]; + [defaults synchronize]; + std::string expected_id = ios::device_util::GetDeviceIdentifier(NULL); + [defaults removeObjectForKey:@"ChromeClientID"]; + [defaults setObject:@"10000000-0000-0000-0000-000000000000" + forKey:@"ChromiumClientID"]; + [defaults synchronize]; + std::string new_id = ios::device_util::GetDeviceIdentifier(NULL); + EXPECT_EQ(expected_id, new_id); + + CleanNSUserDefaultsForDeviceId(); +} + +TEST_F(DeviceUtilTest, CheckMigrationFromZero) { + CleanNSUserDefaultsForDeviceId(); + + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + [defaults setObject:@"00000000-0000-0000-0000-000000000000" + forKey:@"ChromeClientID"]; + [defaults synchronize]; + std::string zero_id = ios::device_util::GetDeviceIdentifier(NULL); + [defaults removeObjectForKey:@"ChromeClientID"]; + [defaults setObject:@"00000000-0000-0000-0000-000000000000" + forKey:@"ChromiumClientID"]; + [defaults synchronize]; + std::string new_id = ios::device_util::GetDeviceIdentifier(NULL); + EXPECT_NE(zero_id, new_id); + + CleanNSUserDefaultsForDeviceId(); +} + +} // namespace
diff --git a/src/base/ios/ios_util.h b/src/base/ios/ios_util.h new file mode 100644 index 0000000..e8f9806 --- /dev/null +++ b/src/base/ios/ios_util.h
@@ -0,0 +1,26 @@ +// Copyright 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_IOS_IOS_UTIL_H_ +#define BASE_IOS_IOS_UTIL_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace ios { + +// Returns whether the operation system is iOS 5 or later. +BASE_EXPORT bool IsRunningOnIOS5OrLater(); + +// Returns whether the operation system is iOS 6 or later. +BASE_EXPORT bool IsRunningOnIOS6OrLater(); + +// Returns whether the operation system is at the given version or later. +BASE_EXPORT bool IsRunningOnOrLater(int32 major, int32 minor, int32 bug_fix); + +} // namespace ios +} // namespace base + +#endif // BASE_IOS_IOS_UTIL_H_
diff --git a/src/base/ios/ios_util.mm b/src/base/ios/ios_util.mm new file mode 100644 index 0000000..37f386d --- /dev/null +++ b/src/base/ios/ios_util.mm
@@ -0,0 +1,42 @@ +// Copyright 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/ios/ios_util.h" + +#include "base/sys_info.h" + +namespace { +// Return a 3 elements array containing the major, minor and bug fix version of +// the OS. +const int32* OSVersionAsArray() { + int32* digits = new int32[3]; + base::SysInfo::OperatingSystemVersionNumbers( + &digits[0], &digits[1], &digits[2]); + return digits; +} +} // namespace + +namespace base { +namespace ios { + +bool IsRunningOnIOS5OrLater() { + return IsRunningOnOrLater(5, 0, 0); +} + +bool IsRunningOnIOS6OrLater() { + return IsRunningOnOrLater(6, 0, 0); +} + +bool IsRunningOnOrLater(int32 major, int32 minor, int32 bug_fix) { + static const int32* current_version = OSVersionAsArray(); + int32 version[] = { major, minor, bug_fix }; + for (size_t i = 0; i < arraysize(version); i++) { + if (current_version[i] != version[i]) + return current_version[i] > version[i]; + } + return true; +} + +} // namespace ios +} // namespace base
diff --git a/src/base/ios/scoped_critical_action.h b/src/base/ios/scoped_critical_action.h new file mode 100644 index 0000000..660a83a --- /dev/null +++ b/src/base/ios/scoped_critical_action.h
@@ -0,0 +1,48 @@ +// 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_IOS_SCOPED_CRITICAL_ACTION_H_ +#define BASE_IOS_SCOPED_CRITICAL_ACTION_H_ + +#include "base/synchronization/lock.h" + +namespace base { +namespace ios { + +// This class attempts to allow the application to continue to run for a period +// of time after it transitions to the background. The construction of an +// instance of this class marks the beginning of a task that needs background +// running time when the application is moved to the background and the +// destruction marks the end of such a task. +// +// Note there is no guarantee that the task will continue to finish when the +// application is moved to the background. +// +// This class should be used at times where leaving a task unfinished might be +// detrimental to user experience. For example, it should be used to ensure that +// the application has enough time to save important data or at least attempt to +// save such data. +class ScopedCriticalAction { + public: + 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_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCriticalAction); +}; + +} // namespace ios +} // namespace base + +#endif // BASE_IOS_SCOPED_CRITICAL_ACTION_H_
diff --git a/src/base/ios/scoped_critical_action.mm b/src/base/ios/scoped_critical_action.mm new file mode 100644 index 0000000..734c0a2 --- /dev/null +++ b/src/base/ios/scoped_critical_action.mm
@@ -0,0 +1,54 @@ +// 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/ios/scoped_critical_action.h" + +#import <UIKit/UIKit.h> + +#include "base/logging.h" +#include "base/synchronization/lock.h" + +namespace base { +namespace ios { + +// 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() { + 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(); + }]; + if (background_task_id_ == UIBackgroundTaskInvalid) { + DLOG(WARNING) << + "beginBackgroundTaskWithExpirationHandler: returned an invalid ID"; + } else { + VLOG(3) << "Beginning background task with id " << background_task_id_; + } +} + +ScopedCriticalAction::~ScopedCriticalAction() { + EndBackgroundTask(); +} + +void ScopedCriticalAction::EndBackgroundTask() { + UIBackgroundTaskIdentifier task_id; + { + AutoLock lock_scope(background_task_id_lock_); + if (background_task_id_ == UIBackgroundTaskInvalid) + return; + task_id = background_task_id_; + background_task_id_ = UIBackgroundTaskInvalid; + } + + VLOG(3) << "Ending background task with id " << task_id; + [[UIApplication sharedApplication] endBackgroundTask:task_id]; +} + +} // namespace ios +} // namespace base
diff --git a/src/base/json/json_file_value_serializer.cc b/src/base/json/json_file_value_serializer.cc new file mode 100644 index 0000000..2fac451 --- /dev/null +++ b/src/base/json/json_file_value_serializer.cc
@@ -0,0 +1,96 @@ +// 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/json/json_file_value_serializer.h" + +#include "base/file_util.h" +#include "base/json/json_string_value_serializer.h" +#include "base/logging.h" + +const char* JSONFileValueSerializer::kAccessDenied = "Access denied."; +const char* JSONFileValueSerializer::kCannotReadFile = "Can't read file."; +const char* JSONFileValueSerializer::kFileLocked = "File locked."; +const char* JSONFileValueSerializer::kNoSuchFile = "File doesn't exist."; + +bool JSONFileValueSerializer::Serialize(const Value& root) { + return SerializeInternal(root, false); +} + +bool JSONFileValueSerializer::SerializeAndOmitBinaryValues(const Value& root) { + return SerializeInternal(root, true); +} + +bool JSONFileValueSerializer::SerializeInternal(const Value& root, + bool omit_binary_values) { + std::string json_string; + JSONStringValueSerializer serializer(&json_string); + serializer.set_pretty_print(true); + bool result = omit_binary_values ? + serializer.SerializeAndOmitBinaryValues(root) : + serializer.Serialize(root); + if (!result) + return false; + + int data_size = static_cast<int>(json_string.size()); + if (file_util::WriteFile(json_file_path_, + json_string.data(), + data_size) != data_size) + return false; + + return true; +} + +int JSONFileValueSerializer::ReadFileToString(std::string* json_string) { + DCHECK(json_string); + if (!file_util::ReadFileToString(json_file_path_, json_string)) { +#if defined(OS_WIN) + int error = ::GetLastError(); + if (error == ERROR_SHARING_VIOLATION || error == ERROR_LOCK_VIOLATION) { + return JSON_FILE_LOCKED; + } else if (error == ERROR_ACCESS_DENIED) { + return JSON_ACCESS_DENIED; + } +#endif + if (!file_util::PathExists(json_file_path_)) + return JSON_NO_SUCH_FILE; + else + return JSON_CANNOT_READ_FILE; + } + return JSON_NO_ERROR; +} + +const char* JSONFileValueSerializer::GetErrorMessageForCode(int error_code) { + switch (error_code) { + case JSON_NO_ERROR: + return ""; + case JSON_ACCESS_DENIED: + return kAccessDenied; + case JSON_CANNOT_READ_FILE: + return kCannotReadFile; + case JSON_FILE_LOCKED: + return kFileLocked; + case JSON_NO_SUCH_FILE: + return kNoSuchFile; + default: + NOTREACHED(); + return ""; + } +} + +Value* JSONFileValueSerializer::Deserialize(int* error_code, + std::string* error_str) { + std::string json_string; + int error = ReadFileToString(&json_string); + if (error != JSON_NO_ERROR) { + if (error_code) + *error_code = error; + if (error_str) + *error_str = GetErrorMessageForCode(error); + return NULL; + } + + JSONStringValueSerializer serializer(json_string); + serializer.set_allow_trailing_comma(allow_trailing_comma_); + return serializer.Deserialize(error_code, error_str); +}
diff --git a/src/base/json/json_file_value_serializer.h b/src/base/json/json_file_value_serializer.h new file mode 100644 index 0000000..633db5d --- /dev/null +++ b/src/base/json/json_file_value_serializer.h
@@ -0,0 +1,88 @@ +// 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_JSON_JSON_FILE_VALUE_SERIALIZER_H_ +#define BASE_JSON_JSON_FILE_VALUE_SERIALIZER_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/values.h" + +class BASE_EXPORT JSONFileValueSerializer : public base::ValueSerializer { + public: + // json_file_patch is the path of a file that will be source of the + // deserialization or the destination of the serialization. + // When deserializing, the file should exist, but when serializing, the + // serializer will attempt to create the file at the specified location. + explicit JSONFileValueSerializer(const FilePath& json_file_path) + : json_file_path_(json_file_path), + allow_trailing_comma_(false) {} + + virtual ~JSONFileValueSerializer() {} + + // DO NOT USE except in unit tests to verify the file was written properly. + // We should never serialize directly to a file since this will block the + // thread. Instead, serialize to a string and write to the file you want on + // the file thread. + // + // Attempt to serialize the data structure represented by Value into + // JSON. If the return value is true, the result will have been written + // into the file whose name was passed into the constructor. + virtual bool Serialize(const Value& root) OVERRIDE; + + // Equivalent to Serialize(root) except binary values are omitted from the + // output. + bool SerializeAndOmitBinaryValues(const Value& root); + + // Attempt to deserialize the data structure encoded in the file 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). + // 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. + virtual Value* Deserialize(int* error_code, + std::string* error_message) OVERRIDE; + + // This enum is designed to safely overlap with JSONReader::JsonParseError. + enum JsonFileError { + JSON_NO_ERROR = 0, + JSON_ACCESS_DENIED = 1000, + JSON_CANNOT_READ_FILE, + JSON_FILE_LOCKED, + JSON_NO_SUCH_FILE + }; + + // File-specific error messages that can be returned. + static const char* kAccessDenied; + static const char* kCannotReadFile; + static const char* kFileLocked; + static const char* kNoSuchFile; + + // Convert an error code into an error message. |error_code| is assumed to + // be a JsonFileError. + static const char* GetErrorMessageForCode(int error_code); + + void set_allow_trailing_comma(bool new_value) { + allow_trailing_comma_ = new_value; + } + + private: + bool SerializeInternal(const Value& root, bool omit_binary_values); + + FilePath json_file_path_; + bool allow_trailing_comma_; + + // A wrapper for file_util::ReadFileToString which returns a non-zero + // JsonFileError if there were file errors. + int ReadFileToString(std::string* json_string); + + DISALLOW_IMPLICIT_CONSTRUCTORS(JSONFileValueSerializer); +}; + +#endif // BASE_JSON_JSON_FILE_VALUE_SERIALIZER_H_ +
diff --git a/src/base/json/json_parser.cc b/src/base/json/json_parser.cc new file mode 100644 index 0000000..4c03a98 --- /dev/null +++ b/src/base/json/json_parser.cc
@@ -0,0 +1,962 @@ +// 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/json/json_parser.h" + +#include "base/float_util.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_number_conversions.h" +#include "base/string_piece.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/third_party/icu/icu_utf.h" +#include "base/utf_string_conversion_utils.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" + +namespace base { +namespace internal { + +namespace { + +const int kStackMaxDepth = 100; + +const int32 kExtendedASCIIStart = 0x80; + +// This and the class below are used to own the JSON input string for when +// string tokens are stored as StringPiece instead of std::string. This +// optimization avoids about 2/3rds of string memory copies. The constructor +// takes ownership of the input string. The real root value is Swap()ed into +// the new instance. +class DictionaryHiddenRootValue : public base::DictionaryValue { + public: + DictionaryHiddenRootValue(std::string* json, Value* root) : json_(json) { + DCHECK(root->IsType(Value::TYPE_DICTIONARY)); + DictionaryValue::Swap(static_cast<DictionaryValue*>(root)); + } + + virtual void Swap(DictionaryValue* other) OVERRIDE { + DVLOG(1) << "Swap()ing a DictionaryValue inefficiently."; + + // First deep copy to convert JSONStringValue to std::string and swap that + // copy with |other|, which contains the new contents of |this|. + scoped_ptr<base::DictionaryValue> copy(DeepCopy()); + copy->Swap(other); + + // Then erase the contents of the current dictionary and swap in the + // new contents, originally from |other|. + Clear(); + json_.reset(); + DictionaryValue::Swap(copy.get()); + } + + // Not overriding DictionaryValue::Remove because it just calls through to + // the method below. + + virtual bool RemoveWithoutPathExpansion(const std::string& key, + Value** out) OVERRIDE { + // If the caller won't take ownership of the removed value, just call up. + if (!out) + return DictionaryValue::RemoveWithoutPathExpansion(key, out); + + DVLOG(1) << "Remove()ing from a DictionaryValue inefficiently."; + + // Otherwise, remove the value while its still "owned" by this and copy it + // to convert any JSONStringValues to std::string. + Value* out_owned = NULL; + if (!DictionaryValue::RemoveWithoutPathExpansion(key, &out_owned)) + return false; + + *out = out_owned->DeepCopy(); + delete out_owned; + + return true; + } + + private: + scoped_ptr<std::string> json_; + + DISALLOW_COPY_AND_ASSIGN(DictionaryHiddenRootValue); +}; + +class ListHiddenRootValue : public base::ListValue { + public: + ListHiddenRootValue(std::string* json, Value* root) : json_(json) { + DCHECK(root->IsType(Value::TYPE_LIST)); + ListValue::Swap(static_cast<ListValue*>(root)); + } + + virtual void Swap(ListValue* other) OVERRIDE { + DVLOG(1) << "Swap()ing a ListValue inefficiently."; + + // First deep copy to convert JSONStringValue to std::string and swap that + // copy with |other|, which contains the new contents of |this|. + scoped_ptr<base::ListValue> copy(DeepCopy()); + copy->Swap(other); + + // Then erase the contents of the current list and swap in the new contents, + // originally from |other|. + Clear(); + json_.reset(); + ListValue::Swap(copy.get()); + } + + virtual bool Remove(size_t index, Value** out) OVERRIDE { + // If the caller won't take ownership of the removed value, just call up. + if (!out) + return ListValue::Remove(index, out); + + DVLOG(1) << "Remove()ing from a ListValue inefficiently."; + + // Otherwise, remove the value while its still "owned" by this and copy it + // to convert any JSONStringValues to std::string. + Value* out_owned = NULL; + if (!ListValue::Remove(index, &out_owned)) + return false; + + *out = out_owned->DeepCopy(); + delete out_owned; + + return true; + } + + private: + scoped_ptr<std::string> json_; + + DISALLOW_COPY_AND_ASSIGN(ListHiddenRootValue); +}; + +// A variant on StringValue that uses StringPiece instead of copying the string +// into the Value. This can only be stored in a child of hidden root (above), +// otherwise the referenced string will not be guaranteed to outlive it. +class JSONStringValue : public base::Value { + public: + explicit JSONStringValue(const base::StringPiece& piece) + : Value(TYPE_STRING), + string_piece_(piece) { + } + + // Overridden from base::Value: + virtual bool GetAsString(std::string* out_value) const OVERRIDE { + string_piece_.CopyToString(out_value); + return true; + } + virtual bool GetAsString(string16* out_value) const OVERRIDE { + *out_value = UTF8ToUTF16(string_piece_); + return true; + } + virtual Value* DeepCopy() const OVERRIDE { + return new StringValue(string_piece_.as_string()); + } + virtual bool Equals(const Value* other) const OVERRIDE { + std::string other_string; + return other->IsType(TYPE_STRING) && other->GetAsString(&other_string) && + StringPiece(other_string) == string_piece_; + } + + private: + // The location in the original input stream. + base::StringPiece string_piece_; + + DISALLOW_COPY_AND_ASSIGN(JSONStringValue); +}; + +// Simple class that checks for maximum recursion/"stack overflow." +class StackMarker { + public: + explicit StackMarker(int* depth) : depth_(depth) { + ++(*depth_); + DCHECK_LE(*depth_, kStackMaxDepth); + } + ~StackMarker() { + --(*depth_); + } + + bool IsTooDeep() const { + return *depth_ >= kStackMaxDepth; + } + + private: + int* const depth_; + + DISALLOW_COPY_AND_ASSIGN(StackMarker); +}; + +} // namespace + +JSONParser::JSONParser(int options) + : options_(options), + start_pos_(NULL), + pos_(NULL), + end_pos_(NULL), + index_(0), + stack_depth_(0), + line_number_(0), + index_last_line_(0), + error_code_(JSONReader::JSON_NO_ERROR), + error_line_(0), + error_column_(0) { +} + +JSONParser::~JSONParser() { +} + +Value* JSONParser::Parse(const StringPiece& input) { + scoped_ptr<std::string> input_copy; + // If the children of a JSON root can be detached, then hidden roots cannot + // be used, so do not bother copying the input because StringPiece will not + // be used anywhere. + if (!(options_ & JSON_DETACHABLE_CHILDREN)) { + input_copy.reset(new std::string(input.as_string())); + start_pos_ = input_copy->data(); + } else { + start_pos_ = input.data(); + } + pos_ = start_pos_; + end_pos_ = start_pos_ + input.length(); + index_ = 0; + line_number_ = 1; + index_last_line_ = 0; + + error_code_ = JSONReader::JSON_NO_ERROR; + error_line_ = 0; + error_column_ = 0; + + // When the input JSON string starts with a UTF-8 Byte-Order-Mark + // <0xEF 0xBB 0xBF>, advance the start position to avoid the + // ParseNextToken function mis-treating a Unicode BOM as an invalid + // character and returning NULL. + if (CanConsume(3) && static_cast<uint8>(*pos_) == 0xEF && + static_cast<uint8>(*(pos_ + 1)) == 0xBB && + static_cast<uint8>(*(pos_ + 2)) == 0xBF) { + NextNChars(3); + } + + // Parse the first and any nested tokens. + scoped_ptr<Value> root(ParseNextToken()); + if (!root.get()) + return NULL; + + // Make sure the input stream is at an end. + if (GetNextToken() != T_END_OF_INPUT) { + if (!CanConsume(1) || (NextChar() && GetNextToken() != T_END_OF_INPUT)) { + ReportError(JSONReader::JSON_UNEXPECTED_DATA_AFTER_ROOT, 1); + return NULL; + } + } + + // Dictionaries and lists can contain JSONStringValues, so wrap them in a + // hidden root. + if (!(options_ & JSON_DETACHABLE_CHILDREN)) { + if (root->IsType(Value::TYPE_DICTIONARY)) { + return new DictionaryHiddenRootValue(input_copy.release(), root.get()); + } else if (root->IsType(Value::TYPE_LIST)) { + return new ListHiddenRootValue(input_copy.release(), root.get()); + } else if (root->IsType(Value::TYPE_STRING)) { + // A string type could be a JSONStringValue, but because there's no + // corresponding HiddenRootValue, the memory will be lost. Deep copy to + // preserve it. + return root->DeepCopy(); + } + } + + // All other values can be returned directly. + return root.release(); +} + +JSONReader::JsonParseError JSONParser::error_code() const { + return error_code_; +} + +std::string JSONParser::GetErrorMessage() const { + return FormatErrorMessage(error_line_, error_column_, + JSONReader::ErrorCodeToString(error_code_)); +} + +// StringBuilder /////////////////////////////////////////////////////////////// + +JSONParser::StringBuilder::StringBuilder() + : pos_(NULL), + length_(0), + string_(NULL) { +} + +JSONParser::StringBuilder::StringBuilder(const char* pos) + : pos_(pos), + length_(0), + string_(NULL) { +} + +void JSONParser::StringBuilder::Swap(StringBuilder* other) { + std::swap(other->string_, string_); + std::swap(other->pos_, pos_); + std::swap(other->length_, length_); +} + +JSONParser::StringBuilder::~StringBuilder() { + delete string_; +} + +void JSONParser::StringBuilder::Append(const char& c) { + DCHECK_GE(c, 0); + DCHECK_LT(c, 128); + + if (string_) + string_->push_back(c); + else + ++length_; +} + +void JSONParser::StringBuilder::AppendString(const std::string& str) { + DCHECK(string_); + string_->append(str); +} + +void JSONParser::StringBuilder::Convert() { + if (string_) + return; + string_ = new std::string(pos_, length_); +} + +bool JSONParser::StringBuilder::CanBeStringPiece() const { + return !string_; +} + +StringPiece JSONParser::StringBuilder::AsStringPiece() { + if (string_) + return StringPiece(); + return StringPiece(pos_, length_); +} + +const std::string& JSONParser::StringBuilder::AsString() { + if (!string_) + Convert(); + return *string_; +} + +// JSONParser private ////////////////////////////////////////////////////////// + +inline bool JSONParser::CanConsume(int length) { + return pos_ + length <= end_pos_; +} + +const char* JSONParser::NextChar() { + DCHECK(CanConsume(1)); + ++index_; + ++pos_; + return pos_; +} + +void JSONParser::NextNChars(int n) { + DCHECK(CanConsume(n)); + index_ += n; + pos_ += n; +} + +JSONParser::Token JSONParser::GetNextToken() { + EatWhitespaceAndComments(); + if (!CanConsume(1)) + return T_END_OF_INPUT; + + switch (*pos_) { + case '{': + return T_OBJECT_BEGIN; + case '}': + return T_OBJECT_END; + case '[': + return T_ARRAY_BEGIN; + case ']': + return T_ARRAY_END; + case '"': + return T_STRING; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return T_NUMBER; + case 't': + return T_BOOL_TRUE; + case 'f': + return T_BOOL_FALSE; + case 'n': + return T_NULL; + case ',': + return T_LIST_SEPARATOR; + case ':': + return T_OBJECT_PAIR_SEPARATOR; + default: + return T_INVALID_TOKEN; + } +} + +void JSONParser::EatWhitespaceAndComments() { + while (pos_ < end_pos_) { + switch (*pos_) { + case '\r': + case '\n': + index_last_line_ = index_; + ++line_number_; + // Fall through. + case ' ': + case '\t': + NextChar(); + break; + case '/': + if (!EatComment()) + return; + break; + default: + return; + } + } +} + +bool JSONParser::EatComment() { + if (*pos_ != '/' || !CanConsume(1)) + return false; + + char next_char = *NextChar(); + if (next_char == '/') { + // Single line comment, read to newline. + while (CanConsume(1)) { + char next_char = *NextChar(); + if (next_char == '\n' || next_char == '\r') + return true; + } + } else if (next_char == '*') { + // Block comment, read until end marker. + while (CanConsume(2)) { + if (*NextChar() == '*' && *NextChar() == '/') { + // EatWhitespaceAndComments will inspect pos_, which will still be on + // the last / of the comment, so advance once more (which may also be + // end of input). + NextChar(); + return true; + } + } + + // If the comment is unterminated, GetNextToken will report T_END_OF_INPUT. + } + + return false; +} + +Value* JSONParser::ParseNextToken() { + return ParseToken(GetNextToken()); +} + +Value* JSONParser::ParseToken(Token token) { + switch (token) { + case T_OBJECT_BEGIN: + return ConsumeDictionary(); + case T_ARRAY_BEGIN: + return ConsumeList(); + case T_STRING: + return ConsumeString(); + case T_NUMBER: + return ConsumeNumber(); + case T_BOOL_TRUE: + case T_BOOL_FALSE: + case T_NULL: + return ConsumeLiteral(); + default: + ReportError(JSONReader::JSON_UNEXPECTED_TOKEN, 1); + return NULL; + } +} + +Value* JSONParser::ConsumeDictionary() { + if (*pos_ != '{') { + ReportError(JSONReader::JSON_UNEXPECTED_TOKEN, 1); + return NULL; + } + + StackMarker depth_check(&stack_depth_); + if (depth_check.IsTooDeep()) { + ReportError(JSONReader::JSON_TOO_MUCH_NESTING, 1); + return NULL; + } + + scoped_ptr<DictionaryValue> dict(new DictionaryValue); + + NextChar(); + Token token = GetNextToken(); + while (token != T_OBJECT_END) { + if (token != T_STRING) { + ReportError(JSONReader::JSON_UNQUOTED_DICTIONARY_KEY, 1); + return NULL; + } + + // First consume the key. + StringBuilder key; + if (!ConsumeStringRaw(&key)) { + return NULL; + } + + // Read the separator. + NextChar(); + token = GetNextToken(); + if (token != T_OBJECT_PAIR_SEPARATOR) { + ReportError(JSONReader::JSON_SYNTAX_ERROR, 1); + return NULL; + } + + // The next token is the value. Ownership transfers to |dict|. + NextChar(); + Value* value = ParseNextToken(); + if (!value) { + // ReportError from deeper level. + return NULL; + } + + dict->SetWithoutPathExpansion(key.AsString(), value); + + NextChar(); + token = GetNextToken(); + if (token == T_LIST_SEPARATOR) { + NextChar(); + token = GetNextToken(); + if (token == T_OBJECT_END && !(options_ & JSON_ALLOW_TRAILING_COMMAS)) { + ReportError(JSONReader::JSON_TRAILING_COMMA, 1); + return NULL; + } + } else if (token != T_OBJECT_END) { + ReportError(JSONReader::JSON_SYNTAX_ERROR, 0); + return NULL; + } + } + + return dict.release(); +} + +Value* JSONParser::ConsumeList() { + if (*pos_ != '[') { + ReportError(JSONReader::JSON_UNEXPECTED_TOKEN, 1); + return NULL; + } + + StackMarker depth_check(&stack_depth_); + if (depth_check.IsTooDeep()) { + ReportError(JSONReader::JSON_TOO_MUCH_NESTING, 1); + return NULL; + } + + scoped_ptr<ListValue> list(new ListValue); + + NextChar(); + Token token = GetNextToken(); + while (token != T_ARRAY_END) { + Value* item = ParseToken(token); + if (!item) { + // ReportError from deeper level. + return NULL; + } + + list->Append(item); + + NextChar(); + token = GetNextToken(); + if (token == T_LIST_SEPARATOR) { + NextChar(); + token = GetNextToken(); + if (token == T_ARRAY_END && !(options_ & JSON_ALLOW_TRAILING_COMMAS)) { + ReportError(JSONReader::JSON_TRAILING_COMMA, 1); + return NULL; + } + } else if (token != T_ARRAY_END) { + ReportError(JSONReader::JSON_SYNTAX_ERROR, 1); + return NULL; + } + } + + return list.release(); +} + +Value* JSONParser::ConsumeString() { + StringBuilder string; + if (!ConsumeStringRaw(&string)) + return NULL; + + // Create the Value representation, using a hidden root, if configured + // to do so, and if the string can be represented by StringPiece. + if (string.CanBeStringPiece() && !(options_ & JSON_DETACHABLE_CHILDREN)) { + return new JSONStringValue(string.AsStringPiece()); + } else { + if (string.CanBeStringPiece()) + string.Convert(); + return new StringValue(string.AsString()); + } +} + +bool JSONParser::ConsumeStringRaw(StringBuilder* out) { + if (*pos_ != '"') { + ReportError(JSONReader::JSON_UNEXPECTED_TOKEN, 1); + return false; + } + + // StringBuilder will internally build a StringPiece unless a UTF-16 + // conversion occurs, at which point it will perform a copy into a + // std::string. + StringBuilder string(NextChar()); + + int length = end_pos_ - start_pos_; + int32 next_char = 0; + + while (CanConsume(1)) { + pos_ = start_pos_ + index_; // CBU8_NEXT is postcrement. + CBU8_NEXT(start_pos_, index_, length, next_char); + if (next_char < 0 || !IsValidCharacter(next_char)) { + ReportError(JSONReader::JSON_UNSUPPORTED_ENCODING, 1); + return false; + } + + // If this character is an escape sequence... + if (next_char == '\\') { + // The input string will be adjusted (either by combining the two + // characters of an encoded escape sequence, or with a UTF conversion), + // so using StringPiece isn't possible -- force a conversion. + string.Convert(); + + if (!CanConsume(1)) { + ReportError(JSONReader::JSON_INVALID_ESCAPE, 0); + return false; + } + + switch (*NextChar()) { + // Allowed esape sequences: + case 'x': { // UTF-8 sequence. + // UTF-8 \x escape sequences are not allowed in the spec, but they + // are supported here for backwards-compatiblity with the old parser. + if (!CanConsume(2)) { + ReportError(JSONReader::JSON_INVALID_ESCAPE, 1); + return false; + } + + int hex_digit = 0; + if (!HexStringToInt(StringPiece(NextChar(), 2), &hex_digit)) { + ReportError(JSONReader::JSON_INVALID_ESCAPE, -1); + return false; + } + NextChar(); + + if (hex_digit < kExtendedASCIIStart) + string.Append(hex_digit); + else + DecodeUTF8(hex_digit, &string); + break; + } + case 'u': { // UTF-16 sequence. + // UTF units are of the form \uXXXX. + if (!CanConsume(5)) { // 5 being 'u' and four HEX digits. + ReportError(JSONReader::JSON_INVALID_ESCAPE, 0); + return false; + } + + // Skip the 'u'. + NextChar(); + + std::string utf8_units; + if (!DecodeUTF16(&utf8_units)) { + ReportError(JSONReader::JSON_INVALID_ESCAPE, -1); + return false; + } + + string.AppendString(utf8_units); + break; + } + case '"': + string.Append('"'); + break; + case '\\': + string.Append('\\'); + break; + case '/': + string.Append('/'); + break; + case 'b': + string.Append('\b'); + break; + case 'f': + string.Append('\f'); + break; + case 'n': + string.Append('\n'); + break; + case 'r': + string.Append('\r'); + break; + case 't': + string.Append('\t'); + break; + case 'v': // Not listed as valid escape sequence in the RFC. + string.Append('\v'); + break; + // All other escape squences are illegal. + default: + ReportError(JSONReader::JSON_INVALID_ESCAPE, 0); + return false; + } + } else if (next_char == '"') { + --index_; // Rewind by one because of CBU8_NEXT. + out->Swap(&string); + return true; + } else { + if (next_char < kExtendedASCIIStart) + string.Append(next_char); + else + DecodeUTF8(next_char, &string); + } + } + + ReportError(JSONReader::JSON_SYNTAX_ERROR, 0); + return false; +} + +// Entry is at the first X in \uXXXX. +bool JSONParser::DecodeUTF16(std::string* dest_string) { + if (!CanConsume(4)) + return false; + + // This is a 32-bit field because the shift operations in the + // conversion process below cause MSVC to error about "data loss." + // This only stores UTF-16 code units, though. + // Consume the UTF-16 code unit, which may be a high surrogate. + int code_unit16_high = 0; + if (!HexStringToInt(StringPiece(pos_, 4), &code_unit16_high)) + return false; + + // Only add 3, not 4, because at the end of this iteration, the parser has + // finished working with the last digit of the UTF sequence, meaning that + // the next iteration will advance to the next byte. + NextNChars(3); + + // Used to convert the UTF-16 code units to a code point and then to a UTF-8 + // code unit sequence. + char code_unit8[8] = { 0 }; + size_t offset = 0; + + // If this is a high surrogate, consume the next code unit to get the + // low surrogate. + if (CBU16_IS_SURROGATE(code_unit16_high)) { + // Make sure this is the high surrogate. If not, it's an encoding + // error. + if (!CBU16_IS_SURROGATE_LEAD(code_unit16_high)) + return false; + + // Make sure that the token has more characters to consume the + // lower surrogate. + if (!CanConsume(6)) // 6 being '\' 'u' and four HEX digits. + return false; + if (*NextChar() != '\\' || *NextChar() != 'u') + return false; + + NextChar(); // Read past 'u'. + int code_unit16_low = 0; + if (!HexStringToInt(StringPiece(pos_, 4), &code_unit16_low)) + return false; + + NextNChars(3); + + if (!CBU16_IS_TRAIL(code_unit16_low)) { + return false; + } + + uint32 code_point = CBU16_GET_SUPPLEMENTARY(code_unit16_high, + code_unit16_low); + offset = 0; + CBU8_APPEND_UNSAFE(code_unit8, offset, code_point); + } else { + // Not a surrogate. + DCHECK(CBU16_IS_SINGLE(code_unit16_high)); + CBU8_APPEND_UNSAFE(code_unit8, offset, code_unit16_high); + } + + dest_string->append(code_unit8); + return true; +} + +void JSONParser::DecodeUTF8(const int32& point, StringBuilder* dest) { + // Anything outside of the basic ASCII plane will need to be decoded from + // int32 to a multi-byte sequence. + if (point < kExtendedASCIIStart) { + dest->Append(point); + } else { + char utf8_units[4] = { 0 }; + int offset = 0; + CBU8_APPEND_UNSAFE(utf8_units, offset, point); + dest->Convert(); + // CBU8_APPEND_UNSAFE can overwrite up to 4 bytes, so utf8_units may not be + // zero terminated at this point. |offset| contains the correct length. + dest->AppendString(std::string(utf8_units, offset)); + } +} + +Value* JSONParser::ConsumeNumber() { + const char* num_start = pos_; + const int start_index = index_; + int end_index = start_index; + + if (*pos_ == '-') + NextChar(); + + if (!ReadInt(false)) { + ReportError(JSONReader::JSON_SYNTAX_ERROR, 1); + return NULL; + } + end_index = index_; + + // The optional fraction part. + if (*pos_ == '.') { + if (!CanConsume(1)) { + ReportError(JSONReader::JSON_SYNTAX_ERROR, 1); + return NULL; + } + NextChar(); + if (!ReadInt(true)) { + ReportError(JSONReader::JSON_SYNTAX_ERROR, 1); + return NULL; + } + end_index = index_; + } + + // Optional exponent part. + if (*pos_ == 'e' || *pos_ == 'E') { + NextChar(); + if (*pos_ == '-' || *pos_ == '+') + NextChar(); + if (!ReadInt(true)) { + ReportError(JSONReader::JSON_SYNTAX_ERROR, 1); + return NULL; + } + end_index = index_; + } + + // ReadInt is greedy because numbers have no easily detectable sentinel, + // so save off where the parser should be on exit (see Consume invariant at + // the top of the header), then make sure the next token is one which is + // valid. + const char* exit_pos = pos_ - 1; + int exit_index = index_ - 1; + + switch (GetNextToken()) { + case T_OBJECT_END: + case T_ARRAY_END: + case T_LIST_SEPARATOR: + case T_END_OF_INPUT: + break; + default: + ReportError(JSONReader::JSON_SYNTAX_ERROR, 1); + return NULL; + } + + pos_ = exit_pos; + index_ = exit_index; + + StringPiece num_string(num_start, end_index - start_index); + + int num_int; + if (StringToInt(num_string, &num_int)) + return new FundamentalValue(num_int); + + double num_double; + if (base::StringToDouble(num_string.as_string(), &num_double) && + IsFinite(num_double)) { + return new FundamentalValue(num_double); + } + + return NULL; +} + +bool JSONParser::ReadInt(bool allow_leading_zeros) { + char first = *pos_; + int len = 0; + + char c = first; + while (CanConsume(1) && IsAsciiDigit(c)) { + c = *NextChar(); + ++len; + } + + if (len == 0) + return false; + + if (!allow_leading_zeros && len > 1 && first == '0') + return false; + + return true; +} + +Value* JSONParser::ConsumeLiteral() { + switch (*pos_) { + case 't': { + const char* kTrueLiteral = "true"; + const int kTrueLen = static_cast<int>(strlen(kTrueLiteral)); + if (!CanConsume(kTrueLen - 1) || + !StringsAreEqual(pos_, kTrueLiteral, kTrueLen)) { + ReportError(JSONReader::JSON_SYNTAX_ERROR, 1); + return NULL; + } + NextNChars(kTrueLen - 1); + return new FundamentalValue(true); + } + case 'f': { + const char* kFalseLiteral = "false"; + const int kFalseLen = static_cast<int>(strlen(kFalseLiteral)); + if (!CanConsume(kFalseLen - 1) || + !StringsAreEqual(pos_, kFalseLiteral, kFalseLen)) { + ReportError(JSONReader::JSON_SYNTAX_ERROR, 1); + return NULL; + } + NextNChars(kFalseLen - 1); + return new FundamentalValue(false); + } + case 'n': { + const char* kNullLiteral = "null"; + const int kNullLen = static_cast<int>(strlen(kNullLiteral)); + if (!CanConsume(kNullLen - 1) || + !StringsAreEqual(pos_, kNullLiteral, kNullLen)) { + ReportError(JSONReader::JSON_SYNTAX_ERROR, 1); + return NULL; + } + NextNChars(kNullLen - 1); + return Value::CreateNullValue(); + } + default: + ReportError(JSONReader::JSON_UNEXPECTED_TOKEN, 1); + return NULL; + } +} + +// static +bool JSONParser::StringsAreEqual(const char* one, const char* two, size_t len) { + return strncmp(one, two, len) == 0; +} + +void JSONParser::ReportError(JSONReader::JsonParseError code, + int column_adjust) { + error_code_ = code; + error_line_ = line_number_; + error_column_ = index_ - index_last_line_ + column_adjust; +} + +// static +std::string JSONParser::FormatErrorMessage(int line, int column, + const std::string& description) { + if (line || column) { + return StringPrintf("Line: %i, column: %i, %s", + line, column, description.c_str()); + } + return description; +} + +} // namespace internal +} // namespace base
diff --git a/src/base/json/json_parser.h b/src/base/json/json_parser.h new file mode 100644 index 0000000..020ac25 --- /dev/null +++ b/src/base/json/json_parser.h
@@ -0,0 +1,271 @@ +// 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_JSON_JSON_PARSER_H_ +#define BASE_JSON_JSON_PARSER_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/json/json_reader.h" +#include "base/string_piece.h" + +#if !defined(OS_CHROMEOS) +#include "base/gtest_prod_util.h" +#endif + +namespace base { +class Value; +} + +#if defined(OS_CHROMEOS) +// Chromium and Chromium OS check out gtest to different places, so this is +// unable to compile on both if gtest_prod.h is included here. Instead, include +// its only contents -- this will need to be updated if the macro ever changes. +#define FRIEND_TEST(test_case_name, test_name)\ +friend class test_case_name##_##test_name##_Test + +#define FRIEND_TEST_ALL_PREFIXES(test_case_name, test_name) \ + FRIEND_TEST(test_case_name, test_name); \ + FRIEND_TEST(test_case_name, DISABLED_##test_name); \ + FRIEND_TEST(test_case_name, FLAKY_##test_name) +#endif // OS_CHROMEOS + +namespace base { +namespace internal { + +class JSONParserTest; + +// The implementation behind the JSONReader interface. This class is not meant +// to be used directly; it encapsulates logic that need not be exposed publicly. +// +// This parser guarantees O(n) time through the input string. It also optimizes +// base::StringValue by using StringPiece where possible when returning Value +// objects by using "hidden roots," discussed in the implementation. +// +// Iteration happens on the byte level, with the functions CanConsume and +// NextChar. The conversion from byte to JSON token happens without advancing +// the parser in GetNextToken/ParseToken, that is tokenization operates on +// the current parser position without advancing. +// +// Built on top of these are a family of Consume functions that iterate +// internally. Invariant: on entry of a Consume function, the parser is wound +// to the first byte of a valid JSON token. On exit, it is on the last byte +// of a token, such that the next iteration of the parser will be at the byte +// immediately following the token, which would likely be the first byte of the +// next token. +class BASE_EXPORT_PRIVATE JSONParser { + public: + explicit JSONParser(int options); + ~JSONParser(); + + // Parses the input string according to the set options and returns the + // result as a Value owned by the caller. + Value* Parse(const StringPiece& input); + + // Returns the error code. + JSONReader::JsonParseError error_code() const; + + // Returns the human-friendly error message. + std::string GetErrorMessage() const; + + private: + enum Token { + T_OBJECT_BEGIN, // { + T_OBJECT_END, // } + T_ARRAY_BEGIN, // [ + T_ARRAY_END, // ] + T_STRING, + T_NUMBER, + T_BOOL_TRUE, // true + T_BOOL_FALSE, // false + T_NULL, // null + T_LIST_SEPARATOR, // , + T_OBJECT_PAIR_SEPARATOR, // : + T_END_OF_INPUT, + T_INVALID_TOKEN, + }; + + // A helper class used for parsing strings. One optimization performed is to + // create base::Value with a StringPiece to avoid unnecessary std::string + // copies. This is not possible if the input string needs to be decoded from + // UTF-16 to UTF-8, or if an escape sequence causes characters to be skipped. + // This class centralizes that logic. + class StringBuilder { + public: + // Empty constructor. Used for creating a builder with which to Swap(). + StringBuilder(); + + // |pos| is the beginning of an input string, excluding the |"|. + explicit StringBuilder(const char* pos); + + ~StringBuilder(); + + // Swaps the contents of |other| with this. + void Swap(StringBuilder* other); + + // Either increases the |length_| of the string or copies the character if + // the StringBuilder has been converted. |c| must be in the basic ASCII + // plane; all other characters need to be in UTF-8 units, appended with + // AppendString below. + void Append(const char& c); + + // Appends a string to the std::string. Must be Convert()ed to use. + void AppendString(const std::string& str); + + // Converts the builder from its default StringPiece to a full std::string, + // performing a copy. Once a builder is converted, it cannot be made a + // StringPiece again. + void Convert(); + + // Returns whether the builder can be converted to a StringPiece. + bool CanBeStringPiece() const; + + // Returns the StringPiece representation. Returns an empty piece if it + // cannot be converted. + StringPiece AsStringPiece(); + + // Returns the builder as a std::string. + const std::string& AsString(); + + private: + // The beginning of the input string. + const char* pos_; + + // Number of bytes in |pos_| that make up the string being built. + size_t length_; + + // The copied string representation. NULL until Convert() is called. + // Strong. scoped_ptr<T> has too much of an overhead here. + std::string* string_; + }; + + // Quick check that the stream has capacity to consume |length| more bytes. + bool CanConsume(int length); + + // The basic way to consume a single character in the stream. Consumes one + // byte of the input stream and returns a pointer to the rest of it. + const char* NextChar(); + + // Performs the equivalent of NextChar N times. + void NextNChars(int n); + + // Skips over whitespace and comments to find the next token in the stream. + // This does not advance the parser for non-whitespace or comment chars. + Token GetNextToken(); + + // Consumes whitespace characters and comments until the next non-that is + // encountered. + void EatWhitespaceAndComments(); + // Helper function that consumes a comment, assuming that the parser is + // currently wound to a '/'. + bool EatComment(); + + // Calls GetNextToken() and then ParseToken(). Caller owns the result. + Value* ParseNextToken(); + + // Takes a token that represents the start of a Value ("a structural token" + // in RFC terms) and consumes it, returning the result as an object the + // caller owns. + Value* ParseToken(Token token); + + // Assuming that the parser is currently wound to '{', this parses a JSON + // object into a DictionaryValue. + Value* ConsumeDictionary(); + + // Assuming that the parser is wound to '[', this parses a JSON list into a + // ListValue. + Value* ConsumeList(); + + // Calls through ConsumeStringRaw and wraps it in a value. + Value* ConsumeString(); + + // Assuming that the parser is wound to a double quote, this parses a string, + // decoding any escape sequences and converts UTF-16 to UTF-8. Returns true on + // success and Swap()s the result into |out|. Returns false on failure with + // error information set. + bool ConsumeStringRaw(StringBuilder* out); + // Helper function for ConsumeStringRaw() that consumes the next four or 10 + // bytes (parser is wound to the first character of a HEX sequence, with the + // potential for consuming another \uXXXX for a surrogate). Returns true on + // success and places the UTF8 code units in |dest_string|, and false on + // failure. + bool DecodeUTF16(std::string* dest_string); + // Helper function for ConsumeStringRaw() that takes a single code point, + // decodes it into UTF-8 units, and appends it to the given builder. The + // point must be valid. + void DecodeUTF8(const int32& point, StringBuilder* dest); + + // Assuming that the parser is wound to the start of a valid JSON number, + // this parses and converts it to either an int or double value. + Value* ConsumeNumber(); + // Helper that reads characters that are ints. Returns true if a number was + // read and false on error. + bool ReadInt(bool allow_leading_zeros); + + // Consumes the literal values of |true|, |false|, and |null|, assuming the + // parser is wound to the first character of any of those. + Value* ConsumeLiteral(); + + // Compares two string buffers of a given length. + static bool StringsAreEqual(const char* left, const char* right, size_t len); + + // Sets the error information to |code| at the current column, based on + // |index_| and |index_last_line_|, with an optional positive/negative + // adjustment by |column_adjust|. + void ReportError(JSONReader::JsonParseError code, int column_adjust); + + // Given the line and column number of an error, formats one of the error + // message contants from json_reader.h for human display. + static std::string FormatErrorMessage(int line, int column, + const std::string& description); + + // base::JSONParserOptions that control parsing. + int options_; + + // Pointer to the start of the input data. + const char* start_pos_; + + // Pointer to the current position in the input data. Equivalent to + // |start_pos_ + index_|. + const char* pos_; + + // Pointer to the last character of the input data. + const char* end_pos_; + + // The index in the input stream to which the parser is wound. + int index_; + + // The number of times the parser has recursed (current stack depth). + int stack_depth_; + + // The line number that the parser is at currently. + int line_number_; + + // The last value of |index_| on the previous line. + int index_last_line_; + + // Error information. + JSONReader::JsonParseError error_code_; + int error_line_; + int error_column_; + + friend class JSONParserTest; + FRIEND_TEST_ALL_PREFIXES(JSONParserTest, NextChar); + FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeDictionary); + FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeList); + FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeString); + FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeLiterals); + FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeNumbers); + FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ErrorMessages); + + DISALLOW_COPY_AND_ASSIGN(JSONParser); +}; + +} // namespace internal +} // namespace base + +#endif // BASE_JSON_JSON_PARSER_H_
diff --git a/src/base/json/json_parser_unittest.cc b/src/base/json/json_parser_unittest.cc new file mode 100644 index 0000000..8ee886b --- /dev/null +++ b/src/base/json/json_parser_unittest.cc
@@ -0,0 +1,306 @@ +// 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/json/json_parser.h" + +#include "base/json/json_reader.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace internal { + +class JSONParserTest : public testing::Test { + public: + JSONParser* NewTestParser(const std::string& input) { + JSONParser* parser = new JSONParser(JSON_PARSE_RFC); + parser->start_pos_ = input.data(); + parser->pos_ = parser->start_pos_; + parser->end_pos_ = parser->start_pos_ + input.length(); + return parser; + } + + void TestLastThree(JSONParser* parser) { + EXPECT_EQ(',', *parser->NextChar()); + EXPECT_EQ('|', *parser->NextChar()); + EXPECT_EQ('\0', *parser->NextChar()); + EXPECT_EQ(parser->end_pos_, parser->pos_); + } +}; + +TEST_F(JSONParserTest, NextChar) { + std::string input("Hello world"); + scoped_ptr<JSONParser> parser(NewTestParser(input)); + + EXPECT_EQ('H', *parser->pos_); + for (size_t i = 1; i < input.length(); ++i) { + EXPECT_EQ(input[i], *parser->NextChar()); + } + EXPECT_EQ(parser->end_pos_, parser->NextChar()); +} + +TEST_F(JSONParserTest, ConsumeString) { + std::string input("\"test\",|"); + scoped_ptr<JSONParser> parser(NewTestParser(input)); + scoped_ptr<Value> value(parser->ConsumeString()); + EXPECT_EQ('"', *parser->pos_); + + TestLastThree(parser.get()); + + ASSERT_TRUE(value.get()); + std::string str; + EXPECT_TRUE(value->GetAsString(&str)); + EXPECT_EQ("test", str); +} + +TEST_F(JSONParserTest, ConsumeList) { + std::string input("[true, false],|"); + scoped_ptr<JSONParser> parser(NewTestParser(input)); + scoped_ptr<Value> value(parser->ConsumeList()); + EXPECT_EQ(']', *parser->pos_); + + TestLastThree(parser.get()); + + ASSERT_TRUE(value.get()); + base::ListValue* list; + EXPECT_TRUE(value->GetAsList(&list)); + EXPECT_EQ(2u, list->GetSize()); +} + +TEST_F(JSONParserTest, ConsumeDictionary) { + std::string input("{\"abc\":\"def\"},|"); + scoped_ptr<JSONParser> parser(NewTestParser(input)); + scoped_ptr<Value> value(parser->ConsumeDictionary()); + EXPECT_EQ('}', *parser->pos_); + + TestLastThree(parser.get()); + + ASSERT_TRUE(value.get()); + base::DictionaryValue* dict; + EXPECT_TRUE(value->GetAsDictionary(&dict)); + std::string str; + EXPECT_TRUE(dict->GetString("abc", &str)); + EXPECT_EQ("def", str); +} + +TEST_F(JSONParserTest, ConsumeLiterals) { + // Literal |true|. + std::string input("true,|"); + scoped_ptr<JSONParser> parser(NewTestParser(input)); + scoped_ptr<Value> value(parser->ConsumeLiteral()); + EXPECT_EQ('e', *parser->pos_); + + TestLastThree(parser.get()); + + ASSERT_TRUE(value.get()); + bool bool_value = false; + EXPECT_TRUE(value->GetAsBoolean(&bool_value)); + EXPECT_TRUE(bool_value); + + // Literal |false|. + input = "false,|"; + parser.reset(NewTestParser(input)); + value.reset(parser->ConsumeLiteral()); + EXPECT_EQ('e', *parser->pos_); + + TestLastThree(parser.get()); + + ASSERT_TRUE(value.get()); + EXPECT_TRUE(value->GetAsBoolean(&bool_value)); + EXPECT_FALSE(bool_value); + + // Literal |null|. + input = "null,|"; + parser.reset(NewTestParser(input)); + value.reset(parser->ConsumeLiteral()); + EXPECT_EQ('l', *parser->pos_); + + TestLastThree(parser.get()); + + ASSERT_TRUE(value.get()); + EXPECT_TRUE(value->IsType(Value::TYPE_NULL)); +} + +TEST_F(JSONParserTest, ConsumeNumbers) { + // Integer. + std::string input("1234,|"); + scoped_ptr<JSONParser> parser(NewTestParser(input)); + scoped_ptr<Value> value(parser->ConsumeNumber()); + EXPECT_EQ('4', *parser->pos_); + + TestLastThree(parser.get()); + + ASSERT_TRUE(value.get()); + int number_i; + EXPECT_TRUE(value->GetAsInteger(&number_i)); + EXPECT_EQ(1234, number_i); + + // Negative integer. + input = "-1234,|"; + parser.reset(NewTestParser(input)); + value.reset(parser->ConsumeNumber()); + EXPECT_EQ('4', *parser->pos_); + + TestLastThree(parser.get()); + + ASSERT_TRUE(value.get()); + EXPECT_TRUE(value->GetAsInteger(&number_i)); + EXPECT_EQ(-1234, number_i); + + // Double. + input = "12.34,|"; + parser.reset(NewTestParser(input)); + value.reset(parser->ConsumeNumber()); + EXPECT_EQ('4', *parser->pos_); + + TestLastThree(parser.get()); + + ASSERT_TRUE(value.get()); + double number_d; + EXPECT_TRUE(value->GetAsDouble(&number_d)); + EXPECT_EQ(12.34, number_d); + + // Scientific. + input = "42e3,|"; + parser.reset(NewTestParser(input)); + value.reset(parser->ConsumeNumber()); + EXPECT_EQ('3', *parser->pos_); + + TestLastThree(parser.get()); + + ASSERT_TRUE(value.get()); + EXPECT_TRUE(value->GetAsDouble(&number_d)); + EXPECT_EQ(42000, number_d); + + // Negative scientific. + input = "314159e-5,|"; + parser.reset(NewTestParser(input)); + value.reset(parser->ConsumeNumber()); + EXPECT_EQ('5', *parser->pos_); + + TestLastThree(parser.get()); + + ASSERT_TRUE(value.get()); + EXPECT_TRUE(value->GetAsDouble(&number_d)); + EXPECT_EQ(3.14159, number_d); + + // Positive scientific. + input = "0.42e+3,|"; + parser.reset(NewTestParser(input)); + value.reset(parser->ConsumeNumber()); + EXPECT_EQ('3', *parser->pos_); + + TestLastThree(parser.get()); + + ASSERT_TRUE(value.get()); + EXPECT_TRUE(value->GetAsDouble(&number_d)); + EXPECT_EQ(420, number_d); +} + +TEST_F(JSONParserTest, ErrorMessages) { + // Error strings should not be modified in case of success. + std::string error_message; + int error_code = 0; + scoped_ptr<Value> root; + root.reset(JSONReader::ReadAndReturnError("[42]", JSON_PARSE_RFC, + &error_code, &error_message)); + EXPECT_TRUE(error_message.empty()); + EXPECT_EQ(0, error_code); + + // Test line and column counting + const char* big_json = "[\n0,\n1,\n2,\n3,4,5,6 7,\n8,\n9\n]"; + // error here ---------------------------------^ + root.reset(JSONReader::ReadAndReturnError(big_json, JSON_PARSE_RFC, + &error_code, &error_message)); + EXPECT_FALSE(root.get()); + EXPECT_EQ(JSONParser::FormatErrorMessage(5, 10, JSONReader::kSyntaxError), + error_message); + EXPECT_EQ(JSONReader::JSON_SYNTAX_ERROR, error_code); + + // Test each of the error conditions + root.reset(JSONReader::ReadAndReturnError("{},{}", JSON_PARSE_RFC, + &error_code, &error_message)); + EXPECT_FALSE(root.get()); + EXPECT_EQ(JSONParser::FormatErrorMessage(1, 3, + JSONReader::kUnexpectedDataAfterRoot), error_message); + EXPECT_EQ(JSONReader::JSON_UNEXPECTED_DATA_AFTER_ROOT, error_code); + + std::string nested_json; + for (int i = 0; i < 101; ++i) { + nested_json.insert(nested_json.begin(), '['); + nested_json.append(1, ']'); + } + root.reset(JSONReader::ReadAndReturnError(nested_json, JSON_PARSE_RFC, + &error_code, &error_message)); + EXPECT_FALSE(root.get()); + EXPECT_EQ(JSONParser::FormatErrorMessage(1, 100, JSONReader::kTooMuchNesting), + error_message); + EXPECT_EQ(JSONReader::JSON_TOO_MUCH_NESTING, error_code); + + root.reset(JSONReader::ReadAndReturnError("[1,]", JSON_PARSE_RFC, + &error_code, &error_message)); + EXPECT_FALSE(root.get()); + EXPECT_EQ(JSONParser::FormatErrorMessage(1, 4, JSONReader::kTrailingComma), + error_message); + EXPECT_EQ(JSONReader::JSON_TRAILING_COMMA, error_code); + + root.reset(JSONReader::ReadAndReturnError("{foo:\"bar\"}", JSON_PARSE_RFC, + &error_code, &error_message)); + EXPECT_FALSE(root.get()); + EXPECT_EQ(JSONParser::FormatErrorMessage(1, 2, + JSONReader::kUnquotedDictionaryKey), error_message); + EXPECT_EQ(JSONReader::JSON_UNQUOTED_DICTIONARY_KEY, error_code); + + root.reset(JSONReader::ReadAndReturnError("{\"foo\":\"bar\",}", + JSON_PARSE_RFC, + &error_code, &error_message)); + EXPECT_FALSE(root.get()); + EXPECT_EQ(JSONParser::FormatErrorMessage(1, 14, JSONReader::kTrailingComma), + error_message); + + root.reset(JSONReader::ReadAndReturnError("[nu]", JSON_PARSE_RFC, + &error_code, &error_message)); + EXPECT_FALSE(root.get()); + EXPECT_EQ(JSONParser::FormatErrorMessage(1, 2, JSONReader::kSyntaxError), + error_message); + EXPECT_EQ(JSONReader::JSON_SYNTAX_ERROR, error_code); + + root.reset(JSONReader::ReadAndReturnError("[\"xxx\\xq\"]", JSON_PARSE_RFC, + &error_code, &error_message)); + EXPECT_FALSE(root.get()); + EXPECT_EQ(JSONParser::FormatErrorMessage(1, 7, JSONReader::kInvalidEscape), + error_message); + EXPECT_EQ(JSONReader::JSON_INVALID_ESCAPE, error_code); + + root.reset(JSONReader::ReadAndReturnError("[\"xxx\\uq\"]", JSON_PARSE_RFC, + &error_code, &error_message)); + EXPECT_FALSE(root.get()); + EXPECT_EQ(JSONParser::FormatErrorMessage(1, 7, JSONReader::kInvalidEscape), + error_message); + EXPECT_EQ(JSONReader::JSON_INVALID_ESCAPE, error_code); + + root.reset(JSONReader::ReadAndReturnError("[\"xxx\\q\"]", JSON_PARSE_RFC, + &error_code, &error_message)); + EXPECT_FALSE(root.get()); + EXPECT_EQ(JSONParser::FormatErrorMessage(1, 7, JSONReader::kInvalidEscape), + error_message); + EXPECT_EQ(JSONReader::JSON_INVALID_ESCAPE, error_code); +} + +TEST_F(JSONParserTest, Decode4ByteUtf8Char) { + // This test strings contains a 4 byte unicode character (a smiley!) that the + // reader should be able to handle (the character is \xf0\x9f\x98\x87). + const char kUtf8Data[] = + "[\"😇\",[],[],[],{\"google:suggesttype\":[]}]"; + std::string error_message; + int error_code = 0; + scoped_ptr<Value> root( + JSONReader::ReadAndReturnError(kUtf8Data, JSON_PARSE_RFC, &error_code, + &error_message)); + EXPECT_TRUE(root.get()) << error_message; +} + +} // namespace internal +} // namespace base
diff --git a/src/base/json/json_reader.cc b/src/base/json/json_reader.cc new file mode 100644 index 0000000..593273e --- /dev/null +++ b/src/base/json/json_reader.cc
@@ -0,0 +1,110 @@ +// 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/json/json_reader.h" + +#include "base/json/json_parser.h" +#include "base/logging.h" + +namespace base { + +const char* JSONReader::kInvalidEscape = + "Invalid escape sequence."; +const char* JSONReader::kSyntaxError = + "Syntax error."; +const char* JSONReader::kUnexpectedToken = + "Unexpected token."; +const char* JSONReader::kTrailingComma = + "Trailing comma not allowed."; +const char* JSONReader::kTooMuchNesting = + "Too much nesting."; +const char* JSONReader::kUnexpectedDataAfterRoot = + "Unexpected data after root element."; +const char* JSONReader::kUnsupportedEncoding = + "Unsupported encoding. JSON must be UTF-8."; +const char* JSONReader::kUnquotedDictionaryKey = + "Dictionary keys must be quoted."; + +JSONReader::JSONReader() + : parser_(new internal::JSONParser(JSON_PARSE_RFC)) { +} + +JSONReader::JSONReader(int options) + : parser_(new internal::JSONParser(options)) { +} + +JSONReader::~JSONReader() { +} + +// static +Value* JSONReader::Read(const StringPiece& json) { + internal::JSONParser parser(JSON_PARSE_RFC); + return parser.Parse(json); +} + +// static +Value* JSONReader::Read(const StringPiece& json, + int options) { + internal::JSONParser parser(options); + return parser.Parse(json); +} + +// static +Value* JSONReader::ReadAndReturnError(const StringPiece& json, + int options, + int* error_code_out, + std::string* error_msg_out) { + internal::JSONParser parser(options); + Value* root = parser.Parse(json); + if (root) + return root; + + if (error_code_out) + *error_code_out = parser.error_code(); + if (error_msg_out) + *error_msg_out = parser.GetErrorMessage(); + + return NULL; +} + +// static +std::string JSONReader::ErrorCodeToString(JsonParseError error_code) { + switch (error_code) { + case JSON_NO_ERROR: + return std::string(); + case JSON_INVALID_ESCAPE: + return kInvalidEscape; + case JSON_SYNTAX_ERROR: + return kSyntaxError; + case JSON_UNEXPECTED_TOKEN: + return kUnexpectedToken; + case JSON_TRAILING_COMMA: + return kTrailingComma; + case JSON_TOO_MUCH_NESTING: + return kTooMuchNesting; + case JSON_UNEXPECTED_DATA_AFTER_ROOT: + return kUnexpectedDataAfterRoot; + case JSON_UNSUPPORTED_ENCODING: + return kUnsupportedEncoding; + case JSON_UNQUOTED_DICTIONARY_KEY: + return kUnquotedDictionaryKey; + default: + NOTREACHED(); + return std::string(); + } +} + +Value* JSONReader::ReadToValue(const std::string& json) { + return parser_->Parse(json); +} + +JSONReader::JsonParseError JSONReader::error_code() const { + return parser_->error_code(); +} + +std::string JSONReader::GetErrorMessage() const { + return parser_->GetErrorMessage(); +} + +} // namespace base
diff --git a/src/base/json/json_reader.h b/src/base/json/json_reader.h new file mode 100644 index 0000000..86b2612 --- /dev/null +++ b/src/base/json/json_reader.h
@@ -0,0 +1,135 @@ +// 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. +// +// A JSON parser. Converts strings of JSON into a Value object (see +// base/values.h). +// http://www.ietf.org/rfc/rfc4627.txt?number=4627 +// +// Known limitations/deviations from the RFC: +// - Only knows how to parse ints within the range of a signed 32 bit int and +// decimal numbers within a double. +// - Assumes input is encoded as UTF8. The spec says we should allow UTF-16 +// (BE or LE) and UTF-32 (BE or LE) as well. +// - We limit nesting to 100 levels to prevent stack overflow (this is allowed +// by the RFC). +// - A Unicode FAQ ("http://unicode.org/faq/utf_bom.html") writes a data +// stream may start with a Unicode Byte-Order-Mark (U+FEFF), i.e. the input +// UTF-8 string for the JSONReader::JsonToValue() function may start with a +// UTF-8 BOM (0xEF, 0xBB, 0xBF). +// To avoid the function from mis-treating a UTF-8 BOM as an invalid +// character, the function skips a Unicode BOM at the beginning of the +// Unicode string (converted from the input UTF-8 string) before parsing it. +// +// TODO(tc): Add a parsing option to to relax object keys being wrapped in +// double quotes +// TODO(tc): Add an option to disable comment stripping + +#ifndef BASE_JSON_JSON_READER_H_ +#define BASE_JSON_JSON_READER_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/string_piece.h" +#include "base/memory/scoped_ptr.h" + +namespace base { +class Value; + +namespace internal { +class JSONParser; +} +} + +namespace base { + +enum JSONParserOptions { + // Parses the input strictly according to RFC 4627, except for where noted + // above. + JSON_PARSE_RFC = 0, + + // Allows commas to exist after the last element in structures. + JSON_ALLOW_TRAILING_COMMAS = 1 << 0, + + // The parser can perform optimizations by placing hidden data in the root of + // the JSON object, which speeds up certain operations on children. However, + // if the child is Remove()d from root, it would result in use-after-free + // unless it is DeepCopy()ed or this option is used. + JSON_DETACHABLE_CHILDREN = 1 << 1, +}; + +class BASE_EXPORT JSONReader { + public: + // Error codes during parsing. + enum JsonParseError { + JSON_NO_ERROR = 0, + JSON_INVALID_ESCAPE, + JSON_SYNTAX_ERROR, + JSON_UNEXPECTED_TOKEN, + JSON_TRAILING_COMMA, + JSON_TOO_MUCH_NESTING, + JSON_UNEXPECTED_DATA_AFTER_ROOT, + JSON_UNSUPPORTED_ENCODING, + JSON_UNQUOTED_DICTIONARY_KEY, + }; + + // String versions of parse error codes. + static const char* kInvalidEscape; + static const char* kSyntaxError; + static const char* kUnexpectedToken; + static const char* kTrailingComma; + static const char* kTooMuchNesting; + static const char* kUnexpectedDataAfterRoot; + static const char* kUnsupportedEncoding; + static const char* kUnquotedDictionaryKey; + + // Constructs a reader with the default options, JSON_PARSE_RFC. + JSONReader(); + + // Constructs a reader with custom options. + explicit JSONReader(int options); + + ~JSONReader(); + + // Reads and parses |json|, returning a Value. The caller owns the returned + // instance. If |json| is not a properly formed JSON string, returns NULL. + static Value* Read(const StringPiece& json); + + // Reads and parses |json|, returning a Value owned by the caller. The + // parser respects the given |options|. If the input is not properly formed, + // returns NULL. + static Value* Read(const StringPiece& json, int options); + + // Reads and parses |json| like Read(). |error_code_out| and |error_msg_out| + // are optional. If specified and NULL is returned, they will be populated + // an error code and a formatted error message (including error location if + // appropriate). Otherwise, they will be unmodified. + static Value* ReadAndReturnError(const StringPiece& json, + int options, // JSONParserOptions + int* error_code_out, + std::string* error_msg_out); + + // Converts a JSON parse error code into a human readable message. + // Returns an empty string if error_code is JSON_NO_ERROR. + static std::string ErrorCodeToString(JsonParseError error_code); + + // Parses an input string into a Value that is owned by the caller. + Value* ReadToValue(const std::string& json); + + // Returns the error code if the last call to ReadToValue() failed. + // Returns JSON_NO_ERROR otherwise. + JsonParseError error_code() const; + + // Converts error_code_ to a human-readable string, including line and column + // numbers if appropriate. + std::string GetErrorMessage() const; + + private: + scoped_ptr<internal::JSONParser> parser_; +}; + +} // namespace base + +#endif // BASE_JSON_JSON_READER_H_
diff --git a/src/base/json/json_reader_unittest.cc b/src/base/json/json_reader_unittest.cc new file mode 100644 index 0000000..38bf590 --- /dev/null +++ b/src/base/json/json_reader_unittest.cc
@@ -0,0 +1,648 @@ +// 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/json/json_reader.h" + +#include "base/base_paths.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/string_piece.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(JSONReaderTest, Reading) { + // some whitespace checking + scoped_ptr<Value> root; + root.reset(JSONReader().ReadToValue(" null ")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_NULL)); + + // Invalid JSON string + root.reset(JSONReader().ReadToValue("nu")); + EXPECT_FALSE(root.get()); + + // Simple bool + root.reset(JSONReader().ReadToValue("true ")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_BOOLEAN)); + + // Embedded comment + root.reset(JSONReader().ReadToValue("/* comment */null")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_NULL)); + root.reset(JSONReader().ReadToValue("40 /* comment */")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_INTEGER)); + root.reset(JSONReader().ReadToValue("true // comment")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_BOOLEAN)); + root.reset(JSONReader().ReadToValue("/* comment */\"sample string\"")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_STRING)); + std::string value; + EXPECT_TRUE(root->GetAsString(&value)); + EXPECT_EQ("sample string", value); + root.reset(JSONReader().ReadToValue("[1, /* comment, 2 ] */ \n 3]")); + ASSERT_TRUE(root.get()); + ListValue* list = static_cast<ListValue*>(root.get()); + EXPECT_EQ(2u, list->GetSize()); + int int_val = 0; + EXPECT_TRUE(list->GetInteger(0, &int_val)); + EXPECT_EQ(1, int_val); + EXPECT_TRUE(list->GetInteger(1, &int_val)); + EXPECT_EQ(3, int_val); + root.reset(JSONReader().ReadToValue("[1, /*a*/2, 3]")); + ASSERT_TRUE(root.get()); + list = static_cast<ListValue*>(root.get()); + EXPECT_EQ(3u, list->GetSize()); + + // Test number formats + root.reset(JSONReader().ReadToValue("43")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_INTEGER)); + EXPECT_TRUE(root->GetAsInteger(&int_val)); + EXPECT_EQ(43, int_val); + + // According to RFC4627, oct, hex, and leading zeros are invalid JSON. + root.reset(JSONReader().ReadToValue("043")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("0x43")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("00")); + EXPECT_FALSE(root.get()); + + // Test 0 (which needs to be special cased because of the leading zero + // clause). + root.reset(JSONReader().ReadToValue("0")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_INTEGER)); + int_val = 1; + EXPECT_TRUE(root->GetAsInteger(&int_val)); + EXPECT_EQ(0, int_val); + + // Numbers that overflow ints should succeed, being internally promoted to + // storage as doubles + root.reset(JSONReader().ReadToValue("2147483648")); + ASSERT_TRUE(root.get()); + double double_val; + EXPECT_TRUE(root->IsType(Value::TYPE_DOUBLE)); + double_val = 0.0; + EXPECT_TRUE(root->GetAsDouble(&double_val)); + EXPECT_DOUBLE_EQ(2147483648.0, double_val); + root.reset(JSONReader().ReadToValue("-2147483649")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_DOUBLE)); + double_val = 0.0; + EXPECT_TRUE(root->GetAsDouble(&double_val)); + EXPECT_DOUBLE_EQ(-2147483649.0, double_val); + + // Parse a double + root.reset(JSONReader().ReadToValue("43.1")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_DOUBLE)); + double_val = 0.0; + EXPECT_TRUE(root->GetAsDouble(&double_val)); + EXPECT_DOUBLE_EQ(43.1, double_val); + + root.reset(JSONReader().ReadToValue("4.3e-1")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_DOUBLE)); + double_val = 0.0; + EXPECT_TRUE(root->GetAsDouble(&double_val)); + EXPECT_DOUBLE_EQ(.43, double_val); + + root.reset(JSONReader().ReadToValue("2.1e0")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_DOUBLE)); + double_val = 0.0; + EXPECT_TRUE(root->GetAsDouble(&double_val)); + EXPECT_DOUBLE_EQ(2.1, double_val); + + root.reset(JSONReader().ReadToValue("2.1e+0001")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_DOUBLE)); + double_val = 0.0; + EXPECT_TRUE(root->GetAsDouble(&double_val)); + EXPECT_DOUBLE_EQ(21.0, double_val); + + root.reset(JSONReader().ReadToValue("0.01")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_DOUBLE)); + double_val = 0.0; + EXPECT_TRUE(root->GetAsDouble(&double_val)); + EXPECT_DOUBLE_EQ(0.01, double_val); + + root.reset(JSONReader().ReadToValue("1.00")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_DOUBLE)); + double_val = 0.0; + EXPECT_TRUE(root->GetAsDouble(&double_val)); + EXPECT_DOUBLE_EQ(1.0, double_val); + + // Fractional parts must have a digit before and after the decimal point. + root.reset(JSONReader().ReadToValue("1.")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue(".1")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("1.e10")); + EXPECT_FALSE(root.get()); + + // Exponent must have a digit following the 'e'. + root.reset(JSONReader().ReadToValue("1e")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("1E")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("1e1.")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("1e1.0")); + EXPECT_FALSE(root.get()); + + // INF/-INF/NaN are not valid + root.reset(JSONReader().ReadToValue("1e1000")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("-1e1000")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("NaN")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("nan")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("inf")); + EXPECT_FALSE(root.get()); + + // Invalid number formats + root.reset(JSONReader().ReadToValue("4.3.1")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("4e3.1")); + EXPECT_FALSE(root.get()); + + // Test string parser + root.reset(JSONReader().ReadToValue("\"hello world\"")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_STRING)); + std::string str_val; + EXPECT_TRUE(root->GetAsString(&str_val)); + EXPECT_EQ("hello world", str_val); + + // Empty string + root.reset(JSONReader().ReadToValue("\"\"")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_STRING)); + str_val.clear(); + EXPECT_TRUE(root->GetAsString(&str_val)); + EXPECT_EQ("", str_val); + + // Test basic string escapes + root.reset(JSONReader().ReadToValue("\" \\\"\\\\\\/\\b\\f\\n\\r\\t\\v\"")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_STRING)); + str_val.clear(); + EXPECT_TRUE(root->GetAsString(&str_val)); + EXPECT_EQ(" \"\\/\b\f\n\r\t\v", str_val); + + // Test hex and unicode escapes including the null character. + root.reset(JSONReader().ReadToValue("\"\\x41\\x00\\u1234\"")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_STRING)); + str_val.clear(); + EXPECT_TRUE(root->GetAsString(&str_val)); + EXPECT_EQ(std::wstring(L"A\0\x1234", 3), UTF8ToWide(str_val)); + + // Test invalid strings + root.reset(JSONReader().ReadToValue("\"no closing quote")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("\"\\z invalid escape char\"")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("\"\\xAQ invalid hex code\"")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("not enough hex chars\\x1\"")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("\"not enough escape chars\\u123\"")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("\"extra backslash at end of input\\\"")); + EXPECT_FALSE(root.get()); + + // Basic array + root.reset(JSONReader::Read("[true, false, null]")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_LIST)); + list = static_cast<ListValue*>(root.get()); + EXPECT_EQ(3U, list->GetSize()); + + // Test with trailing comma. Should be parsed the same as above. + scoped_ptr<Value> root2; + root2.reset(JSONReader::Read("[true, false, null, ]", + JSON_ALLOW_TRAILING_COMMAS)); + EXPECT_TRUE(root->Equals(root2.get())); + + // Empty array + root.reset(JSONReader::Read("[]")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_LIST)); + list = static_cast<ListValue*>(root.get()); + EXPECT_EQ(0U, list->GetSize()); + + // Nested arrays + root.reset(JSONReader::Read("[[true], [], [false, [], [null]], null]")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_LIST)); + list = static_cast<ListValue*>(root.get()); + EXPECT_EQ(4U, list->GetSize()); + + // Lots of trailing commas. + root2.reset(JSONReader::Read("[[true], [], [false, [], [null, ] , ], null,]", + JSON_ALLOW_TRAILING_COMMAS)); + EXPECT_TRUE(root->Equals(root2.get())); + + // Invalid, missing close brace. + root.reset(JSONReader::Read("[[true], [], [false, [], [null]], null")); + EXPECT_FALSE(root.get()); + + // Invalid, too many commas + root.reset(JSONReader::Read("[true,, null]")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader::Read("[true,, null]", JSON_ALLOW_TRAILING_COMMAS)); + EXPECT_FALSE(root.get()); + + // Invalid, no commas + root.reset(JSONReader::Read("[true null]")); + EXPECT_FALSE(root.get()); + + // Invalid, trailing comma + root.reset(JSONReader::Read("[true,]")); + EXPECT_FALSE(root.get()); + + // Valid if we set |allow_trailing_comma| to true. + root.reset(JSONReader::Read("[true,]", JSON_ALLOW_TRAILING_COMMAS)); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_LIST)); + list = static_cast<ListValue*>(root.get()); + EXPECT_EQ(1U, list->GetSize()); + Value* tmp_value = NULL; + ASSERT_TRUE(list->Get(0, &tmp_value)); + EXPECT_TRUE(tmp_value->IsType(Value::TYPE_BOOLEAN)); + bool bool_value = false; + EXPECT_TRUE(tmp_value->GetAsBoolean(&bool_value)); + EXPECT_TRUE(bool_value); + + // Don't allow empty elements, even if |allow_trailing_comma| is + // true. + root.reset(JSONReader::Read("[,]", JSON_ALLOW_TRAILING_COMMAS)); + EXPECT_FALSE(root.get()); + root.reset(JSONReader::Read("[true,,]", JSON_ALLOW_TRAILING_COMMAS)); + EXPECT_FALSE(root.get()); + root.reset(JSONReader::Read("[,true,]", JSON_ALLOW_TRAILING_COMMAS)); + EXPECT_FALSE(root.get()); + root.reset(JSONReader::Read("[true,,false]", JSON_ALLOW_TRAILING_COMMAS)); + EXPECT_FALSE(root.get()); + + // Test objects + root.reset(JSONReader::Read("{}")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_DICTIONARY)); + + root.reset(JSONReader::Read( + "{\"number\":9.87654321, \"null\":null , \"\\x53\" : \"str\" }")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_DICTIONARY)); + DictionaryValue* dict_val = static_cast<DictionaryValue*>(root.get()); + double_val = 0.0; + EXPECT_TRUE(dict_val->GetDouble("number", &double_val)); + EXPECT_DOUBLE_EQ(9.87654321, double_val); + Value* null_val = NULL; + ASSERT_TRUE(dict_val->Get("null", &null_val)); + EXPECT_TRUE(null_val->IsType(Value::TYPE_NULL)); + str_val.clear(); + EXPECT_TRUE(dict_val->GetString("S", &str_val)); + EXPECT_EQ("str", str_val); + + root2.reset(JSONReader::Read( + "{\"number\":9.87654321, \"null\":null , \"\\x53\" : \"str\", }", + JSON_ALLOW_TRAILING_COMMAS)); + ASSERT_TRUE(root2.get()); + EXPECT_TRUE(root->Equals(root2.get())); + + // Test newline equivalence. + root2.reset(JSONReader::Read( + "{\n" + " \"number\":9.87654321,\n" + " \"null\":null,\n" + " \"\\x53\":\"str\",\n" + "}\n", JSON_ALLOW_TRAILING_COMMAS)); + ASSERT_TRUE(root2.get()); + EXPECT_TRUE(root->Equals(root2.get())); + + root2.reset(JSONReader::Read( + "{\r\n" + " \"number\":9.87654321,\r\n" + " \"null\":null,\r\n" + " \"\\x53\":\"str\",\r\n" + "}\r\n", JSON_ALLOW_TRAILING_COMMAS)); + ASSERT_TRUE(root2.get()); + EXPECT_TRUE(root->Equals(root2.get())); + + // Test nesting + root.reset(JSONReader::Read( + "{\"inner\":{\"array\":[true]},\"false\":false,\"d\":{}}")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_DICTIONARY)); + dict_val = static_cast<DictionaryValue*>(root.get()); + DictionaryValue* inner_dict = NULL; + ASSERT_TRUE(dict_val->GetDictionary("inner", &inner_dict)); + ListValue* inner_array = NULL; + ASSERT_TRUE(inner_dict->GetList("array", &inner_array)); + EXPECT_EQ(1U, inner_array->GetSize()); + bool_value = true; + EXPECT_TRUE(dict_val->GetBoolean("false", &bool_value)); + EXPECT_FALSE(bool_value); + inner_dict = NULL; + EXPECT_TRUE(dict_val->GetDictionary("d", &inner_dict)); + + root2.reset(JSONReader::Read( + "{\"inner\": {\"array\":[true] , },\"false\":false,\"d\":{},}", + JSON_ALLOW_TRAILING_COMMAS)); + EXPECT_TRUE(root->Equals(root2.get())); + + // Test keys with periods + root.reset(JSONReader::Read( + "{\"a.b\":3,\"c\":2,\"d.e.f\":{\"g.h.i.j\":1}}")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_DICTIONARY)); + dict_val = static_cast<DictionaryValue*>(root.get()); + int integer_value = 0; + EXPECT_TRUE(dict_val->GetIntegerWithoutPathExpansion("a.b", &integer_value)); + EXPECT_EQ(3, integer_value); + EXPECT_TRUE(dict_val->GetIntegerWithoutPathExpansion("c", &integer_value)); + EXPECT_EQ(2, integer_value); + inner_dict = NULL; + ASSERT_TRUE(dict_val->GetDictionaryWithoutPathExpansion("d.e.f", + &inner_dict)); + EXPECT_EQ(1U, inner_dict->size()); + EXPECT_TRUE(inner_dict->GetIntegerWithoutPathExpansion("g.h.i.j", + &integer_value)); + EXPECT_EQ(1, integer_value); + + root.reset(JSONReader::Read("{\"a\":{\"b\":2},\"a.b\":1}")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_DICTIONARY)); + dict_val = static_cast<DictionaryValue*>(root.get()); + EXPECT_TRUE(dict_val->GetInteger("a.b", &integer_value)); + EXPECT_EQ(2, integer_value); + EXPECT_TRUE(dict_val->GetIntegerWithoutPathExpansion("a.b", &integer_value)); + EXPECT_EQ(1, integer_value); + + // Invalid, no closing brace + root.reset(JSONReader::Read("{\"a\": true")); + EXPECT_FALSE(root.get()); + + // Invalid, keys must be quoted + root.reset(JSONReader::Read("{foo:true}")); + EXPECT_FALSE(root.get()); + + // Invalid, trailing comma + root.reset(JSONReader::Read("{\"a\":true,}")); + EXPECT_FALSE(root.get()); + + // Invalid, too many commas + root.reset(JSONReader::Read("{\"a\":true,,\"b\":false}")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader::Read("{\"a\":true,,\"b\":false}", + JSON_ALLOW_TRAILING_COMMAS)); + EXPECT_FALSE(root.get()); + + // Invalid, no separator + root.reset(JSONReader::Read("{\"a\" \"b\"}")); + EXPECT_FALSE(root.get()); + + // Invalid, lone comma. + root.reset(JSONReader::Read("{,}")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader::Read("{,}", JSON_ALLOW_TRAILING_COMMAS)); + EXPECT_FALSE(root.get()); + root.reset(JSONReader::Read("{\"a\":true,,}", JSON_ALLOW_TRAILING_COMMAS)); + EXPECT_FALSE(root.get()); + root.reset(JSONReader::Read("{,\"a\":true}", JSON_ALLOW_TRAILING_COMMAS)); + EXPECT_FALSE(root.get()); + root.reset(JSONReader::Read("{\"a\":true,,\"b\":false}", + JSON_ALLOW_TRAILING_COMMAS)); + EXPECT_FALSE(root.get()); + + // Test stack overflow + std::string evil(1000000, '['); + evil.append(std::string(1000000, ']')); + root.reset(JSONReader::Read(evil)); + EXPECT_FALSE(root.get()); + + // A few thousand adjacent lists is fine. + std::string not_evil("["); + not_evil.reserve(15010); + for (int i = 0; i < 5000; ++i) { + not_evil.append("[],"); + } + not_evil.append("[]]"); + root.reset(JSONReader::Read(not_evil)); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_LIST)); + list = static_cast<ListValue*>(root.get()); + EXPECT_EQ(5001U, list->GetSize()); + + // Test utf8 encoded input + root.reset(JSONReader().ReadToValue("\"\xe7\xbd\x91\xe9\xa1\xb5\"")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_STRING)); + str_val.clear(); + EXPECT_TRUE(root->GetAsString(&str_val)); + EXPECT_EQ(L"\x7f51\x9875", UTF8ToWide(str_val)); + + root.reset(JSONReader().ReadToValue( + "{\"path\": \"/tmp/\xc3\xa0\xc3\xa8\xc3\xb2.png\"}")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_DICTIONARY)); + EXPECT_TRUE(root->GetAsDictionary(&dict_val)); + EXPECT_TRUE(dict_val->GetString("path", &str_val)); + EXPECT_EQ("/tmp/\xC3\xA0\xC3\xA8\xC3\xB2.png", str_val); + + // Test invalid utf8 encoded input + root.reset(JSONReader().ReadToValue("\"345\xb0\xa1\xb0\xa2\"")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("\"123\xc0\x81\"")); + EXPECT_FALSE(root.get()); + root.reset(JSONReader().ReadToValue("\"abc\xc0\xae\"")); + EXPECT_FALSE(root.get()); + + // Test utf16 encoded strings. + root.reset(JSONReader().ReadToValue("\"\\u20ac3,14\"")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_STRING)); + str_val.clear(); + EXPECT_TRUE(root->GetAsString(&str_val)); + EXPECT_EQ("\xe2\x82\xac""3,14", str_val); + + root.reset(JSONReader().ReadToValue("\"\\ud83d\\udca9\\ud83d\\udc6c\"")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->IsType(Value::TYPE_STRING)); + str_val.clear(); + EXPECT_TRUE(root->GetAsString(&str_val)); + EXPECT_EQ("\xf0\x9f\x92\xa9\xf0\x9f\x91\xac", str_val); + + // Test invalid utf16 strings. + const char* cases[] = { + "\"\\u123\"", // Invalid scalar. + "\"\\ud83d\"", // Invalid scalar. + "\"\\u$%@!\"", // Invalid scalar. + "\"\\uzz89\"", // Invalid scalar. + "\"\\ud83d\\udca\"", // Invalid lower surrogate. + "\"\\ud83d\\ud83d\"", // Invalid lower surrogate. + "\"\\ud83foo\"", // No lower surrogate. + "\"\\ud83\\foo\"" // No lower surrogate. + }; + for (size_t i = 0; i < arraysize(cases); ++i) { + root.reset(JSONReader().ReadToValue(cases[i])); + EXPECT_FALSE(root.get()) << cases[i]; + } + + // Test literal root objects. + root.reset(JSONReader::Read("null")); + EXPECT_TRUE(root->IsType(Value::TYPE_NULL)); + + root.reset(JSONReader::Read("true")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->GetAsBoolean(&bool_value)); + EXPECT_TRUE(bool_value); + + root.reset(JSONReader::Read("10")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->GetAsInteger(&integer_value)); + EXPECT_EQ(10, integer_value); + + root.reset(JSONReader::Read("\"root\"")); + ASSERT_TRUE(root.get()); + EXPECT_TRUE(root->GetAsString(&str_val)); + EXPECT_EQ("root", str_val); +} + +TEST(JSONReaderTest, ReadFromFile) { + FilePath path; + ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &path)); + path = path.Append(FILE_PATH_LITERAL("base")) + .Append(FILE_PATH_LITERAL("data")) + .Append(FILE_PATH_LITERAL("json")); + + std::string input; + ASSERT_TRUE(file_util::ReadFileToString( + path.Append(FILE_PATH_LITERAL("bom_feff.json")), &input)); + + JSONReader reader; + scoped_ptr<Value> root(reader.ReadToValue(input)); + ASSERT_TRUE(root.get()) << reader.GetErrorMessage(); + EXPECT_TRUE(root->IsType(Value::TYPE_DICTIONARY)); +} + +// Tests that the root of a JSON object can be deleted safely while its +// children outlive it. +TEST(JSONReaderTest, StringOptimizations) { + Value* dict_literals[2] = {0}; + Value* dict_strings[2] = {0}; + Value* list_values[2] = {0}; + + { + scoped_ptr<Value> root(JSONReader::Read( + "{" + " \"test\": {" + " \"foo\": true," + " \"bar\": 3.14," + " \"baz\": \"bat\"," + " \"moo\": \"cow\"" + " }," + " \"list\": [" + " \"a\"," + " \"b\"" + " ]" + "}", JSON_DETACHABLE_CHILDREN)); + ASSERT_TRUE(root.get()); + + DictionaryValue* root_dict = NULL; + ASSERT_TRUE(root->GetAsDictionary(&root_dict)); + + DictionaryValue* dict = NULL; + ListValue* list = NULL; + + ASSERT_TRUE(root_dict->GetDictionary("test", &dict)); + ASSERT_TRUE(root_dict->GetList("list", &list)); + + EXPECT_TRUE(dict->Remove("foo", &dict_literals[0])); + EXPECT_TRUE(dict->Remove("bar", &dict_literals[1])); + EXPECT_TRUE(dict->Remove("baz", &dict_strings[0])); + EXPECT_TRUE(dict->Remove("moo", &dict_strings[1])); + + ASSERT_EQ(2u, list->GetSize()); + EXPECT_TRUE(list->Remove(0, &list_values[0])); + EXPECT_TRUE(list->Remove(0, &list_values[1])); + } + + bool b = false; + double d = 0; + std::string s; + + EXPECT_TRUE(dict_literals[0]->GetAsBoolean(&b)); + EXPECT_TRUE(b); + + EXPECT_TRUE(dict_literals[1]->GetAsDouble(&d)); + EXPECT_EQ(3.14, d); + + EXPECT_TRUE(dict_strings[0]->GetAsString(&s)); + EXPECT_EQ("bat", s); + + EXPECT_TRUE(dict_strings[1]->GetAsString(&s)); + EXPECT_EQ("cow", s); + + EXPECT_TRUE(list_values[0]->GetAsString(&s)); + EXPECT_EQ("a", s); + EXPECT_TRUE(list_values[1]->GetAsString(&s)); + EXPECT_EQ("b", s); + + delete dict_literals[0]; + delete dict_literals[1]; + delete dict_strings[0]; + delete dict_strings[1]; + delete list_values[0]; + delete list_values[1]; +} + +// A smattering of invalid JSON designed to test specific portions of the +// parser implementation against buffer overflow. Best run with DCHECKs so +// that the one in NextChar fires. +TEST(JSONReaderTest, InvalidSanity) { + const char* invalid_json[] = { + "/* test *", + "{\"foo\"", + "{\"foo\":", + " [", + "\"\\u123g\"", + "{\n\"eh:\n}", + }; + + for (size_t i = 0; i < arraysize(invalid_json); ++i) { + JSONReader reader; + LOG(INFO) << "Sanity test " << i << ": <" << invalid_json[i] << ">"; + EXPECT_FALSE(reader.ReadToValue(invalid_json[i])); + EXPECT_NE(JSONReader::JSON_NO_ERROR, reader.error_code()); + EXPECT_NE("", reader.GetErrorMessage()); + } +} + +TEST(JSONReaderTest, IllegalTrailingNull) { + const char json[] = { '"', 'n', 'u', 'l', 'l', '"', '\0' }; + std::string json_string(json, sizeof(json)); + JSONReader reader; + EXPECT_FALSE(reader.ReadToValue(json_string)); + EXPECT_EQ(JSONReader::JSON_UNEXPECTED_DATA_AFTER_ROOT, reader.error_code()); +} + +} // namespace base
diff --git a/src/base/json/json_string_value_serializer.cc b/src/base/json/json_string_value_serializer.cc new file mode 100644 index 0000000..59c0765 --- /dev/null +++ b/src/base/json/json_string_value_serializer.cc
@@ -0,0 +1,46 @@ +// 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/json/json_string_value_serializer.h" + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/logging.h" + +JSONStringValueSerializer::~JSONStringValueSerializer() {} + +bool JSONStringValueSerializer::Serialize(const Value& root) { + return SerializeInternal(root, false); +} + +bool JSONStringValueSerializer::SerializeAndOmitBinaryValues( + const Value& root) { + return SerializeInternal(root, true); +} + +bool JSONStringValueSerializer::SerializeInternal(const Value& root, + bool omit_binary_values) { + if (!json_string_ || initialized_with_const_string_) + return false; + + int options = 0; + if (omit_binary_values) + options |= base::JSONWriter::OPTIONS_OMIT_BINARY_VALUES; + if (pretty_print_) + options |= base::JSONWriter::OPTIONS_PRETTY_PRINT; + + base::JSONWriter::WriteWithOptions(&root, options, json_string_); + return true; +} + +Value* JSONStringValueSerializer::Deserialize(int* error_code, + std::string* error_str) { + if (!json_string_) + return NULL; + + return base::JSONReader::ReadAndReturnError(*json_string_, + allow_trailing_comma_ ? base::JSON_ALLOW_TRAILING_COMMAS : + base::JSON_PARSE_RFC, + error_code, error_str); +}
diff --git a/src/base/json/json_string_value_serializer.h b/src/base/json/json_string_value_serializer.h new file mode 100644 index 0000000..e333dc6 --- /dev/null +++ b/src/base/json/json_string_value_serializer.h
@@ -0,0 +1,77 @@ +// 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_JSON_JSON_STRING_VALUE_SERIALIZER_H_ +#define BASE_JSON_JSON_STRING_VALUE_SERIALIZER_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/values.h" + +class BASE_EXPORT JSONStringValueSerializer : public base::ValueSerializer { + public: + // json_string is the string that will be source of the deserialization + // or the destination of the serialization. The caller of the constructor + // retains ownership of the string. + explicit JSONStringValueSerializer(std::string* json_string) + : json_string_(json_string), + initialized_with_const_string_(false), + pretty_print_(false), + allow_trailing_comma_(false) { + } + + // This version allows initialization with a const string reference for + // deserialization only. + explicit JSONStringValueSerializer(const std::string& json_string) + : json_string_(&const_cast<std::string&>(json_string)), + initialized_with_const_string_(true), + pretty_print_(false), + allow_trailing_comma_(false) { + } + + virtual ~JSONStringValueSerializer(); + + // Attempt to serialize the data structure represented by Value into + // JSON. If the return value is true, the result will have been written + // into the string passed into the constructor. + virtual bool Serialize(const Value& root) OVERRIDE; + + // Equivalent to Serialize(root) except binary values are omitted from the + // output. + bool SerializeAndOmitBinaryValues(const Value& root); + + // 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). + // 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. + virtual Value* Deserialize(int* error_code, + std::string* error_message) OVERRIDE; + + void set_pretty_print(bool new_value) { pretty_print_ = new_value; } + bool pretty_print() { return pretty_print_; } + + void set_allow_trailing_comma(bool new_value) { + allow_trailing_comma_ = new_value; + } + + private: + bool SerializeInternal(const Value& root, bool omit_binary_values); + + std::string* json_string_; + bool initialized_with_const_string_; + bool pretty_print_; // If true, serialization will span multiple lines. + // If true, deserialization will allow trailing commas. + bool allow_trailing_comma_; + + DISALLOW_COPY_AND_ASSIGN(JSONStringValueSerializer); +}; + +#endif // BASE_JSON_JSON_STRING_VALUE_SERIALIZER_H_ +
diff --git a/src/base/json/json_value_converter.h b/src/base/json/json_value_converter.h new file mode 100644 index 0000000..69da0d8 --- /dev/null +++ b/src/base/json/json_value_converter.h
@@ -0,0 +1,527 @@ +// 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_JSON_JSON_VALUE_CONVERTER_H_ +#define BASE_JSON_JSON_VALUE_CONVERTER_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/stl_util.h" +#include "base/string16.h" +#include "base/string_piece.h" +#include "base/values.h" + +// JSONValueConverter converts a JSON value into a C++ struct in a +// lightweight way. +// +// Usage: +// For real examples, you may want to refer to _unittest.cc file. +// +// Assume that you have a struct like this: +// struct Message { +// int foo; +// std::string bar; +// static void RegisterJSONConverter( +// JSONValueConverter<Message>* converter); +// }; +// +// And you want to parse a json data into this struct. First, you +// need to declare RegisterJSONConverter() method in your struct. +// // static +// void Message::RegisterJSONConverter( +// JSONValueConverter<Message>* converter) { +// converter->RegisterIntField("foo", &Message::foo); +// converter->RegisterStringField("bar", &Message::bar); +// } +// +// Then, you just instantiate your JSONValueConverter of your type and call +// Convert() method. +// Message message; +// JSONValueConverter<Message> converter; +// converter.Convert(json, &message); +// +// Convert() returns false when it fails. Here "fail" means that the value is +// structurally different from expected, such like a string value appears +// for an int field. Do not report failures for missing fields. +// Also note that Convert() will modify the passed |message| even when it +// fails for performance reason. +// +// For nested field, the internal message also has to implement the registration +// method. Then, just use RegisterNestedField() from the containing struct's +// RegisterJSONConverter method. +// struct Nested { +// Message foo; +// static void RegisterJSONConverter(...) { +// ... +// converter->RegisterNestedField("foo", &Nested::foo); +// } +// }; +// +// For repeated field, we just assume ScopedVector for its container +// and you can put RegisterRepeatedInt or some other types. Use +// RegisterRepeatedMessage for nested repeated fields. +// +// Sometimes JSON format uses string representations for other types such +// like enum, timestamp, or URL. You can use RegisterCustomField method +// and specify a function to convert a StringPiece to your type. +// bool ConvertFunc(const StringPiece& s, YourEnum* result) { +// // do something and return true if succeed... +// } +// struct Message { +// YourEnum ye; +// ... +// static void RegisterJSONConverter(...) { +// ... +// converter->RegsiterCustomField<YourEnum>( +// "your_enum", &Message::ye, &ConvertFunc); +// } +// }; + +namespace base { + +template <typename StructType> +class JSONValueConverter; + +namespace internal { + +template<typename StructType> +class FieldConverterBase { + public: + explicit FieldConverterBase(const std::string& path) : field_path_(path) {} + virtual ~FieldConverterBase() {} + virtual bool ConvertField(const base::Value& value, StructType* obj) + const = 0; + const std::string& field_path() const { return field_path_; } + + private: + std::string field_path_; + DISALLOW_COPY_AND_ASSIGN(FieldConverterBase); +}; + +template <typename FieldType> +class ValueConverter { + public: + virtual ~ValueConverter() {} + virtual bool Convert(const base::Value& value, FieldType* field) const = 0; +}; + +template <typename StructType, typename FieldType> +class FieldConverter : public FieldConverterBase<StructType> { + public: + explicit FieldConverter(const std::string& path, + FieldType StructType::* field, + ValueConverter<FieldType>* converter) + : FieldConverterBase<StructType>(path), + field_pointer_(field), + value_converter_(converter) { + } + + virtual bool ConvertField( + const base::Value& value, StructType* dst) const OVERRIDE { + return value_converter_->Convert(value, &(dst->*field_pointer_)); + } + + private: + FieldType StructType::* field_pointer_; + scoped_ptr<ValueConverter<FieldType> > value_converter_; + DISALLOW_COPY_AND_ASSIGN(FieldConverter); +}; + +template <typename FieldType> +class BasicValueConverter; + +template <> +class BasicValueConverter<int> : public ValueConverter<int> { + public: + BasicValueConverter() {} + + virtual bool Convert(const base::Value& value, int* field) const OVERRIDE { + return value.GetAsInteger(field); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicValueConverter); +}; + +template <> +class BasicValueConverter<std::string> : public ValueConverter<std::string> { + public: + BasicValueConverter() {} + + virtual bool Convert( + const base::Value& value, std::string* field) const OVERRIDE { + return value.GetAsString(field); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicValueConverter); +}; + +template <> +class BasicValueConverter<string16> : public ValueConverter<string16> { + public: + BasicValueConverter() {} + + virtual bool Convert( + const base::Value& value, string16* field) const OVERRIDE { + return value.GetAsString(field); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicValueConverter); +}; + +template <> +class BasicValueConverter<double> : public ValueConverter<double> { + public: + BasicValueConverter() {} + + virtual bool Convert(const base::Value& value, double* field) const OVERRIDE { + return value.GetAsDouble(field); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicValueConverter); +}; + +template <> +class BasicValueConverter<bool> : public ValueConverter<bool> { + public: + BasicValueConverter() {} + + virtual bool Convert(const base::Value& value, bool* field) const OVERRIDE { + return value.GetAsBoolean(field); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicValueConverter); +}; + +template <typename FieldType> +class ValueFieldConverter : public ValueConverter<FieldType> { + public: + typedef bool(*ConvertFunc)(const base::Value* value, FieldType* field); + + ValueFieldConverter(ConvertFunc convert_func) + : convert_func_(convert_func) {} + + virtual bool Convert(const base::Value& value, + FieldType* field) const OVERRIDE { + return convert_func_(&value, field); + } + + private: + ConvertFunc convert_func_; + + DISALLOW_COPY_AND_ASSIGN(ValueFieldConverter); +}; + +template <typename FieldType> +class CustomFieldConverter : public ValueConverter<FieldType> { + public: + typedef bool(*ConvertFunc)(const StringPiece& value, FieldType* field); + + CustomFieldConverter(ConvertFunc convert_func) + : convert_func_(convert_func) {} + + virtual bool Convert(const base::Value& value, + FieldType* field) const OVERRIDE { + std::string string_value; + return value.GetAsString(&string_value) && + convert_func_(string_value, field); + } + + private: + ConvertFunc convert_func_; + + DISALLOW_COPY_AND_ASSIGN(CustomFieldConverter); +}; + +template <typename NestedType> +class NestedValueConverter : public ValueConverter<NestedType> { + public: + NestedValueConverter() {} + + virtual bool Convert( + const base::Value& value, NestedType* field) const OVERRIDE { + return converter_.Convert(value, field); + } + + private: + JSONValueConverter<NestedType> converter_; + DISALLOW_COPY_AND_ASSIGN(NestedValueConverter); +}; + +template <typename Element> +class RepeatedValueConverter : public ValueConverter<ScopedVector<Element> > { + public: + RepeatedValueConverter() {} + + virtual bool Convert( + const base::Value& value, ScopedVector<Element>* field) const OVERRIDE { + const base::ListValue* list = NULL; + if (!value.GetAsList(&list)) { + // The field is not a list. + return false; + } + + field->reserve(list->GetSize()); + for (size_t i = 0; i < list->GetSize(); ++i) { + const base::Value* element = NULL; + if (!list->Get(i, &element)) + continue; + + scoped_ptr<Element> e(new Element); + if (basic_converter_.Convert(*element, e.get())) { + field->push_back(e.release()); + } else { + DVLOG(1) << "failure at " << i << "-th element"; + return false; + } + } + return true; + } + + private: + BasicValueConverter<Element> basic_converter_; + DISALLOW_COPY_AND_ASSIGN(RepeatedValueConverter); +}; + +template <typename NestedType> +class RepeatedMessageConverter + : public ValueConverter<ScopedVector<NestedType> > { + public: + RepeatedMessageConverter() {} + + virtual bool Convert(const base::Value& value, + ScopedVector<NestedType>* field) const OVERRIDE { + const base::ListValue* list = NULL; + if (!value.GetAsList(&list)) + return false; + + field->reserve(list->GetSize()); + for (size_t i = 0; i < list->GetSize(); ++i) { + const base::Value* element = NULL; + if (!list->Get(i, &element)) + continue; + + scoped_ptr<NestedType> nested(new NestedType); + if (converter_.Convert(*element, nested.get())) { + field->push_back(nested.release()); + } else { + DVLOG(1) << "failure at " << i << "-th element"; + return false; + } + } + return true; + } + + private: + JSONValueConverter<NestedType> converter_; + DISALLOW_COPY_AND_ASSIGN(RepeatedMessageConverter); +}; + +template <typename NestedType> +class RepeatedCustomValueConverter + : public ValueConverter<ScopedVector<NestedType> > { + public: + typedef bool(*ConvertFunc)(const base::Value* value, NestedType* field); + + RepeatedCustomValueConverter(ConvertFunc convert_func) + : convert_func_(convert_func) {} + + virtual bool Convert(const base::Value& value, + ScopedVector<NestedType>* field) const OVERRIDE { + const base::ListValue* list = NULL; + if (!value.GetAsList(&list)) + return false; + + field->reserve(list->GetSize()); + for (size_t i = 0; i < list->GetSize(); ++i) { + const base::Value* element = NULL; + if (!list->Get(i, &element)) + continue; + + scoped_ptr<NestedType> nested(new NestedType); + if ((*convert_func_)(element, nested.get())) { + field->push_back(nested.release()); + } else { + DVLOG(1) << "failure at " << i << "-th element"; + return false; + } + } + return true; + } + + private: + ConvertFunc convert_func_; + DISALLOW_COPY_AND_ASSIGN(RepeatedCustomValueConverter); +}; + + +} // namespace internal + +template <class StructType> +class JSONValueConverter { + public: + JSONValueConverter() { + StructType::RegisterJSONConverter(this); + } + + void RegisterIntField(const std::string& field_name, + int StructType::* field) { + fields_.push_back(new internal::FieldConverter<StructType, int>( + field_name, field, new internal::BasicValueConverter<int>)); + } + + void RegisterStringField(const std::string& field_name, + std::string StructType::* field) { + fields_.push_back(new internal::FieldConverter<StructType, std::string>( + field_name, field, new internal::BasicValueConverter<std::string>)); + } + + void RegisterStringField(const std::string& field_name, + string16 StructType::* field) { + fields_.push_back(new internal::FieldConverter<StructType, string16>( + field_name, field, new internal::BasicValueConverter<string16>)); + } + + void RegisterBoolField(const std::string& field_name, + bool StructType::* field) { + fields_.push_back(new internal::FieldConverter<StructType, bool>( + field_name, field, new internal::BasicValueConverter<bool>)); + } + + void RegisterDoubleField(const std::string& field_name, + double StructType::* field) { + fields_.push_back(new internal::FieldConverter<StructType, double>( + field_name, field, new internal::BasicValueConverter<double>)); + } + + template <class NestedType> + void RegisterNestedField( + const std::string& field_name, NestedType StructType::* field) { + fields_.push_back(new internal::FieldConverter<StructType, NestedType>( + field_name, + field, + new internal::NestedValueConverter<NestedType>)); + } + + template <typename FieldType> + void RegisterCustomField( + const std::string& field_name, + FieldType StructType::* field, + bool (*convert_func)(const StringPiece&, FieldType*)) { + fields_.push_back(new internal::FieldConverter<StructType, FieldType>( + field_name, + field, + new internal::CustomFieldConverter<FieldType>(convert_func))); + } + + template <typename FieldType> + void RegisterCustomValueField( + const std::string& field_name, + FieldType StructType::* field, + bool (*convert_func)(const base::Value*, FieldType*)) { + fields_.push_back(new internal::FieldConverter<StructType, FieldType>( + field_name, + field, + new internal::ValueFieldConverter<FieldType>(convert_func))); + } + + void RegisterRepeatedInt(const std::string& field_name, + ScopedVector<int> StructType::* field) { + fields_.push_back( + new internal::FieldConverter<StructType, ScopedVector<int> >( + field_name, field, new internal::RepeatedValueConverter<int>)); + } + + void RegisterRepeatedString(const std::string& field_name, + ScopedVector<std::string> StructType::* field) { + fields_.push_back( + new internal::FieldConverter<StructType, ScopedVector<std::string> >( + field_name, + field, + new internal::RepeatedValueConverter<std::string>)); + } + + void RegisterRepeatedString(const std::string& field_name, + ScopedVector<string16> StructType::* field) { + fields_.push_back( + new internal::FieldConverter<StructType, ScopedVector<string16> >( + field_name, + field, + new internal::RepeatedValueConverter<string16>)); + } + + void RegisterRepeatedDouble(const std::string& field_name, + ScopedVector<double> StructType::* field) { + fields_.push_back( + new internal::FieldConverter<StructType, ScopedVector<double> >( + field_name, field, new internal::RepeatedValueConverter<double>)); + } + + void RegisterRepeatedBool(const std::string& field_name, + ScopedVector<bool> StructType::* field) { + fields_.push_back( + new internal::FieldConverter<StructType, ScopedVector<bool> >( + field_name, field, new internal::RepeatedValueConverter<bool>)); + } + + template <class NestedType> + void RegisterRepeatedCustomValue( + const std::string& field_name, + ScopedVector<NestedType> StructType::* field, + bool (*convert_func)(const base::Value*, NestedType*)) { + fields_.push_back( + new internal::FieldConverter<StructType, ScopedVector<NestedType> >( + field_name, + field, + new internal::RepeatedCustomValueConverter<NestedType>( + convert_func))); + } + + template <class NestedType> + void RegisterRepeatedMessage(const std::string& field_name, + ScopedVector<NestedType> StructType::* field) { + fields_.push_back( + new internal::FieldConverter<StructType, ScopedVector<NestedType> >( + field_name, + field, + new internal::RepeatedMessageConverter<NestedType>)); + } + + bool Convert(const base::Value& value, StructType* output) const { + const DictionaryValue* dictionary_value = NULL; + if (!value.GetAsDictionary(&dictionary_value)) + return false; + + for(size_t i = 0; i < fields_.size(); ++i) { + const internal::FieldConverterBase<StructType>* field_converter = + fields_[i]; + const base::Value* field = NULL; + if (dictionary_value->Get(field_converter->field_path(), &field)) { + if (!field_converter->ConvertField(*field, output)) { + DVLOG(1) << "failure at field " << field_converter->field_path(); + return false; + } + } + } + return true; + } + + private: + ScopedVector<internal::FieldConverterBase<StructType> > fields_; + + DISALLOW_COPY_AND_ASSIGN(JSONValueConverter); +}; + +} // namespace base + +#endif // BASE_JSON_JSON_VALUE_CONVERTER_H_
diff --git a/src/base/json/json_value_converter_unittest.cc b/src/base/json/json_value_converter_unittest.cc new file mode 100644 index 0000000..e5ad289 --- /dev/null +++ b/src/base/json/json_value_converter_unittest.cc
@@ -0,0 +1,256 @@ +// 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/json/json_value_converter.h" + +#include <string> +#include <vector> + +#include "base/values.h" +#include "base/json/json_reader.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/string_piece.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +// Very simple messages. +struct SimpleMessage { + enum SimpleEnum { + FOO, BAR, + }; + int foo; + std::string bar; + bool baz; + bool bstruct; + SimpleEnum simple_enum; + ScopedVector<int> ints; + ScopedVector<std::string> string_values; + SimpleMessage() : foo(0), baz(false), bstruct(false), simple_enum(FOO) {} + + static bool ParseSimpleEnum(const StringPiece& value, SimpleEnum* field) { + if (value == "foo") { + *field = FOO; + return true; + } else if (value == "bar") { + *field = BAR; + return true; + } + return false; + } + + static bool HasFieldPresent(const base::Value* value, bool* result) { + *result = value != NULL; + return true; + } + + static bool GetValueString(const base::Value* value, std::string* result) { + const base::DictionaryValue* dict = NULL; + if (!value->GetAsDictionary(&dict)) + return false; + + if (!dict->GetString("val", result)) + return false; + + return true; + } + + static void RegisterJSONConverter( + base::JSONValueConverter<SimpleMessage>* converter) { + converter->RegisterIntField("foo", &SimpleMessage::foo); + converter->RegisterStringField("bar", &SimpleMessage::bar); + converter->RegisterBoolField("baz", &SimpleMessage::baz); + converter->RegisterCustomField<SimpleEnum>( + "simple_enum", &SimpleMessage::simple_enum, &ParseSimpleEnum); + converter->RegisterRepeatedInt("ints", &SimpleMessage::ints); + converter->RegisterCustomValueField<bool>("bstruct", + &SimpleMessage::bstruct, + &HasFieldPresent); + converter->RegisterRepeatedCustomValue<std::string>( + "string_values", + &SimpleMessage::string_values, + &GetValueString); + } +}; + +// For nested messages. +struct NestedMessage { + double foo; + SimpleMessage child; + ScopedVector<SimpleMessage> children; + + NestedMessage() : foo(0) {} + + static void RegisterJSONConverter( + base::JSONValueConverter<NestedMessage>* converter) { + converter->RegisterDoubleField("foo", &NestedMessage::foo); + converter->RegisterNestedField("child", &NestedMessage::child); + converter->RegisterRepeatedMessage("children", &NestedMessage::children); + } +}; + +} // namespace + +TEST(JSONValueConverterTest, ParseSimpleMessage) { + const char normal_data[] = + "{\n" + " \"foo\": 1,\n" + " \"bar\": \"bar\",\n" + " \"baz\": true,\n" + " \"bstruct\": {},\n" + " \"string_values\": [{\"val\": \"value_1\"}, {\"val\": \"value_2\"}]," + " \"simple_enum\": \"foo\"," + " \"ints\": [1, 2]" + "}\n"; + + scoped_ptr<Value> value(base::JSONReader::Read(normal_data)); + SimpleMessage message; + base::JSONValueConverter<SimpleMessage> converter; + EXPECT_TRUE(converter.Convert(*value.get(), &message)); + + EXPECT_EQ(1, message.foo); + EXPECT_EQ("bar", message.bar); + EXPECT_TRUE(message.baz); + EXPECT_EQ(SimpleMessage::FOO, message.simple_enum); + EXPECT_EQ(2, static_cast<int>(message.ints.size())); + ASSERT_EQ(2U, message.string_values.size()); + EXPECT_EQ("value_1", *message.string_values[0]); + EXPECT_EQ("value_2", *message.string_values[1]); + EXPECT_EQ(1, *(message.ints[0])); + EXPECT_EQ(2, *(message.ints[1])); +} + +TEST(JSONValueConverterTest, ParseNestedMessage) { + const char normal_data[] = + "{\n" + " \"foo\": 1.0,\n" + " \"child\": {\n" + " \"foo\": 1,\n" + " \"bar\": \"bar\",\n" + " \"bstruct\": {},\n" + " \"string_values\": [{\"val\": \"value_1\"}, {\"val\": \"value_2\"}]," + " \"baz\": true\n" + " },\n" + " \"children\": [{\n" + " \"foo\": 2,\n" + " \"bar\": \"foobar\",\n" + " \"bstruct\": \"\",\n" + " \"string_values\": [{\"val\": \"value_1\"}]," + " \"baz\": true\n" + " },\n" + " {\n" + " \"foo\": 3,\n" + " \"bar\": \"barbaz\",\n" + " \"baz\": false\n" + " }]\n" + "}\n"; + + scoped_ptr<Value> value(base::JSONReader::Read(normal_data)); + NestedMessage message; + base::JSONValueConverter<NestedMessage> converter; + EXPECT_TRUE(converter.Convert(*value.get(), &message)); + + EXPECT_EQ(1.0, message.foo); + EXPECT_EQ(1, message.child.foo); + EXPECT_EQ("bar", message.child.bar); + EXPECT_TRUE(message.child.baz); + EXPECT_TRUE(message.child.bstruct); + ASSERT_EQ(2U, message.child.string_values.size()); + EXPECT_EQ("value_1", *message.child.string_values[0]); + EXPECT_EQ("value_2", *message.child.string_values[1]); + + EXPECT_EQ(2, static_cast<int>(message.children.size())); + const SimpleMessage* first_child = message.children[0]; + ASSERT_TRUE(first_child); + EXPECT_EQ(2, first_child->foo); + EXPECT_EQ("foobar", first_child->bar); + EXPECT_TRUE(first_child->baz); + EXPECT_TRUE(first_child->bstruct); + ASSERT_EQ(1U, first_child->string_values.size()); + EXPECT_EQ("value_1", *first_child->string_values[0]); + + const SimpleMessage* second_child = message.children[1]; + ASSERT_TRUE(second_child); + EXPECT_EQ(3, second_child->foo); + EXPECT_EQ("barbaz", second_child->bar); + EXPECT_FALSE(second_child->baz); + EXPECT_FALSE(second_child->bstruct); + EXPECT_EQ(0U, second_child->string_values.size()); +} + +TEST(JSONValueConverterTest, ParseFailures) { + const char normal_data[] = + "{\n" + " \"foo\": 1,\n" + " \"bar\": 2,\n" // "bar" is an integer here. + " \"baz\": true,\n" + " \"ints\": [1, 2]" + "}\n"; + + scoped_ptr<Value> value(base::JSONReader::Read(normal_data)); + SimpleMessage message; + base::JSONValueConverter<SimpleMessage> converter; + EXPECT_FALSE(converter.Convert(*value.get(), &message)); + // Do not check the values below. |message| may be modified during + // Convert() even it fails. +} + +TEST(JSONValueConverterTest, ParseWithMissingFields) { + const char normal_data[] = + "{\n" + " \"foo\": 1,\n" + " \"baz\": true,\n" + " \"ints\": [1, 2]" + "}\n"; + + scoped_ptr<Value> value(base::JSONReader::Read(normal_data)); + SimpleMessage message; + base::JSONValueConverter<SimpleMessage> converter; + // Convert() still succeeds even if the input doesn't have "bar" field. + EXPECT_TRUE(converter.Convert(*value.get(), &message)); + + EXPECT_EQ(1, message.foo); + EXPECT_TRUE(message.baz); + EXPECT_EQ(2, static_cast<int>(message.ints.size())); + EXPECT_EQ(1, *(message.ints[0])); + EXPECT_EQ(2, *(message.ints[1])); +} + +TEST(JSONValueConverterTest, EnumParserFails) { + const char normal_data[] = + "{\n" + " \"foo\": 1,\n" + " \"bar\": \"bar\",\n" + " \"baz\": true,\n" + " \"simple_enum\": \"baz\"," + " \"ints\": [1, 2]" + "}\n"; + + scoped_ptr<Value> value(base::JSONReader::Read(normal_data)); + SimpleMessage message; + base::JSONValueConverter<SimpleMessage> converter; + EXPECT_FALSE(converter.Convert(*value.get(), &message)); + // No check the values as mentioned above. +} + +TEST(JSONValueConverterTest, RepeatedValueErrorInTheMiddle) { + const char normal_data[] = + "{\n" + " \"foo\": 1,\n" + " \"bar\": \"bar\",\n" + " \"baz\": true,\n" + " \"simple_enum\": \"baz\"," + " \"ints\": [1, false]" + "}\n"; + + scoped_ptr<Value> value(base::JSONReader::Read(normal_data)); + SimpleMessage message; + base::JSONValueConverter<SimpleMessage> converter; + EXPECT_FALSE(converter.Convert(*value.get(), &message)); + // No check the values as mentioned above. +} + +} // namespace base
diff --git a/src/base/json/json_value_serializer_unittest.cc b/src/base/json/json_value_serializer_unittest.cc new file mode 100644 index 0000000..dc103dd --- /dev/null +++ b/src/base/json/json_value_serializer_unittest.cc
@@ -0,0 +1,156 @@ +// 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 <string> + +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/json/json_file_value_serializer.h" +#include "base/json/json_reader.h" +#include "base/json/json_string_value_serializer.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_util.h" +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +// Some proper JSON to test with: +const char kProperJSON[] = + "{\n" + " \"compound\": {\n" + " \"a\": 1,\n" + " \"b\": 2\n" + " },\n" + " \"some_String\": \"1337\",\n" + " \"some_int\": 42,\n" + " \"the_list\": [ \"val1\", \"val2\" ]\n" + "}\n"; + +// Some proper JSON with trailing commas: +const char kProperJSONWithCommas[] = + "{\n" + "\t\"some_int\": 42,\n" + "\t\"some_String\": \"1337\",\n" + "\t\"the_list\": [\"val1\", \"val2\", ],\n" + "\t\"compound\": { \"a\": 1, \"b\": 2, },\n" + "}\n"; + +const char kWinLineEnds[] = "\r\n"; +const char kLinuxLineEnds[] = "\n"; + +// Verifies the generated JSON against the expected output. +void CheckJSONIsStillTheSame(Value& value) { + // Serialize back the output. + std::string serialized_json; + JSONStringValueSerializer str_serializer(&serialized_json); + str_serializer.set_pretty_print(true); + ASSERT_TRUE(str_serializer.Serialize(value)); + // Unify line endings between platforms. + ReplaceSubstringsAfterOffset(&serialized_json, 0, + kWinLineEnds, kLinuxLineEnds); + // Now compare the input with the output. + ASSERT_EQ(kProperJSON, serialized_json); +} + +// Test proper JSON [de]serialization from string is working. +TEST(JSONValueSerializerTest, ReadProperJSONFromString) { + // Try to deserialize it through the serializer. + std::string proper_json(kProperJSON); + JSONStringValueSerializer str_deserializer(proper_json); + + int error_code = 0; + std::string error_message; + scoped_ptr<Value> value( + str_deserializer.Deserialize(&error_code, &error_message)); + ASSERT_TRUE(value.get()); + ASSERT_EQ(0, error_code); + ASSERT_TRUE(error_message.empty()); + // Verify if the same JSON is still there. + CheckJSONIsStillTheSame(*value); +} + +// Test that trialing commas are only properly deserialized from string when +// the proper flag for that is set. +TEST(JSONValueSerializerTest, ReadJSONWithTrailingCommasFromString) { + // Try to deserialize it through the serializer. + std::string proper_json(kProperJSONWithCommas); + JSONStringValueSerializer str_deserializer(proper_json); + + int error_code = 0; + std::string error_message; + scoped_ptr<Value> value( + str_deserializer.Deserialize(&error_code, &error_message)); + ASSERT_FALSE(value.get()); + ASSERT_NE(0, error_code); + ASSERT_FALSE(error_message.empty()); + // Now the flag is set and it must pass. + str_deserializer.set_allow_trailing_comma(true); + value.reset(str_deserializer.Deserialize(&error_code, &error_message)); + ASSERT_TRUE(value.get()); + ASSERT_EQ(JSONReader::JSON_TRAILING_COMMA, error_code); + // Verify if the same JSON is still there. + CheckJSONIsStillTheSame(*value); +} + +// Test proper JSON [de]serialization from file is working. +TEST(JSONValueSerializerTest, ReadProperJSONFromFile) { + ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + // Write it down in the file. + FilePath temp_file(tempdir.path().AppendASCII("test.json")); + ASSERT_EQ(static_cast<int>(strlen(kProperJSON)), + file_util::WriteFile(temp_file, kProperJSON, strlen(kProperJSON))); + + // Try to deserialize it through the serializer. + JSONFileValueSerializer file_deserializer(temp_file); + + int error_code = 0; + std::string error_message; + scoped_ptr<Value> value( + file_deserializer.Deserialize(&error_code, &error_message)); + ASSERT_TRUE(value.get()); + ASSERT_EQ(0, error_code); + ASSERT_TRUE(error_message.empty()); + // Verify if the same JSON is still there. + CheckJSONIsStillTheSame(*value); +} + +// Test that trialing commas are only properly deserialized from file when +// the proper flag for that is set. +TEST(JSONValueSerializerTest, ReadJSONWithCommasFromFile) { + ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + // Write it down in the file. + FilePath temp_file(tempdir.path().AppendASCII("test.json")); + ASSERT_EQ(static_cast<int>(strlen(kProperJSONWithCommas)), + file_util::WriteFile(temp_file, + kProperJSONWithCommas, + strlen(kProperJSONWithCommas))); + + // Try to deserialize it through the serializer. + JSONFileValueSerializer file_deserializer(temp_file); + // This must fail without the proper flag. + int error_code = 0; + std::string error_message; + scoped_ptr<Value> value( + file_deserializer.Deserialize(&error_code, &error_message)); + ASSERT_FALSE(value.get()); + ASSERT_NE(0, error_code); + ASSERT_FALSE(error_message.empty()); + // Now the flag is set and it must pass. + file_deserializer.set_allow_trailing_comma(true); + value.reset(file_deserializer.Deserialize(&error_code, &error_message)); + ASSERT_TRUE(value.get()); + ASSERT_EQ(JSONReader::JSON_TRAILING_COMMA, error_code); + // Verify if the same JSON is still there. + CheckJSONIsStillTheSame(*value); +} + +} // namespace + +} // namespace base +
diff --git a/src/base/json/json_writer.cc b/src/base/json/json_writer.cc new file mode 100644 index 0000000..e8cf9ac --- /dev/null +++ b/src/base/json/json_writer.cc
@@ -0,0 +1,236 @@ +// 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/json/json_writer.h" + +#include <cmath> + +#include "base/json/string_escape.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "base/string_number_conversions.h" +#include "base/values.h" +#include "base/utf_string_conversions.h" + +namespace base { + +#if defined(OS_WIN) +static const char kPrettyPrintLineEnding[] = "\r\n"; +#else +static const char kPrettyPrintLineEnding[] = "\n"; +#endif + +/* static */ +const char* JSONWriter::kEmptyArray = "[]"; + +/* static */ +void JSONWriter::Write(const Value* const node, std::string* json) { + WriteWithOptions(node, 0, json); +} + +/* 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, + pretty_print, json); + writer.BuildJSONString(node, 0); + + if (pretty_print) + json->append(kPrettyPrintLineEnding); +} + +JSONWriter::JSONWriter(bool escape, bool omit_binary_values, + bool omit_double_type_preservation, bool pretty_print, + std::string* json) + : escape_(escape), + omit_binary_values_(omit_binary_values), + omit_double_type_preservation_(omit_double_type_preservation), + pretty_print_(pretty_print), + json_string_(json) { + DCHECK(json); +} + +void JSONWriter::BuildJSONString(const Value* const node, int depth) { + switch (node->GetType()) { + case Value::TYPE_NULL: + json_string_->append("null"); + break; + + case Value::TYPE_BOOLEAN: + { + bool value; + bool result = node->GetAsBoolean(&value); + DCHECK(result); + json_string_->append(value ? "true" : "false"); + break; + } + + case Value::TYPE_INTEGER: + { + int value; + bool result = node->GetAsInteger(&value); + DCHECK(result); + base::StringAppendF(json_string_, "%d", value); + break; + } + + case Value::TYPE_DOUBLE: + { + double value; + bool result = node->GetAsDouble(&value); + DCHECK(result); + if (omit_double_type_preservation_ && + value <= kint64max && + value >= kint64min && + std::floor(value) == value) { + json_string_->append(Int64ToString(static_cast<int64>(value))); + break; + } + std::string real = DoubleToString(value); + // 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"); + } + json_string_->append(real); + break; + } + + case Value::TYPE_STRING: + { + std::string value; + bool result = node->GetAsString(&value); + DCHECK(result); + if (escape_) { + JsonDoubleQuote(UTF8ToUTF16(value), true, json_string_); + } else { + JsonDoubleQuote(value, true, json_string_); + } + break; + } + + case Value::TYPE_LIST: + { + json_string_->append("["); + if (pretty_print_) + json_string_->append(" "); + + const ListValue* list = static_cast<const ListValue*>(node); + for (size_t i = 0; i < list->GetSize(); ++i) { + const Value* value = NULL; + bool result = list->Get(i, &value); + DCHECK(result); + + if (omit_binary_values_ && value->GetType() == Value::TYPE_BINARY) { + continue; + } + + if (i != 0) { + json_string_->append(","); + if (pretty_print_) + json_string_->append(" "); + } + + BuildJSONString(value, depth); + } + + if (pretty_print_) + json_string_->append(" "); + json_string_->append("]"); + break; + } + + case Value::TYPE_DICTIONARY: + { + json_string_->append("{"); + if (pretty_print_) + json_string_->append(kPrettyPrintLineEnding); + + const DictionaryValue* dict = + static_cast<const DictionaryValue*>(node); + for (DictionaryValue::key_iterator key_itr = dict->begin_keys(); + key_itr != dict->end_keys(); + ++key_itr) { + const Value* value = NULL; + bool result = dict->GetWithoutPathExpansion(*key_itr, &value); + DCHECK(result); + + if (omit_binary_values_ && value->GetType() == Value::TYPE_BINARY) { + continue; + } + + if (key_itr != dict->begin_keys()) { + json_string_->append(","); + if (pretty_print_) + json_string_->append(kPrettyPrintLineEnding); + } + + if (pretty_print_) + IndentLine(depth + 1); + AppendQuotedString(*key_itr); + if (pretty_print_) { + json_string_->append(": "); + } else { + json_string_->append(":"); + } + BuildJSONString(value, depth + 1); + } + + if (pretty_print_) { + json_string_->append(kPrettyPrintLineEnding); + IndentLine(depth); + json_string_->append("}"); + } else { + json_string_->append("}"); + } + break; + } + + case Value::TYPE_BINARY: + { + if (!omit_binary_values_) { + NOTREACHED() << "Cannot serialize binary value."; + } + break; + } + + default: + NOTREACHED() << "unknown json type"; + } +} + +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. + json_string_->append(std::string(depth * 3, ' ')); +} + +} // namespace base
diff --git a/src/base/json/json_writer.h b/src/base/json/json_writer.h new file mode 100644 index 0000000..94052c8 --- /dev/null +++ b/src/base/json/json_writer.h
@@ -0,0 +1,83 @@ +// 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_JSON_JSON_WRITER_H_ +#define BASE_JSON_JSON_WRITER_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { + +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, + + // 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, + + // Return a slightly nicer formatted json string (pads with whitespace to + // help with readability). + OPTIONS_PRETTY_PRINT = 1 << 3 + }; + + // Given a root node, generates a JSON string and puts it into |json|. + // TODO(tc): Should we generate json if it would be invalid json (e.g., + // |node| is not a DictionaryValue/ListValue or if there are inf/-inf float + // values)? + static void Write(const Value* const node, std::string* json); + + // Same as above but with |options| which is a bunch of JSONWriter::Options + // bitwise ORed together. + 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, + bool omit_double_type_preservation, bool pretty_print, + std::string* json); + + // Called recursively to build the JSON string. Whe completed, value is + // 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_; + + // Where we write JSON data as we generate it. + std::string* json_string_; + + DISALLOW_COPY_AND_ASSIGN(JSONWriter); +}; + +} // namespace base + +#endif // BASE_JSON_JSON_WRITER_H_
diff --git a/src/base/json/json_writer_unittest.cc b/src/base/json/json_writer_unittest.cc new file mode 100644 index 0000000..7ddd7b4 --- /dev/null +++ b/src/base/json/json_writer_unittest.cc
@@ -0,0 +1,131 @@ +// 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/json/json_writer.h" +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(JSONWriterTest, Writing) { + // Test null + Value* root = Value::CreateNullValue(); + std::string output_js; + JSONWriter::Write(root, &output_js); + ASSERT_EQ("null", output_js); + delete root; + + // Test empty dict + root = new DictionaryValue; + JSONWriter::Write(root, &output_js); + ASSERT_EQ("{}", output_js); + delete root; + + // Test empty list + root = new ListValue; + JSONWriter::Write(root, &output_js); + ASSERT_EQ("[]", output_js); + delete root; + + // Test Real values should always have a decimal or an 'e'. + root = new FundamentalValue(1.0); + JSONWriter::Write(root, &output_js); + ASSERT_EQ("1.0", output_js); + delete root; + + // Test Real values in the the range (-1, 1) must have leading zeros + root = new FundamentalValue(0.2); + JSONWriter::Write(root, &output_js); + ASSERT_EQ("0.2", output_js); + delete root; + + // Test Real values in the the range (-1, 1) must have leading zeros + root = new FundamentalValue(-0.8); + JSONWriter::Write(root, &output_js); + ASSERT_EQ("-0.8", output_js); + delete root; + + // Writer unittests like empty list/dict nesting, + // list list nesting, etc. + DictionaryValue root_dict; + ListValue* list = new ListValue; + root_dict.Set("list", list); + DictionaryValue* inner_dict = new DictionaryValue; + list->Append(inner_dict); + inner_dict->SetInteger("inner int", 10); + ListValue* inner_list = new ListValue; + list->Append(inner_list); + list->Append(new FundamentalValue(true)); + + // Test the pretty-printer. + JSONWriter::Write(&root_dict, &output_js); + ASSERT_EQ("{\"list\":[{\"inner int\":10},[],true]}", output_js); + JSONWriter::WriteWithOptions(&root_dict, JSONWriter::OPTIONS_PRETTY_PRINT, + &output_js); + // The pretty-printer uses a different newline style on Windows than on + // other platforms. +#if defined(OS_WIN) +#define JSON_NEWLINE "\r\n" +#else +#define JSON_NEWLINE "\n" +#endif + ASSERT_EQ("{" JSON_NEWLINE + " \"list\": [ {" JSON_NEWLINE + " \"inner int\": 10" JSON_NEWLINE + " }, [ ], true ]" JSON_NEWLINE + "}" JSON_NEWLINE, + output_js); +#undef JSON_NEWLINE + + // Test keys with periods + DictionaryValue period_dict; + period_dict.SetWithoutPathExpansion("a.b", new FundamentalValue(3)); + period_dict.SetWithoutPathExpansion("c", new FundamentalValue(2)); + DictionaryValue* period_dict2 = new DictionaryValue; + period_dict2->SetWithoutPathExpansion("g.h.i.j", new FundamentalValue(1)); + period_dict.SetWithoutPathExpansion("d.e.f", period_dict2); + JSONWriter::Write(&period_dict, &output_js); + ASSERT_EQ("{\"a.b\":3,\"c\":2,\"d.e.f\":{\"g.h.i.j\":1}}", output_js); + + DictionaryValue period_dict3; + period_dict3.Set("a.b", new FundamentalValue(2)); + period_dict3.SetWithoutPathExpansion("a.b", new FundamentalValue(1)); + JSONWriter::Write(&period_dict3, &output_js); + ASSERT_EQ("{\"a\":{\"b\":2},\"a.b\":1}", output_js); + + // Test omitting binary values. + root = BinaryValue::CreateWithCopiedBuffer("asdf", 4); + JSONWriter::WriteWithOptions(root, JSONWriter::OPTIONS_OMIT_BINARY_VALUES, + &output_js); + ASSERT_TRUE(output_js.empty()); + delete root; + + ListValue binary_list; + binary_list.Append(new FundamentalValue(5)); + binary_list.Append(BinaryValue::CreateWithCopiedBuffer("asdf", 4)); + binary_list.Append(new FundamentalValue(2)); + JSONWriter::WriteWithOptions(&binary_list, + JSONWriter::OPTIONS_OMIT_BINARY_VALUES, + &output_js); + ASSERT_EQ("[5,2]", output_js); + + DictionaryValue binary_dict; + binary_dict.Set("a", new FundamentalValue(5)); + binary_dict.Set("b", BinaryValue::CreateWithCopiedBuffer("asdf", 4)); + binary_dict.Set("c", new FundamentalValue(2)); + JSONWriter::WriteWithOptions(&binary_dict, + JSONWriter::OPTIONS_OMIT_BINARY_VALUES, + &output_js); + ASSERT_EQ("{\"a\":5,\"c\":2}", output_js); + + // Test allowing a double with no fractional part to be written as an integer. + FundamentalValue double_value(1e10); + JSONWriter::WriteWithOptions( + &double_value, + JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION, + &output_js); + ASSERT_EQ("10000000000", output_js); +} + +} // namespace base
diff --git a/src/base/json/string_escape.cc b/src/base/json/string_escape.cc new file mode 100644 index 0000000..b4415b8 --- /dev/null +++ b/src/base/json/string_escape.cc
@@ -0,0 +1,105 @@ +// Copyright (c) 2006-2008 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 <string> + +#include "base/stringprintf.h" +#include "base/string_util.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) { + // 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) { + case '\b': + dst->append("\\b"); + break; + case '\f': + dst->append("\\f"); + break; + case '\n': + dst->append("\\n"); + break; + case '\r': + dst->append("\\r"); + break; + case '\t': + dst->append("\\t"); + break; + case '\\': + dst->append("\\\\"); + break; + case '"': + dst->append("\\\""); + break; + default: + return false; + } + return true; +} + +template <class STR> +void JsonDoubleQuoteT(const STR& str, + bool put_in_quotes, + std::string* dst) { + 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); + } + } + } + + if (put_in_quotes) + dst->push_back('"'); +} + +} // namespace + +void JsonDoubleQuote(const std::string& str, + bool put_in_quotes, + std::string* dst) { + JsonDoubleQuoteT(str, put_in_quotes, dst); +} + +std::string GetDoubleQuotedJson(const std::string& str) { + std::string dst; + JsonDoubleQuote(str, true, &dst); + return dst; +} + +void JsonDoubleQuote(const string16& str, + bool put_in_quotes, + std::string* dst) { + JsonDoubleQuoteT(str, put_in_quotes, dst); +} + +std::string GetDoubleQuotedJson(const string16& str) { + std::string dst; + JsonDoubleQuote(str, true, &dst); + return dst; +} + +} // namespace base
diff --git a/src/base/json/string_escape.h b/src/base/json/string_escape.h new file mode 100644 index 0000000..088db62 --- /dev/null +++ b/src/base/json/string_escape.h
@@ -0,0 +1,38 @@ +// 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. + +#ifndef BASE_JSON_STRING_ESCAPE_H_ +#define BASE_JSON_STRING_ESCAPE_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/string16.h" + +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 std::string& str, + bool put_in_quotes, + std::string* dst); + +// Same as above, but always returns the result double quoted. +BASE_EXPORT std::string GetDoubleQuotedJson(const std::string& str); + +BASE_EXPORT void JsonDoubleQuote(const string16& str, + bool put_in_quotes, + std::string* dst); + +// Same as above, but always returns the result double quoted. +BASE_EXPORT std::string GetDoubleQuotedJson(const string16& str); + +} // namespace base + +#endif // BASE_JSON_STRING_ESCAPE_H_
diff --git a/src/base/json/string_escape_unittest.cc b/src/base/json/string_escape_unittest.cc new file mode 100644 index 0000000..c550ca3 --- /dev/null +++ b/src/base/json/string_escape_unittest.cc
@@ -0,0 +1,100 @@ +// Copyright (c) 2006-2008 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/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) { + std::string in = json_narrow_cases[i].to_escape; + std::string out; + JsonDoubleQuote(in, false, &out); + EXPECT_EQ(std::string(json_narrow_cases[i].escaped), out); + } + + std::string in = json_narrow_cases[0].to_escape; + std::string out; + JsonDoubleQuote(in, false, &out); + + // test quoting + std::string out_quoted; + JsonDoubleQuote(in, true, &out_quoted); + EXPECT_EQ(out.length() + 2, out_quoted.length()); + EXPECT_EQ(out_quoted.find(out), 1U); + + // 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; + out.clear(); + JsonDoubleQuote(in, false, &out); + EXPECT_EQ(expected, 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"}, +}; + +} // 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); + } + + string16 in = WideToUTF16(json_wide_cases[0].to_escape); + std::string out; + JsonDoubleQuote(in, false, &out); + + // test quoting + std::string out_quoted; + JsonDoubleQuote(in, true, &out_quoted); + EXPECT_EQ(out.length() + 2, out_quoted.length()); + EXPECT_EQ(out_quoted.find(out), 1U); + + // 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; + out.clear(); + JsonDoubleQuote(in, false, &out); + EXPECT_EQ(expected, out); +} + +} // namespace base
diff --git a/src/base/lazy_instance.cc b/src/base/lazy_instance.cc new file mode 100644 index 0000000..a81cb8c --- /dev/null +++ b/src/base/lazy_instance.cc
@@ -0,0 +1,59 @@ +// 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/lazy_instance.h" + +#include "base/at_exit.h" +#include "base/atomicops.h" +#include "base/basictypes.h" +#include "base/threading/platform_thread.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" + +namespace base { +namespace internal { + +// TODO(joth): This function could be shared with Singleton, in place of its +// WaitForInstance() call. +bool NeedsLazyInstance(subtle::AtomicWord* state) { + // Try to create the instance, if we're the first, will go from 0 to + // kLazyInstanceStateCreating, otherwise we've already been beaten here. + // The memory access has no memory ordering as state 0 and + // kLazyInstanceStateCreating have no associated data (memory barriers are + // all about ordering of memory accesses to *associated* data). + if (subtle::NoBarrier_CompareAndSwap(state, 0, + kLazyInstanceStateCreating) == 0) + // Caller must create instance + return true; + + // It's either in the process of being created, or already created. Spin. + // The load has acquire memory ordering as a thread which sees + // state_ == STATE_CREATED needs to acquire visibility over + // the associated data (buf_). Pairing Release_Store is in + // CompleteLazyInstance(). + while (subtle::Acquire_Load(state) == kLazyInstanceStateCreating) { + PlatformThread::YieldCurrentThread(); + } + // Someone else created the instance. + return false; +} + +void CompleteLazyInstance(subtle::AtomicWord* state, + subtle::AtomicWord new_instance, + void* lazy_instance, + void (*dtor)(void*)) { + // See the comment to the corresponding HAPPENS_AFTER in Pointer(). + ANNOTATE_HAPPENS_BEFORE(state); + + // Instance is created, go from CREATING to CREATED. + // Releases visibility over private_buf_ to readers. Pairing Acquire_Load's + // are in NeedsInstance() and Pointer(). + subtle::Release_Store(state, new_instance); + + // Make sure that the lazily instantiated object will get destroyed at exit. + if (dtor) + AtExitManager::RegisterCallback(dtor, lazy_instance); +} + +} // namespace internal +} // namespace base
diff --git a/src/base/lazy_instance.h b/src/base/lazy_instance.h new file mode 100644 index 0000000..6e833bf --- /dev/null +++ b/src/base/lazy_instance.h
@@ -0,0 +1,209 @@ +// 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. + +// The LazyInstance<Type, Traits> class manages a single instance of Type, +// which will be lazily created on the first time it's accessed. This class is +// useful for places you would normally use a function-level static, but you +// need to have guaranteed thread-safety. The Type constructor will only ever +// be called once, even if two threads are racing to create the object. Get() +// and Pointer() will always return the same, completely initialized instance. +// When the instance is constructed it is registered with AtExitManager. The +// destructor will be called on program exit. +// +// LazyInstance is completely thread safe, assuming that you create it safely. +// The class was designed to be POD initialized, so it shouldn't require a +// static constructor. It really only makes sense to declare a LazyInstance as +// a global variable using the LAZY_INSTANCE_INITIALIZER initializer. +// +// LazyInstance is similar to Singleton, except it does not have the singleton +// property. You can have multiple LazyInstance's of the same type, and each +// will manage a unique instance. It also preallocates the space for Type, as +// to avoid allocating the Type instance on the heap. This may help with the +// performance of creating the instance, and reducing heap fragmentation. This +// requires that Type be a complete type so we can determine the size. +// +// Example usage: +// static LazyInstance<MyClass> my_instance = LAZY_INSTANCE_INITIALIZER; +// void SomeMethod() { +// my_instance.Get().SomeMethod(); // MyClass::SomeMethod() +// +// MyClass* ptr = my_instance.Pointer(); +// ptr->DoDoDo(); // MyClass::DoDoDo +// } + +#ifndef BASE_LAZY_INSTANCE_H_ +#define BASE_LAZY_INSTANCE_H_ + +#include <new> // For placement new. + +#include "base/atomicops.h" +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/aligned_memory.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/thread_restrictions.h" + +// LazyInstance uses its own struct initializer-list style static +// initialization, as base's LINKER_INITIALIZED requires a constructor and on +// some compilers (notably gcc 4.4) this still ends up needing runtime +// initialization. +#define LAZY_INSTANCE_INITIALIZER {0} + +namespace base { + +template <typename Type> +struct DefaultLazyInstanceTraits { + static const bool kRegisterOnExit = true; + static const bool kAllowedToAccessOnNonjoinableThread = false; + + static Type* New(void* instance) { + DCHECK_EQ(reinterpret_cast<uintptr_t>(instance) & (ALIGNOF(Type) - 1), 0u) + << ": Bad boy, the buffer passed to placement new is not aligned!\n" + "This may break some stuff like SSE-based optimizations assuming the " + "<Type> objects are word aligned."; + // Use placement new to initialize our instance in our preallocated space. + // The parenthesis is very important here to force POD type initialization. + return new (instance) Type(); + } + static void Delete(Type* instance) { + // Explicitly call the destructor. + instance->~Type(); + } +}; + +// We pull out some of the functionality into non-templated functions, so we +// can implement the more complicated pieces out of line in the .cc file. +namespace internal { + +// Use LazyInstance<T>::Leaky for a less-verbose call-site typedef; e.g.: +// base::LazyInstance<T>::Leaky my_leaky_lazy_instance; +// instead of: +// base::LazyInstance<T, base::internal::LeakyLazyInstanceTraits<T> > +// my_leaky_lazy_instance; +// (especially when T is MyLongTypeNameImplClientHolderFactory). +// Only use this internal::-qualified verbose form to extend this traits class +// (depending on its implementation details). +template <typename Type> +struct LeakyLazyInstanceTraits { + static const bool kRegisterOnExit = false; + static const bool kAllowedToAccessOnNonjoinableThread = true; + + static Type* New(void* instance) { + return DefaultLazyInstanceTraits<Type>::New(instance); + } + static void Delete(Type* /* instance */) {} +}; + +// Our AtomicWord doubles as a spinlock, where a value of +// kBeingCreatedMarker means the spinlock is being held for creation. +static const subtle::AtomicWord kLazyInstanceStateCreating = 1; + +// Check if instance needs to be created. If so return true otherwise +// if another thread has beat us, wait for instance to be created and +// return false. +BASE_EXPORT bool NeedsLazyInstance(subtle::AtomicWord* state); + +// After creating an instance, call this to register the dtor to be called +// at program exit and to update the atomic state to hold the |new_instance| +BASE_EXPORT void CompleteLazyInstance(subtle::AtomicWord* state, + subtle::AtomicWord new_instance, + void* lazy_instance, + void (*dtor)(void*)); + +} // namespace internal + +template <typename Type, typename Traits = DefaultLazyInstanceTraits<Type> > +class LazyInstance { + public: + // Do not define a destructor, as doing so makes LazyInstance a + // non-POD-struct. We don't want that because then a static initializer will + // be created to register the (empty) destructor with atexit() under MSVC, for + // example. We handle destruction of the contained Type class explicitly via + // the OnExit member function, where needed. + // ~LazyInstance() {} + + // Convenience typedef to avoid having to repeat Type for leaky lazy + // instances. + typedef LazyInstance<Type, internal::LeakyLazyInstanceTraits<Type> > Leaky; + + Type& Get() { + return *Pointer(); + } + + Type* Pointer() { +#ifndef NDEBUG + // Avoid making TLS lookup on release builds. + if (!Traits::kAllowedToAccessOnNonjoinableThread) + ThreadRestrictions::AssertSingletonAllowed(); +#endif + // If any bit in the created mask is true, the instance has already been + // fully constructed. + static const subtle::AtomicWord kLazyInstanceCreatedMask = + ~internal::kLazyInstanceStateCreating; + + // We will hopefully have fast access when the instance is already created. + // Since a thread sees private_instance_ == 0 or kLazyInstanceStateCreating + // at most once, the load is taken out of NeedsInstance() as a fast-path. + // The load has acquire memory ordering as a thread which sees + // private_instance_ > creating needs to acquire visibility over + // the associated data (private_buf_). Pairing Release_Store is in + // CompleteLazyInstance(). + subtle::AtomicWord value = subtle::Acquire_Load(&private_instance_); + if (!(value & kLazyInstanceCreatedMask) && + internal::NeedsLazyInstance(&private_instance_)) { + // Create the instance in the space provided by |private_buf_|. + value = reinterpret_cast<subtle::AtomicWord>( + Traits::New(private_buf_.void_data())); + internal::CompleteLazyInstance(&private_instance_, value, this, + Traits::kRegisterOnExit ? OnExit : NULL); + } + + // This annotation helps race detectors recognize correct lock-less + // synchronization between different threads calling Pointer(). + // We suggest dynamic race detection tool that "Traits::New" above + // and CompleteLazyInstance(...) happens before "return instance()" below. + // See the corresponding HAPPENS_BEFORE in CompleteLazyInstance(...). + ANNOTATE_HAPPENS_AFTER(&private_instance_); + return instance(); + } + + bool operator==(Type* p) { + switch (subtle::NoBarrier_Load(&private_instance_)) { + case 0: + return p == NULL; + case internal::kLazyInstanceStateCreating: + return static_cast<void*>(p) == private_buf_.void_data(); + default: + return p == instance(); + } + } + + // Effectively private: member data is only public to allow the linker to + // statically initialize it and to maintain a POD class. DO NOT USE FROM + // OUTSIDE THIS CLASS. + + subtle::AtomicWord private_instance_; + // Preallocated space for the Type instance. + base::AlignedMemory<sizeof(Type), ALIGNOF(Type)> private_buf_; + + private: + Type* instance() { + return reinterpret_cast<Type*>(subtle::NoBarrier_Load(&private_instance_)); + } + + // Adapter function for use with AtExit. This should be called single + // threaded, so don't synchronize across threads. + // Calling OnExit while the instance is in use by other threads is a mistake. + static void OnExit(void* lazy_instance) { + LazyInstance<Type, Traits>* me = + reinterpret_cast<LazyInstance<Type, Traits>*>(lazy_instance); + Traits::Delete(me->instance()); + subtle::NoBarrier_Store(&me->private_instance_, 0); + } +}; + +} // namespace base + +#endif // BASE_LAZY_INSTANCE_H_
diff --git a/src/base/lazy_instance_unittest.cc b/src/base/lazy_instance_unittest.cc new file mode 100644 index 0000000..e25366e --- /dev/null +++ b/src/base/lazy_instance_unittest.cc
@@ -0,0 +1,172 @@ +// 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/at_exit.h" +#include "base/atomic_sequence_num.h" +#include "base/lazy_instance.h" +#include "base/memory/aligned_memory.h" +#include "base/threading/simple_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +base::StaticAtomicSequenceNumber constructed_seq_; +base::StaticAtomicSequenceNumber destructed_seq_; + +class ConstructAndDestructLogger { + public: + ConstructAndDestructLogger() { + constructed_seq_.GetNext(); + } + ~ConstructAndDestructLogger() { + destructed_seq_.GetNext(); + } +}; + +class SlowConstructor { + public: + SlowConstructor() : some_int_(0) { + // Sleep for 1 second to try to cause a race. + base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); + ++constructed; + some_int_ = 12; + } + int some_int() const { return some_int_; } + + static int constructed; + private: + int some_int_; +}; + +int SlowConstructor::constructed = 0; + +class SlowDelegate : public base::DelegateSimpleThread::Delegate { + public: + explicit SlowDelegate(base::LazyInstance<SlowConstructor>* lazy) + : lazy_(lazy) {} + + virtual void Run() OVERRIDE { + EXPECT_EQ(12, lazy_->Get().some_int()); + EXPECT_EQ(12, lazy_->Pointer()->some_int()); + } + + private: + base::LazyInstance<SlowConstructor>* lazy_; +}; + +} // namespace + +static base::LazyInstance<ConstructAndDestructLogger> lazy_logger = + LAZY_INSTANCE_INITIALIZER; + +TEST(LazyInstanceTest, Basic) { + { + base::ShadowingAtExitManager shadow; + + EXPECT_EQ(0, constructed_seq_.GetNext()); + EXPECT_EQ(0, destructed_seq_.GetNext()); + + lazy_logger.Get(); + EXPECT_EQ(2, constructed_seq_.GetNext()); + EXPECT_EQ(1, destructed_seq_.GetNext()); + + lazy_logger.Pointer(); + EXPECT_EQ(3, constructed_seq_.GetNext()); + EXPECT_EQ(2, destructed_seq_.GetNext()); + } + EXPECT_EQ(4, constructed_seq_.GetNext()); + EXPECT_EQ(4, destructed_seq_.GetNext()); +} + +static base::LazyInstance<SlowConstructor> lazy_slow = + LAZY_INSTANCE_INITIALIZER; + +TEST(LazyInstanceTest, ConstructorThreadSafety) { + { + base::ShadowingAtExitManager shadow; + + SlowDelegate delegate(&lazy_slow); + EXPECT_EQ(0, SlowConstructor::constructed); + + base::DelegateSimpleThreadPool pool("lazy_instance_cons", 5); + pool.AddWork(&delegate, 20); + EXPECT_EQ(0, SlowConstructor::constructed); + + pool.Start(); + pool.JoinAll(); + EXPECT_EQ(1, SlowConstructor::constructed); + } +} + +namespace { + +// DeleteLogger is an object which sets a flag when it's destroyed. +// It accepts a bool* and sets the bool to true when the dtor runs. +class DeleteLogger { + public: + DeleteLogger() : deleted_(NULL) {} + ~DeleteLogger() { *deleted_ = true; } + + void SetDeletedPtr(bool* deleted) { + deleted_ = deleted; + } + + private: + bool* deleted_; +}; + +} // anonymous namespace + +TEST(LazyInstanceTest, LeakyLazyInstance) { + // Check that using a plain LazyInstance causes the dtor to run + // when the AtExitManager finishes. + bool deleted1 = false; + { + base::ShadowingAtExitManager shadow; + static base::LazyInstance<DeleteLogger> test = LAZY_INSTANCE_INITIALIZER; + test.Get().SetDeletedPtr(&deleted1); + } + EXPECT_TRUE(deleted1); + + // Check that using a *leaky* LazyInstance makes the dtor not run + // when the AtExitManager finishes. + bool deleted2 = false; + { + base::ShadowingAtExitManager shadow; + static base::LazyInstance<DeleteLogger>::Leaky + test = LAZY_INSTANCE_INITIALIZER; + test.Get().SetDeletedPtr(&deleted2); + } + EXPECT_FALSE(deleted2); +} + +namespace { + +template <size_t alignment> +class AlignedData { + public: + AlignedData() {} + ~AlignedData() {} + base::AlignedMemory<alignment, alignment> data_; +}; + +} // anonymous namespace + +#define EXPECT_ALIGNED(ptr, align) \ + EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(ptr) & (align - 1)) + +TEST(LazyInstanceTest, Alignment) { + using base::LazyInstance; + + // Create some static instances with increasing sizes and alignment + // requirements. By ordering this way, the linker will need to do some work to + // ensure proper alignment of the static data. + static LazyInstance<AlignedData<4> > align4 = LAZY_INSTANCE_INITIALIZER; + static LazyInstance<AlignedData<32> > align32 = LAZY_INSTANCE_INITIALIZER; + static LazyInstance<AlignedData<4096> > align4096 = LAZY_INSTANCE_INITIALIZER; + + EXPECT_ALIGNED(align4.Pointer(), 4); + EXPECT_ALIGNED(align32.Pointer(), 32); + EXPECT_ALIGNED(align4096.Pointer(), 4096); +}
diff --git a/src/base/linux_util.cc b/src/base/linux_util.cc new file mode 100644 index 0000000..49df773 --- /dev/null +++ b/src/base/linux_util.cc
@@ -0,0 +1,304 @@ +// 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/linux_util.h" + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <vector> + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/path_service.h" +#include "base/process_util.h" +#include "base/string_util.h" +#include "base/synchronization/lock.h" + +namespace { + +// Not needed for OS_CHROMEOS. +#if defined(OS_LINUX) +enum LinuxDistroState { + STATE_DID_NOT_CHECK = 0, + STATE_CHECK_STARTED = 1, + STATE_CHECK_FINISHED = 2, +}; + +// Helper class for GetLinuxDistro(). +class LinuxDistroHelper { + public: + // Retrieves the Singleton. + static LinuxDistroHelper* GetInstance() { + return Singleton<LinuxDistroHelper>::get(); + } + + // The simple state machine goes from: + // STATE_DID_NOT_CHECK -> STATE_CHECK_STARTED -> STATE_CHECK_FINISHED. + LinuxDistroHelper() : state_(STATE_DID_NOT_CHECK) {} + ~LinuxDistroHelper() {} + + // Retrieve the current state, if we're in STATE_DID_NOT_CHECK, + // we automatically move to STATE_CHECK_STARTED so nobody else will + // do the check. + LinuxDistroState State() { + base::AutoLock scoped_lock(lock_); + if (STATE_DID_NOT_CHECK == state_) { + state_ = STATE_CHECK_STARTED; + return STATE_DID_NOT_CHECK; + } + return state_; + } + + // Indicate the check finished, move to STATE_CHECK_FINISHED. + void CheckFinished() { + base::AutoLock scoped_lock(lock_); + DCHECK_EQ(STATE_CHECK_STARTED, state_); + state_ = STATE_CHECK_FINISHED; + } + + private: + base::Lock lock_; + LinuxDistroState state_; +}; +#endif // if defined(OS_LINUX) + +// expected prefix of the target of the /proc/self/fd/%d link for a socket +const char kSocketLinkPrefix[] = "socket:["; + +// Parse a symlink in /proc/pid/fd/$x and return the inode number of the +// socket. +// inode_out: (output) set to the inode number on success +// path: e.g. /proc/1234/fd/5 (must be a UNIX domain socket descriptor) +// log: if true, log messages about failure details +bool ProcPathGetInode(ino_t* inode_out, const char* path, bool log = false) { + DCHECK(inode_out); + DCHECK(path); + + char buf[256]; + const ssize_t n = readlink(path, buf, sizeof(buf) - 1); + if (n == -1) { + if (log) { + DLOG(WARNING) << "Failed to read the inode number for a socket from /proc" + "(" << errno << ")"; + } + return false; + } + buf[n] = 0; + + if (memcmp(kSocketLinkPrefix, buf, sizeof(kSocketLinkPrefix) - 1)) { + if (log) { + DLOG(WARNING) << "The descriptor passed from the crashing process wasn't " + " a UNIX domain socket."; + } + return false; + } + + char* endptr; + const unsigned long long int inode_ul = + strtoull(buf + sizeof(kSocketLinkPrefix) - 1, &endptr, 10); + if (*endptr != ']') + return false; + + if (inode_ul == ULLONG_MAX) { + if (log) { + DLOG(WARNING) << "Failed to parse a socket's inode number: the number " + "was too large. Please report this bug: " << buf; + } + return false; + } + + *inode_out = inode_ul; + return true; +} + +} // namespace + +namespace base { + +const char kFindInodeSwitch[] = "--find-inode"; + +// Account for the terminating null character. +static const int kDistroSize = 128 + 1; + +// We use this static string to hold the Linux distro info. If we +// crash, the crash handler code will send this in the crash dump. +char g_linux_distro[kDistroSize] = +#if defined(OS_CHROMEOS) + "CrOS"; +#elif defined(OS_ANDROID) + "Android"; +#else // if defined(OS_LINUX) + "Unknown"; +#endif + +std::string GetLinuxDistro() { +#if defined(OS_CHROMEOS) || defined(OS_ANDROID) + return g_linux_distro; +#elif defined(OS_LINUX) + LinuxDistroHelper* distro_state_singleton = LinuxDistroHelper::GetInstance(); + LinuxDistroState state = distro_state_singleton->State(); + if (STATE_CHECK_FINISHED == state) + return g_linux_distro; + if (STATE_CHECK_STARTED == state) + return "Unknown"; // Don't wait for other thread to finish. + DCHECK_EQ(state, STATE_DID_NOT_CHECK); + // We do this check only once per process. If it fails, there's + // little reason to believe it will work if we attempt to run + // lsb_release again. + std::vector<std::string> argv; + argv.push_back("lsb_release"); + argv.push_back("-d"); + std::string output; + base::GetAppOutput(CommandLine(argv), &output); + if (output.length() > 0) { + // lsb_release -d should return: Description:<tab>Distro Info + const char field[] = "Description:\t"; + if (output.compare(0, strlen(field), field) == 0) { + SetLinuxDistro(output.substr(strlen(field))); + } + } + distro_state_singleton->CheckFinished(); + return g_linux_distro; +#else + NOTIMPLEMENTED(); + return "Unknown"; +#endif +} + +void SetLinuxDistro(const std::string& distro) { + std::string trimmed_distro; + TrimWhitespaceASCII(distro, TRIM_ALL, &trimmed_distro); + base::strlcpy(g_linux_distro, trimmed_distro.c_str(), kDistroSize); +} + +bool FileDescriptorGetInode(ino_t* inode_out, int fd) { + DCHECK(inode_out); + + struct stat buf; + if (fstat(fd, &buf) < 0) + return false; + + if (!S_ISSOCK(buf.st_mode)) + return false; + + *inode_out = buf.st_ino; + return true; +} + +bool FindProcessHoldingSocket(pid_t* pid_out, ino_t socket_inode) { + DCHECK(pid_out); + bool already_found = false; + + DIR* proc = opendir("/proc"); + if (!proc) { + DLOG(WARNING) << "Cannot open /proc"; + return false; + } + + std::vector<pid_t> pids; + + struct dirent* dent; + while ((dent = readdir(proc))) { + char* endptr; + const unsigned long int pid_ul = strtoul(dent->d_name, &endptr, 10); + if (pid_ul == ULONG_MAX || *endptr) + continue; + pids.push_back(pid_ul); + } + closedir(proc); + + for (std::vector<pid_t>::const_iterator + i = pids.begin(); i != pids.end(); ++i) { + const pid_t current_pid = *i; + char buf[256]; + snprintf(buf, sizeof(buf), "/proc/%d/fd", current_pid); + DIR* fd = opendir(buf); + if (!fd) + continue; + + while ((dent = readdir(fd))) { + if (snprintf(buf, sizeof(buf), "/proc/%d/fd/%s", current_pid, + dent->d_name) >= static_cast<int>(sizeof(buf))) { + continue; + } + + ino_t fd_inode; + if (ProcPathGetInode(&fd_inode, buf)) { + if (fd_inode == socket_inode) { + if (already_found) { + closedir(fd); + return false; + } + + already_found = true; + *pid_out = current_pid; + break; + } + } + } + + closedir(fd); + } + + return already_found; +} + +pid_t FindThreadIDWithSyscall(pid_t pid, const std::string& expected_data, + bool* syscall_supported) { + char buf[256]; + snprintf(buf, sizeof(buf), "/proc/%d/task", pid); + + if (syscall_supported != NULL) + *syscall_supported = false; + + DIR* task = opendir(buf); + if (!task) { + DLOG(WARNING) << "Cannot open " << buf; + return -1; + } + + std::vector<pid_t> tids; + struct dirent* dent; + while ((dent = readdir(task))) { + char* endptr; + const unsigned long int tid_ul = strtoul(dent->d_name, &endptr, 10); + if (tid_ul == ULONG_MAX || *endptr) + continue; + tids.push_back(tid_ul); + } + closedir(task); + + scoped_array<char> syscall_data(new char[expected_data.length()]); + for (std::vector<pid_t>::const_iterator + i = tids.begin(); i != tids.end(); ++i) { + const pid_t current_tid = *i; + snprintf(buf, sizeof(buf), "/proc/%d/task/%d/syscall", pid, current_tid); + int fd = open(buf, O_RDONLY); + if (fd < 0) + continue; + if (syscall_supported != NULL) + *syscall_supported = true; + bool read_ret = + file_util::ReadFromFD(fd, syscall_data.get(), expected_data.length()); + close(fd); + if (!read_ret) + continue; + + if (0 == strncmp(expected_data.c_str(), syscall_data.get(), + expected_data.length())) { + return current_tid; + } + } + return -1; +} + +} // namespace base
diff --git a/src/base/linux_util.h b/src/base/linux_util.h new file mode 100644 index 0000000..b9ba56d --- /dev/null +++ b/src/base/linux_util.h
@@ -0,0 +1,47 @@ +// 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_LINUX_UTIL_H_ +#define BASE_LINUX_UTIL_H_ + +#include <stdint.h> +#include <sys/types.h> + +#include <string> + +#include "base/base_export.h" + +namespace base { + +BASE_EXPORT extern const char kFindInodeSwitch[]; + +// This is declared here so the crash reporter can access the memory directly +// in compromised context without going through the standard library. +BASE_EXPORT extern char g_linux_distro[]; + +// Get the Linux Distro if we can, or return "Unknown". +BASE_EXPORT std::string GetLinuxDistro(); + +// Set the Linux Distro string. +BASE_EXPORT void SetLinuxDistro(const std::string& distro); + +// Return the inode number for the UNIX domain socket |fd|. +BASE_EXPORT bool FileDescriptorGetInode(ino_t* inode_out, int fd); + +// Find the process which holds the given socket, named by inode number. If +// multiple processes hold the socket, this function returns false. +BASE_EXPORT bool FindProcessHoldingSocket(pid_t* pid_out, ino_t socket_inode); + +// For a given process |pid|, look through all its threads and find the first +// thread with /proc/[pid]/task/[thread_id]/syscall whose first N bytes matches +// |expected_data|, where N is the length of |expected_data|. +// Returns the thread id or -1 on error. If |syscall_supported| is +// set to false the kernel does not support syscall in procfs. +BASE_EXPORT pid_t FindThreadIDWithSyscall(pid_t pid, + const std::string& expected_data, + bool* syscall_supported); + +} // namespace base + +#endif // BASE_LINUX_UTIL_H_
diff --git a/src/base/location.cc b/src/base/location.cc new file mode 100644 index 0000000..b6ded2c --- /dev/null +++ b/src/base/location.cc
@@ -0,0 +1,102 @@ +// 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 "build/build_config.h" + +#if defined(COMPILER_MSVC) +// MSDN says to #include <intrin.h>, but that breaks the VS2005 build. +extern "C" { + void* _ReturnAddress(); +} +#endif + +#include "base/location.h" +#include "base/string_number_conversions.h" +#include "base/stringprintf.h" + +namespace tracked_objects { + +Location::Location(const char* function_name, + const char* file_name, + int line_number, + const void* program_counter) + : function_name_(function_name), + file_name_(file_name), + line_number_(line_number), + program_counter_(program_counter) { +} + +Location::Location() + : function_name_("Unknown"), + file_name_("Unknown"), + line_number_(-1), + program_counter_(NULL) { +} + +std::string Location::ToString() const { + return std::string(function_name_) + "@" + file_name_ + ":" + + base::IntToString(line_number_); +} + +void Location::Write(bool display_filename, bool display_function_name, + std::string* output) const { + base::StringAppendF(output, "%s[%d] ", + display_filename ? file_name_ : "line", + line_number_); + + if (display_function_name) { + WriteFunctionName(output); + output->push_back(' '); + } +} + +void Location::WriteFunctionName(std::string* output) const { + // Translate "<" to "<" for HTML safety. + // TODO(jar): Support ASCII or html for logging in ASCII. + for (const char *p = function_name_; *p; p++) { + switch (*p) { + case '<': + output->append("<"); + break; + + case '>': + output->append(">"); + break; + + default: + output->push_back(*p); + break; + } + } +} + +//------------------------------------------------------------------------------ +LocationSnapshot::LocationSnapshot() : line_number(-1) { +} + +LocationSnapshot::LocationSnapshot( + const tracked_objects::Location& location) + : file_name(location.file_name()), + function_name(location.function_name()), + line_number(location.line_number()) { +} + +LocationSnapshot::~LocationSnapshot() { +} + +//------------------------------------------------------------------------------ +#if defined(COMPILER_MSVC) +__declspec(noinline) +#endif +BASE_EXPORT const void* GetProgramCounter() { +#if defined(COMPILER_MSVC) + return _ReturnAddress(); +#elif defined(COMPILER_GCC) && !defined(__LB_PS3__) && !defined(__LB_WIIU__) + return __builtin_extract_return_addr(__builtin_return_address(0)); +#endif // COMPILER_GCC + + return NULL; +} + +} // namespace tracked_objects
diff --git a/src/base/location.h b/src/base/location.h new file mode 100644 index 0000000..05a4f66 --- /dev/null +++ b/src/base/location.h
@@ -0,0 +1,94 @@ +// 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_LOCATION_H_ +#define BASE_LOCATION_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace tracked_objects { + +// Location provides basic info where of an object was constructed, or was +// significantly brought to life. +class BASE_EXPORT Location { + public: + // Constructor should be called with a long-lived char*, such as __FILE__. + // It assumes the provided value will persist as a global constant, and it + // will not make a copy of it. + Location(const char* function_name, + const char* file_name, + int line_number, + const void* program_counter); + + // Provide a default constructor for easy of debugging. + Location(); + + // Comparison operator for insertion into a std::map<> hash tables. + // All we need is *some* (any) hashing distinction. Strings should already + // be unique, so we don't bother with strcmp or such. + // Use line number as the primary key (because it is fast, and usually gets us + // a difference), and then pointers as secondary keys (just to get some + // distinctions). + bool operator < (const Location& other) const { + if (line_number_ != other.line_number_) + return line_number_ < other.line_number_; + if (file_name_ != other.file_name_) + return file_name_ < other.file_name_; + return function_name_ < other.function_name_; + } + + const char* function_name() const { return function_name_; } + const char* file_name() const { return file_name_; } + int line_number() const { return line_number_; } + const void* program_counter() const { return program_counter_; } + + std::string ToString() const; + + // Translate the some of the state in this instance into a human readable + // string with HTML characters in the function names escaped, and append that + // string to |output|. Inclusion of the file_name_ and function_name_ are + // optional, and controlled by the boolean arguments. + void Write(bool display_filename, bool display_function_name, + std::string* output) const; + + // Write function_name_ in HTML with '<' and '>' properly encoded. + void WriteFunctionName(std::string* output) const; + + private: + const char* function_name_; + const char* file_name_; + int line_number_; + const void* program_counter_; +}; + +// A "snapshotted" representation of the Location class that can safely be +// passed across process boundaries. +struct BASE_EXPORT LocationSnapshot { + // The default constructor is exposed to support the IPC serialization macros. + LocationSnapshot(); + explicit LocationSnapshot(const tracked_objects::Location& location); + ~LocationSnapshot(); + + std::string file_name; + std::string function_name; + int line_number; +}; + +BASE_EXPORT const void* GetProgramCounter(); + +// Define a macro to record the current source location. +#define FROM_HERE FROM_HERE_WITH_EXPLICIT_FUNCTION(__FUNCTION__) + +#define FROM_HERE_WITH_EXPLICIT_FUNCTION(function_name) \ + ::tracked_objects::Location(function_name, \ + __FILE__, \ + __LINE__, \ + ::tracked_objects::GetProgramCounter()) + +} // namespace tracked_objects + +#endif // BASE_LOCATION_H_
diff --git a/src/base/logging.cc b/src/base/logging.cc new file mode 100644 index 0000000..6a135ea --- /dev/null +++ b/src/base/logging.cc
@@ -0,0 +1,995 @@ +// 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/logging.h" + +#if defined(OS_WIN) +#include <io.h> +#include <windows.h> +typedef HANDLE FileHandle; +typedef HANDLE MutexHandle; +// Windows warns on using write(). It prefers _write(). +#define write(fd, buf, count) _write(fd, buf, static_cast<unsigned int>(count)) +// Windows doesn't define STDERR_FILENO. Define it here. +#define STDERR_FILENO 2 +#elif defined(OS_MACOSX) +#include <mach/mach.h> +#include <mach/mach_time.h> +#include <mach-o/dyld.h> +#elif defined(OS_POSIX) +#if defined(OS_NACL) +#include <sys/time.h> // timespec doesn't seem to be in <time.h> +#else +#include <sys/syscall.h> +#endif +#include <time.h> +#elif defined(OS_STARBOARD) +#include "starboard/client_porting/eztime/eztime.h" +#include "starboard/file.h" +#include "starboard/log.h" +#include "starboard/mutex.h" +#include "starboard/system.h" +#include "starboard/time.h" +typedef SbFile FileHandle; +typedef SbMutex MutexHandle; +#endif + +#if defined(__LB_SHELL__) +// To implement TickCount. +#include "lb_platform.h" +#endif + +#if defined(OS_POSIX) +#include <errno.h> +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#define MAX_PATH PATH_MAX +typedef FILE* FileHandle; +typedef pthread_mutex_t* MutexHandle; +#endif + +#include <algorithm> +#include <cstring> +#include <ctime> +#include <iomanip> +#include <ostream> + +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/debug/alias.h" +#include "base/debug/debugger.h" +#include "base/debug/stack_trace.h" +#include "base/posix/eintr_wrapper.h" +#include "base/string_piece.h" +#include "base/synchronization/lock_impl.h" +#include "base/threading/platform_thread.h" +#include "base/utf_string_conversions.h" +#include "base/vlog.h" +#if defined(OS_POSIX) +#include "base/safe_strerror_posix.h" +#endif + +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) +#include <android/log.h> +#endif + +#if defined(__LB_XB1__) || defined(__LB_XB360__) +#undef MAX_PATH +#include <Windows.h> +#undef MAX_PATH +#endif + +namespace logging { + +DcheckState g_dcheck_state = DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS; + +namespace { + +VlogInfo* g_vlog_info = NULL; +VlogInfo* g_vlog_info_prev = NULL; + +const char* const log_severity_names[LOG_NUM_SEVERITIES] = { + "INFO", "WARNING", "ERROR", "ERROR_REPORT", "FATAL" }; + +int min_log_level = 0; + +// The default set here for logging_destination will only be used if +// InitLogging is not called. On Windows, use a file next to the exe; +// on POSIX platforms, where it may not even be possible to locate the +// executable on disk, use stderr. +#if defined(OS_WIN) +LoggingDestination logging_destination = LOG_ONLY_TO_FILE; +#elif defined(OS_POSIX) +LoggingDestination logging_destination = LOG_ONLY_TO_SYSTEM_DEBUG_LOG; +#elif defined(OS_STARBOARD) +LoggingDestination logging_destination = LOG_ONLY_TO_SYSTEM_DEBUG_LOG; +#endif + +// For LOG_ERROR and above, always print to stderr. +const int kAlwaysPrintErrorLevel = LOG_ERROR; + +// Which log file to use? This is initialized by InitLogging or +// will be lazily initialized to the default value when it is +// first needed. +#if defined(OS_WIN) +typedef std::wstring PathString; +#else +typedef std::string PathString; +#endif +PathString* log_file_name = NULL; + +// this file is lazily opened and the handle may be NULL +FileHandle log_file = NULL; + +// what should be prepended to each message? +bool log_process_id = false; +bool log_thread_id = false; +bool log_timestamp = true; +bool log_tickcount = false; + +// Should we pop up fatal debug messages in a dialog? +bool show_error_dialogs = false; + +// An assert handler override specified by the client to be called instead of +// the debug message dialog and process termination. +LogAssertHandlerFunction log_assert_handler = NULL; +// An report handler override specified by the client to be called instead of +// the debug message dialog. +LogReportHandlerFunction log_report_handler = NULL; +// A log message handler that gets notified of every log message we process. +LogMessageHandlerFunction log_message_handler = NULL; + +// Helper functions to wrap platform differences. + +int32 CurrentProcessId() { +#if defined(OS_WIN) + return GetCurrentProcessId(); +#elif defined(__LB_SHELL__) || defined(OS_STARBOARD) + // This should never be reached, because logging PIDs should never + // be enabled for LB shell. + return 0; +#elif defined(OS_POSIX) + return getpid(); +#endif +} + +uint64 TickCount() { +#if defined(OS_WIN) + return GetTickCount(); +#elif defined(OS_MACOSX) + return mach_absolute_time(); +#elif defined(OS_NACL) + // NaCl sadly does not have _POSIX_TIMERS enabled in sys/features.h + // So we have to use clock() for now. + return clock(); +#elif defined(OS_STARBOARD) + return static_cast<uint64>(SbTimeGetMonotonicNow()); +#elif defined(__LB_SHELL__) + return LB::Platform::TickCount(); +#elif defined(OS_POSIX) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + + uint64 absolute_micro = + static_cast<int64>(ts.tv_sec) * 1000000 + + static_cast<int64>(ts.tv_nsec) / 1000; + + return absolute_micro; +#endif +} + +void CloseFile(FileHandle log) { +#if defined(OS_WIN) + CloseHandle(log); +#elif defined(OS_STARBOARD) + SbFileClose(log); +#else + fclose(log); +#endif +} + +void DeleteFilePath(const PathString& log_name) { +#if defined(OS_WIN) + DeleteFile(log_name.c_str()); +#elif defined(OS_STARBOARD) + SbFileDelete(log_name.c_str()); +#else + unlink(log_name.c_str()); +#endif +} + +PathString GetDefaultLogFile() { +#if defined(OS_WIN) + // On Windows we use the same path as the exe. + wchar_t module_name[MAX_PATH]; + GetModuleFileName(NULL, module_name, MAX_PATH); + + PathString log_file = module_name; + PathString::size_type last_backslash = + log_file.rfind('\\', log_file.size()); + if (last_backslash != PathString::npos) + log_file.erase(last_backslash + 1); + log_file += L"debug.log"; + return log_file; +#elif defined(OS_STARBOARD) + // On Starboard, we politely ask for the log directory, like a civilized + // platform. + char path[SB_FILE_MAX_PATH + 1]; + SbSystemGetPath(kSbSystemPathDebugOutputDirectory, path, + SB_ARRAY_SIZE_INT(path)); + PathString log_file = path; + log_file += SB_FILE_SEP_STRING "debug.log"; + return log_file; +#elif defined(OS_POSIX) + // On other platforms we just use the current directory. + return PathString("debug.log"); +#endif +} + +// This class acts as a wrapper for locking the logging files. +// LoggingLock::Init() should be called from the main thread before any logging +// is done. Then whenever logging, be sure to have a local LoggingLock +// instance on the stack. This will ensure that the lock is unlocked upon +// exiting the frame. +// LoggingLocks can not be nested. +class LoggingLock { + public: + LoggingLock() { + LockLogging(); + } + + ~LoggingLock() { + UnlockLogging(); + } + + static void Init(LogLockingState lock_log, const PathChar* new_log_file) { + if (initialized) + return; + lock_log_file = lock_log; + if (lock_log_file == LOCK_LOG_FILE) { +#if defined(OS_WIN) + if (!log_mutex) { + std::wstring safe_name; + if (new_log_file) + safe_name = new_log_file; + else + safe_name = GetDefaultLogFile(); + // \ is not a legal character in mutex names so we replace \ with / + std::replace(safe_name.begin(), safe_name.end(), '\\', '/'); + std::wstring t(L"Global\\"); + t.append(safe_name); + log_mutex = ::CreateMutex(NULL, FALSE, t.c_str()); + + if (log_mutex == NULL) { +#if DEBUG + // Keep the error code for debugging + int error = GetLastError(); // NOLINT + base::debug::BreakDebugger(); +#endif + // Return nicely without putting initialized to true. + return; + } + } +#endif + } else { + log_lock = new base::internal::LockImpl(); + } + initialized = true; + } + + private: + static void LockLogging() { + if (lock_log_file == LOCK_LOG_FILE) { +#if defined(OS_WIN) + ::WaitForSingleObject(log_mutex, INFINITE); + // WaitForSingleObject could have returned WAIT_ABANDONED. We don't + // abort the process here. UI tests might be crashy sometimes, + // and aborting the test binary only makes the problem worse. + // We also don't use LOG macros because that might lead to an infinite + // loop. For more info see http://crbug.com/18028. +#elif defined(OS_POSIX) + pthread_mutex_lock(&log_mutex); +#elif defined(OS_STARBOARD) + SbMutexAcquire(&log_mutex); +#endif + } else { + // use the lock + log_lock->Lock(); + } + } + + static void UnlockLogging() { + if (lock_log_file == LOCK_LOG_FILE) { +#if defined(OS_WIN) + ReleaseMutex(log_mutex); +#elif defined(OS_POSIX) + pthread_mutex_unlock(&log_mutex); +#elif defined(OS_STARBOARD) + SbMutexRelease(&log_mutex); +#endif + } else { + log_lock->Unlock(); + } + } + + // The lock is used if log file locking is false. It helps us avoid problems + // with multiple threads writing to the log file at the same time. Use + // LockImpl directly instead of using Lock, because Lock makes logging calls. + static base::internal::LockImpl* log_lock; + + // When we don't use a lock, we are using a global mutex. We need to do this + // because LockFileEx is not thread safe. +#if defined(OS_WIN) + static MutexHandle log_mutex; +#elif defined(OS_POSIX) + static pthread_mutex_t log_mutex; +#elif defined(OS_STARBOARD) + static SbMutex log_mutex; +#endif + + static bool initialized; + static LogLockingState lock_log_file; +}; + +// static +bool LoggingLock::initialized = false; +// static +base::internal::LockImpl* LoggingLock::log_lock = NULL; +// static +LogLockingState LoggingLock::lock_log_file = LOCK_LOG_FILE; + +#if defined(OS_WIN) +// static +MutexHandle LoggingLock::log_mutex = NULL; +#elif defined(OS_POSIX) +pthread_mutex_t LoggingLock::log_mutex = PTHREAD_MUTEX_INITIALIZER; +#elif defined(OS_STARBOARD) +SbMutex LoggingLock::log_mutex = SB_MUTEX_INITIALIZER; +#endif + +// Called by logging functions to ensure that debug_file is initialized +// and can be used for writing. Returns false if the file could not be +// initialized. debug_file will be NULL in this case. +bool InitializeLogFileHandle() { + if (log_file) + return true; + + if (!log_file_name) { + // Nobody has called InitLogging to specify a debug log file, so here we + // initialize the log file name to a default. + log_file_name = new PathString(GetDefaultLogFile()); + } + +#if defined(COBALT) + // This seems to get called a lot with an empty filename, at least in + // base_unittests. + if (log_file_name->empty()) { + return false; + } +#endif + + if (logging_destination == LOG_ONLY_TO_FILE || + logging_destination == LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG) { +#if defined(OS_WIN) + log_file = CreateFile(log_file_name->c_str(), GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (log_file == INVALID_HANDLE_VALUE || log_file == NULL) { + // try the current directory + log_file = CreateFile(L".\\debug.log", GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (log_file == INVALID_HANDLE_VALUE || log_file == NULL) { + log_file = NULL; + return false; + } + } + SetFilePointer(log_file, 0, 0, FILE_END); +#elif defined(OS_POSIX) + log_file = fopen(log_file_name->c_str(), "a"); + if (log_file == NULL) + return false; +#elif defined(OS_STARBOARD) + log_file = SbFileOpen(log_file_name->c_str(), + kSbFileOpenAlways | kSbFileWrite, NULL, NULL); + if (!SbFileIsValid(log_file)) + return false; + + SbFileSeek(log_file, kSbFileFromEnd, 0); +#endif + } + + return true; +} + +} // namespace + + +bool BaseInitLoggingImpl(const PathChar* new_log_file, + LoggingDestination logging_dest, + LogLockingState lock_log, + OldFileDeletionState delete_old, + DcheckState dcheck_state) { + g_dcheck_state = dcheck_state; +// TODO(bbudge) Hook this up to NaCl logging. +#if !defined(OS_NACL) + CommandLine* command_line = CommandLine::ForCurrentProcess(); + // Don't bother initializing g_vlog_info unless we use one of the + // vlog switches. + if (command_line->HasSwitch(switches::kV) || + command_line->HasSwitch(switches::kVModule)) { + // NOTE: If g_vlog_info has already been initialized, it might be in use + // by another thread. Don't delete the old VLogInfo, just create a second + // one. We keep track of both to avoid memory leak warnings. + CHECK(!g_vlog_info_prev); + g_vlog_info_prev = g_vlog_info; + + g_vlog_info = + new VlogInfo(command_line->GetSwitchValueASCII(switches::kV), + command_line->GetSwitchValueASCII(switches::kVModule), + &min_log_level); + } + + LoggingLock::Init(lock_log, new_log_file); + + LoggingLock logging_lock; + + if (log_file) { + // calling InitLogging twice or after some log call has already opened the + // default log file will re-initialize to the new options + CloseFile(log_file); + log_file = NULL; + } + + logging_destination = logging_dest; + + // ignore file options if logging is disabled or only to system + if (logging_destination == LOG_NONE || + logging_destination == LOG_ONLY_TO_SYSTEM_DEBUG_LOG) + return true; + + if (!log_file_name) + log_file_name = new PathString(); + *log_file_name = new_log_file; + if (delete_old == DELETE_OLD_LOG_FILE) + DeleteFilePath(*log_file_name); + + return InitializeLogFileHandle(); +#else + (void) g_vlog_info_prev; + return true; +#endif // !defined(OS_NACL) +} + +void SetMinLogLevel(int level) { + min_log_level = std::min(LOG_ERROR_REPORT, level); +} + +int GetMinLogLevel() { +#if LOGGING_IS_OFFICIAL_BUILD + return LOG_FATAL; +#else + return min_log_level; +#endif +} + +int GetVlogVerbosity() { + return std::max(-1, LOG_INFO - GetMinLogLevel()); +} + +int GetVlogLevelHelper(const char* file, size_t N) { + DCHECK_GT(N, 0U); + // Note: g_vlog_info may change on a different thread during startup + // (but will always be valid or NULL). + VlogInfo* vlog_info = g_vlog_info; + return vlog_info ? + vlog_info->GetVlogLevel(base::StringPiece(file, N - 1)) : + GetVlogVerbosity(); +} + +void SetLogItems(bool enable_process_id, bool enable_thread_id, + bool enable_timestamp, bool enable_tickcount) { + log_process_id = enable_process_id; + log_thread_id = enable_thread_id; + log_timestamp = enable_timestamp; + log_tickcount = enable_tickcount; +} + +void SetShowErrorDialogs(bool enable_dialogs) { + show_error_dialogs = enable_dialogs; +} + +void SetLogAssertHandler(LogAssertHandlerFunction handler) { + log_assert_handler = handler; +} + +void SetLogReportHandler(LogReportHandlerFunction handler) { + log_report_handler = handler; +} + +void SetLogMessageHandler(LogMessageHandlerFunction handler) { + log_message_handler = handler; +} + +LogMessageHandlerFunction GetLogMessageHandler() { + return log_message_handler; +} + +// MSVC doesn't like complex extern templates and DLLs. +#if !defined(COMPILER_MSVC) +// Explicit instantiations for commonly used comparisons. +template std::string* MakeCheckOpString<int, int>( + const int&, const int&, const char* names); +template std::string* MakeCheckOpString<unsigned long, unsigned long>( + const unsigned long&, const unsigned long&, const char* names); +template std::string* MakeCheckOpString<unsigned long, unsigned int>( + const unsigned long&, const unsigned int&, const char* names); +template std::string* MakeCheckOpString<unsigned int, unsigned long>( + const unsigned int&, const unsigned long&, const char* names); +template std::string* MakeCheckOpString<std::string, std::string>( + const std::string&, const std::string&, const char* name); +#endif + +// Displays a message box to the user with the error message in it. +// Used for fatal messages, where we close the app simultaneously. +// This is for developers only; we don't use this in circumstances +// (like release builds) where users could see it, since users don't +// understand these messages anyway. +void DisplayDebugMessageInDialog(const std::string& str) { + if (str.empty()) + return; + + if (!show_error_dialogs) + return; + +#if defined(OS_WIN) + // For Windows programs, it's possible that the message loop is + // messed up on a fatal error, and creating a MessageBox will cause + // that message loop to be run. Instead, we try to spawn another + // process that displays its command line. We look for "Debug + // Message.exe" in the same directory as the application. If it + // exists, we use it, otherwise, we use a regular message box. + wchar_t prog_name[MAX_PATH]; + GetModuleFileNameW(NULL, prog_name, MAX_PATH); + wchar_t* backslash = wcsrchr(prog_name, '\\'); + if (backslash) + backslash[1] = 0; + wcscat_s(prog_name, MAX_PATH, L"debug_message.exe"); + + std::wstring cmdline = UTF8ToWide(str); + if (cmdline.empty()) + return; + + STARTUPINFO startup_info; + memset(&startup_info, 0, sizeof(startup_info)); + startup_info.cb = sizeof(startup_info); + + PROCESS_INFORMATION process_info; + if (CreateProcessW(prog_name, &cmdline[0], NULL, NULL, false, 0, NULL, + NULL, &startup_info, &process_info)) { + WaitForSingleObject(process_info.hProcess, INFINITE); + CloseHandle(process_info.hThread); + CloseHandle(process_info.hProcess); + } else { + // debug process broken, let's just do a message box + MessageBoxW(NULL, &cmdline[0], L"Fatal error", + MB_OK | MB_ICONHAND | MB_TOPMOST); + } +#else + // We intentionally don't implement a dialog on other platforms. + // You can just look at stderr. +#endif +} + +#if defined(OS_WIN) +LogMessage::SaveLastError::SaveLastError() : last_error_(::GetLastError()) { +} + +LogMessage::SaveLastError::~SaveLastError() { + ::SetLastError(last_error_); +} +#endif // defined(OS_WIN) + +LogMessage::LogMessage(const char* file, int line, LogSeverity severity, + int ctr) + : severity_(severity), file_(file), line_(line) { + Init(file, line); +} + +LogMessage::LogMessage(const char* file, int line) + : severity_(LOG_INFO), file_(file), line_(line) { + Init(file, line); +} + +LogMessage::LogMessage(const char* file, int line, LogSeverity severity) + : severity_(severity), file_(file), line_(line) { + Init(file, line); +} + +LogMessage::LogMessage(const char* file, int line, std::string* result) + : severity_(LOG_FATAL), file_(file), line_(line) { + Init(file, line); + stream_ << "Check failed: " << *result; + delete result; +} + +LogMessage::LogMessage(const char* file, int line, LogSeverity severity, + std::string* result) + : severity_(severity), file_(file), line_(line) { + Init(file, line); + stream_ << "Check failed: " << *result; + delete result; +} + +LogMessage::~LogMessage() { + // TODO(port): enable stacktrace generation on LOG_FATAL once backtrace are + // working in Android. +#if (!defined(NDEBUG) || defined(__LB_SHELL__FORCE_LOGGING__)) && \ + !defined(OS_ANDROID) && !defined(OS_NACL) + if (severity_ == LOG_FATAL) { + // Include a stack trace on a fatal. + base::debug::StackTrace trace; + stream_ << std::endl; // Newline to separate from log message. + trace.OutputToStream(&stream_); + } +#endif + stream_ << std::endl; + std::string str_newline(stream_.str()); + + // Give any log message handler first dibs on the message. + if (log_message_handler && log_message_handler(severity_, file_, line_, + message_start_, str_newline)) { + // The handler took care of it, no further processing. + return; + } + + if (logging_destination == LOG_ONLY_TO_SYSTEM_DEBUG_LOG || + logging_destination == LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG) { +#if defined(OS_STARBOARD) + SbLogPriority priority = kSbLogPriorityUnknown; + switch (severity_) { + case LOG_INFO: + priority = kSbLogPriorityInfo; + break; + case LOG_WARNING: + priority = kSbLogPriorityWarning; + break; + case LOG_ERROR: + case LOG_ERROR_REPORT: + priority = kSbLogPriorityError; + break; + case LOG_FATAL: + priority = kSbLogPriorityFatal; + break; + } + SbLog(priority, str_newline.c_str()); +#elif defined(OS_WIN) || defined(COBALT_WIN) + OutputDebugStringA(str_newline.c_str()); +#elif defined(OS_ANDROID) || defined(__LB_ANDROID__) + android_LogPriority priority = ANDROID_LOG_UNKNOWN; + switch (severity_) { + case LOG_INFO: + priority = ANDROID_LOG_INFO; + break; + case LOG_WARNING: + priority = ANDROID_LOG_WARN; + break; + case LOG_ERROR: + case LOG_ERROR_REPORT: + priority = ANDROID_LOG_ERROR; + break; + case LOG_FATAL: + priority = ANDROID_LOG_FATAL; + break; + } + __android_log_write(priority, "chromium", str_newline.c_str()); +#endif +#if !defined(OS_STARBOARD) + fprintf(stderr, "%s", str_newline.c_str()); + fflush(stderr); +#endif + } else if (severity_ >= kAlwaysPrintErrorLevel) { + // When we're only outputting to a log file, above a certain log level, we + // should still output to stderr so that we can better detect and diagnose + // problems with unit tests, especially on the buildbots. + fprintf(stderr, "%s", str_newline.c_str()); + fflush(stderr); + } + + // We can have multiple threads and/or processes, so try to prevent them + // from clobbering each other's writes. + // If the client app did not call InitLogging, and the lock has not + // been created do it now. We do this on demand, but if two threads try + // to do this at the same time, there will be a race condition to create + // the lock. This is why InitLogging should be called from the main + // thread at the beginning of execution. + LoggingLock::Init(LOCK_LOG_FILE, NULL); + // write to log file + if (logging_destination != LOG_NONE && + logging_destination != LOG_ONLY_TO_SYSTEM_DEBUG_LOG) { + LoggingLock logging_lock; + if (InitializeLogFileHandle()) { +#if defined(OS_WIN) + SetFilePointer(log_file, 0, 0, SEEK_END); + DWORD num_written; + WriteFile(log_file, + static_cast<const void*>(str_newline.c_str()), + static_cast<DWORD>(str_newline.length()), + &num_written, + NULL); +#elif defined(OS_STARBOARD) + SbFileSeek(log_file, kSbFileFromEnd, 0); + int written = 0; + while (written < str_newline.length()) { + int result = SbFileWrite(log_file, &(str_newline.c_str()[written]), + str_newline.length() - written); + if (result < 0) { + break; + } + written += result; + } +#else + fprintf(log_file, "%s", str_newline.c_str()); + fflush(log_file); +#endif + } + } + + if (severity_ == LOG_FATAL) { + // Ensure the first characters of the string are on the stack so they + // are contained in minidumps for diagnostic purposes. + char str_stack[1024]; + str_newline.copy(str_stack, arraysize(str_stack)); + base::debug::Alias(str_stack); + + // display a message or break into the debugger on a fatal error + if (base::debug::BeingDebugged()) { + base::debug::BreakDebugger(); + } else { + if (log_assert_handler) { + // make a copy of the string for the handler out of paranoia + log_assert_handler(std::string(stream_.str())); + } else { + // Don't use the string with the newline, get a fresh version to send to + // the debug message process. We also don't display assertions to the + // user in release mode. The enduser can't do anything with this + // information, and displaying message boxes when the application is + // hosed can cause additional problems. +#ifndef NDEBUG + DisplayDebugMessageInDialog(stream_.str()); +#endif + // Crash the process to generate a dump. + base::debug::BreakDebugger(); + } + } + } else if (severity_ == LOG_ERROR_REPORT) { + // We are here only if the user runs with --enable-dcheck in release mode. + if (log_report_handler) { + log_report_handler(std::string(stream_.str())); + } else { + DisplayDebugMessageInDialog(stream_.str()); + } + } +} + +// writes the common header info to the stream +void LogMessage::Init(const char* file, int line) { + base::StringPiece filename(file); + size_t last_slash_pos = filename.find_last_of("\\/"); + if (last_slash_pos != base::StringPiece::npos) + filename.remove_prefix(last_slash_pos + 1); + + // TODO(darin): It might be nice if the columns were fixed width. + + stream_ << '['; + if (log_process_id) + stream_ << CurrentProcessId() << ':'; + if (log_thread_id) + stream_ << base::PlatformThread::CurrentId() << ':'; + if (log_timestamp) { +#if defined(OS_STARBOARD) + EzTimeT t = EzTimeTGetNow(NULL); + struct EzTimeExploded local_time = {0}; + EzTimeTExplodeLocal(&t, &local_time); + struct EzTimeExploded* tm_time = &local_time; +#else + time_t t = time(NULL); + struct tm local_time = {0}; +#if _MSC_VER >= 1400 + localtime_s(&local_time, &t); +#else + localtime_r(&t, &local_time); +#endif + struct tm* tm_time = &local_time; +#endif + stream_ << std::setfill('0') + << std::setw(2) << 1 + tm_time->tm_mon + << std::setw(2) << tm_time->tm_mday + << '/' + << std::setw(2) << tm_time->tm_hour + << std::setw(2) << tm_time->tm_min + << std::setw(2) << tm_time->tm_sec + << ':'; + } + if (log_tickcount) + stream_ << TickCount() << ':'; + if (severity_ >= 0) + stream_ << log_severity_names[severity_]; + else + stream_ << "VERBOSE" << -severity_; + + stream_ << ":" << filename << "(" << line << ")] "; + + message_start_ = stream_.tellp(); +} + +#if defined(OS_WIN) +// This has already been defined in the header, but defining it again as DWORD +// ensures that the type used in the header is equivalent to DWORD. If not, +// the redefinition is a compile error. +typedef DWORD SystemErrorCode; +#endif + +SystemErrorCode GetLastSystemErrorCode() { +#if defined(OS_WIN) + return ::GetLastError(); +#elif defined(OS_POSIX) + return errno; +#elif defined(OS_STARBOARD) + // Not implemented yet + return SbSystemGetLastError(); +#else +#error Not implemented +#endif +} + +#if defined(OS_WIN) +Win32ErrorLogMessage::Win32ErrorLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err, + const char* module) + : err_(err), + module_(module), + log_message_(file, line, severity) { +} + +Win32ErrorLogMessage::Win32ErrorLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err) + : err_(err), + module_(NULL), + log_message_(file, line, severity) { +} + +Win32ErrorLogMessage::~Win32ErrorLogMessage() { + const int error_message_buffer_size = 256; + char msgbuf[error_message_buffer_size]; + DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; + HMODULE hmod; + if (module_) { + hmod = GetModuleHandleA(module_); + if (hmod) { + flags |= FORMAT_MESSAGE_FROM_HMODULE; + } else { + // This makes a nested Win32ErrorLogMessage. It will have module_ of NULL + // so it will not call GetModuleHandle, so recursive errors are + // impossible. + DPLOG(WARNING) << "Couldn't open module " << module_ + << " for error message query"; + } + } else { + hmod = NULL; + } + DWORD len = FormatMessageA(flags, + hmod, + err_, + 0, + msgbuf, + sizeof(msgbuf) / sizeof(msgbuf[0]), + NULL); + if (len) { + while ((len > 0) && + isspace(static_cast<unsigned char>(msgbuf[len - 1]))) { + msgbuf[--len] = 0; + } + stream() << ": " << msgbuf; + } else { + stream() << ": Error " << GetLastError() << " while retrieving error " + << err_; + } + // We're about to crash (CHECK). Put |err_| on the stack (by placing it in a + // field) and use Alias in hopes that it makes it into crash dumps. + DWORD last_error = err_; + base::debug::Alias(&last_error); +} +#elif defined(OS_POSIX) +ErrnoLogMessage::ErrnoLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err) + : err_(err), + log_message_(file, line, severity) { +} + +ErrnoLogMessage::~ErrnoLogMessage() { + stream() << ": " << safe_strerror(err_); +} +#elif defined(OS_STARBOARD) +StarboardLogMessage::StarboardLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err) + : err_(err), + log_message_(file, line, severity) { +} + +StarboardLogMessage::~StarboardLogMessage() { + char message[512]; + SbSystemGetErrorString(err_, message, SB_ARRAY_SIZE_INT(message)); + stream() << ": " << message; +} +#endif // OS_WIN + +void CloseLogFile() { + LoggingLock logging_lock; + + if (!log_file) + return; + + CloseFile(log_file); + log_file = NULL; +} + +void RawLog(int level, const char* message) { + if (level >= min_log_level) { +#if defined(OS_STARBOARD) + SbLogRaw(message); + const size_t message_len = strlen(message); + if (message_len > 0 && message[message_len - 1] != '\n') { + SbLogRaw("\n"); + } +#else + size_t bytes_written = 0; + const size_t message_len = strlen(message); + int rv; + while (bytes_written < message_len) { + rv = HANDLE_EINTR( + write(STDERR_FILENO, message + bytes_written, + message_len - bytes_written)); + if (rv < 0) { + // Give up, nothing we can do now. + break; + } + bytes_written += rv; + } + + if (message_len > 0 && message[message_len - 1] != '\n') { + do { + rv = HANDLE_EINTR(write(STDERR_FILENO, "\n", 1)); + if (rv < 0) { + // Give up, nothing we can do now. + break; + } + } while (rv != 1); + } +#endif + } + + if (level == LOG_FATAL) + base::debug::BreakDebugger(); +} + +// This was defined at the beginning of this file. +#undef write + +} // namespace logging + +std::ostream& operator<<(std::ostream& out, const wchar_t* wstr) { + return out << WideToUTF8(std::wstring(wstr)); +}
diff --git a/src/base/logging.h b/src/base/logging.h new file mode 100644 index 0000000..2fb626e --- /dev/null +++ b/src/base/logging.h
@@ -0,0 +1,1092 @@ +// 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_LOGGING_H_ +#define BASE_LOGGING_H_ + +#include <cassert> +#include <string> +#include <cstring> +#include <sstream> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/debug/debugger.h" +#include "build/build_config.h" + +#if defined(OS_STARBOARD) +#include "starboard/system.h" +#endif + +// +// Optional message capabilities +// ----------------------------- +// Assertion failed messages and fatal errors are displayed in a dialog box +// before the application exits. However, running this UI creates a message +// loop, which causes application messages to be processed and potentially +// dispatched to existing application windows. Since the application is in a +// bad state when this assertion dialog is displayed, these messages may not +// get processed and hang the dialog, or the application might go crazy. +// +// Therefore, it can be beneficial to display the error dialog in a separate +// process from the main application. When the logging system needs to display +// a fatal error dialog box, it will look for a program called +// "DebugMessage.exe" in the same directory as the application executable. It +// will run this application with the message as the command line, and will +// not include the name of the application as is traditional for easier +// parsing. +// +// The code for DebugMessage.exe is only one line. In WinMain, do: +// MessageBox(NULL, GetCommandLineW(), L"Fatal Error", 0); +// +// If DebugMessage.exe is not found, the logging code will use a normal +// MessageBox, potentially causing the problems discussed above. + + +// Instructions +// ------------ +// +// Make a bunch of macros for logging. The way to log things is to stream +// things to LOG(<a particular severity level>). E.g., +// +// LOG(INFO) << "Found " << num_cookies << " cookies"; +// +// You can also do conditional logging: +// +// LOG_IF(INFO, num_cookies > 10) << "Got lots of cookies"; +// +// The above will cause log messages to be output on the 1st, 11th, 21st, ... +// times it is executed. Note that the special COUNTER value is used to +// identify which repetition is happening. +// +// The CHECK(condition) macro is active in both debug and release builds and +// effectively performs a LOG(FATAL) which terminates the process and +// generates a crashdump unless a debugger is attached. +// +// There are also "debug mode" logging macros like the ones above: +// +// DLOG(INFO) << "Found cookies"; +// +// DLOG_IF(INFO, num_cookies > 10) << "Got lots of cookies"; +// +// All "debug mode" logging is compiled away to nothing for non-debug mode +// compiles. LOG_IF and development flags also work well together +// because the code can be compiled away sometimes. +// +// We also have +// +// LOG_ASSERT(assertion); +// DLOG_ASSERT(assertion); +// +// which is syntactic sugar for {,D}LOG_IF(FATAL, assert fails) << assertion; +// +// There are "verbose level" logging macros. They look like +// +// VLOG(1) << "I'm printed when you run the program with --v=1 or more"; +// VLOG(2) << "I'm printed when you run the program with --v=2 or more"; +// +// These always log at the INFO log level (when they log at all). +// The verbose logging can also be turned on module-by-module. For instance, +// --vmodule=profile=2,icon_loader=1,browser_*=3,*/chromeos/*=4 --v=0 +// will cause: +// a. VLOG(2) and lower messages to be printed from profile.{h,cc} +// b. VLOG(1) and lower messages to be printed from icon_loader.{h,cc} +// c. VLOG(3) and lower messages to be printed from files prefixed with +// "browser" +// d. VLOG(4) and lower messages to be printed from files under a +// "chromeos" directory. +// e. VLOG(0) and lower messages to be printed from elsewhere +// +// The wildcarding functionality shown by (c) supports both '*' (match +// 0 or more characters) and '?' (match any single character) +// wildcards. Any pattern containing a forward or backward slash will +// be tested against the whole pathname and not just the module. +// E.g., "*/foo/bar/*=2" would change the logging level for all code +// in source files under a "foo/bar" directory. +// +// There's also VLOG_IS_ON(n) "verbose level" condition macro. To be used as +// +// if (VLOG_IS_ON(2)) { +// // do some logging preparation and logging +// // that can't be accomplished with just VLOG(2) << ...; +// } +// +// There is also a VLOG_IF "verbose level" condition macro for sample +// cases, when some extra computation and preparation for logs is not +// needed. +// +// VLOG_IF(1, (size > 1024)) +// << "I'm printed when size is more than 1024 and when you run the " +// "program with --v=1 or more"; +// +// We also override the standard 'assert' to use 'DLOG_ASSERT'. +// +// Lastly, there is: +// +// PLOG(ERROR) << "Couldn't do foo"; +// DPLOG(ERROR) << "Couldn't do foo"; +// PLOG_IF(ERROR, cond) << "Couldn't do foo"; +// DPLOG_IF(ERROR, cond) << "Couldn't do foo"; +// PCHECK(condition) << "Couldn't do foo"; +// DPCHECK(condition) << "Couldn't do foo"; +// +// which append the last system error to the message in string form (taken from +// GetLastError() on Windows and errno on POSIX). +// +// The supported severity levels for macros that allow you to specify one +// are (in increasing order of severity) INFO, WARNING, ERROR, ERROR_REPORT, +// and FATAL. +// +// Very important: logging a message at the FATAL severity level causes +// the program to terminate (after the message is logged). +// +// Note the special severity of ERROR_REPORT only available/relevant in normal +// mode, which displays error dialog without terminating the program. There is +// no error dialog for severity ERROR or below in normal mode. +// +// There is also the special severity of DFATAL, which logs FATAL in +// debug mode, ERROR in normal mode. + +namespace logging { + +// Where to record logging output? A flat file and/or system debug log via +// OutputDebugString. Defaults on Windows to LOG_ONLY_TO_FILE, and on +// POSIX to LOG_ONLY_TO_SYSTEM_DEBUG_LOG (aka stderr). +enum LoggingDestination { LOG_NONE, + LOG_ONLY_TO_FILE, + LOG_ONLY_TO_SYSTEM_DEBUG_LOG, + LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG }; + +// Indicates that the log file should be locked when being written to. +// Often, there is no locking, which is fine for a single threaded program. +// If logging is being done from multiple threads or there can be more than +// one process doing the logging, the file should be locked during writes to +// make each log outut atomic. Other writers will block. +// +// All processes writing to the log file must have their locking set for it to +// work properly. Defaults to DONT_LOCK_LOG_FILE. +enum LogLockingState { LOCK_LOG_FILE, DONT_LOCK_LOG_FILE }; + +// On startup, should we delete or append to an existing log file (if any)? +// Defaults to APPEND_TO_OLD_LOG_FILE. +enum OldFileDeletionState { DELETE_OLD_LOG_FILE, APPEND_TO_OLD_LOG_FILE }; + +enum DcheckState { + DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS, + ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS +}; + +// TODO(avi): do we want to do a unification of character types here? +#if defined(OS_WIN) +typedef wchar_t PathChar; +#else +typedef char PathChar; +#endif + +// Define different names for the BaseInitLoggingImpl() function depending on +// whether NDEBUG is defined or not so that we'll fail to link if someone tries +// to compile logging.cc with NDEBUG but includes logging.h without defining it, +// or vice versa. +#if defined(NDEBUG) && !defined(__LB_SHELL__FORCE_LOGGING__) +#define BaseInitLoggingImpl BaseInitLoggingImpl_built_with_NDEBUG +#else +#define BaseInitLoggingImpl BaseInitLoggingImpl_built_without_NDEBUG +#endif + +// Implementation of the InitLogging() method declared below. We use a +// more-specific name so we can #define it above without affecting other code +// that has named stuff "InitLogging". +BASE_EXPORT bool BaseInitLoggingImpl(const PathChar* log_file, + LoggingDestination logging_dest, + LogLockingState lock_log, + OldFileDeletionState delete_old, + DcheckState dcheck_state); + +// Sets the log file name and other global logging state. Calling this function +// is recommended, and is normally done at the beginning of application init. +// If you don't call it, all the flags will be initialized to their default +// values, and there is a race condition that may leak a critical section +// object if two threads try to do the first log at the same time. +// See the definition of the enums above for descriptions and default values. +// +// The default log file is initialized to "debug.log" in the application +// directory. You probably don't want this, especially since the program +// directory may not be writable on an enduser's system. +// +// This function may be called a second time to re-direct logging (e.g after +// loging in to a user partition), however it should never be called more than +// twice. +inline bool InitLogging(const PathChar* log_file, + LoggingDestination logging_dest, + LogLockingState lock_log, + OldFileDeletionState delete_old, + DcheckState dcheck_state) { + return BaseInitLoggingImpl(log_file, logging_dest, lock_log, + delete_old, dcheck_state); +} + +// Sets the log level. Anything at or above this level will be written to the +// log file/displayed to the user (if applicable). Anything below this level +// will be silently ignored. The log level defaults to 0 (everything is logged +// up to level INFO) if this function is not called. +// Note that log messages for VLOG(x) are logged at level -x, so setting +// the min log level to negative values enables verbose logging. +BASE_EXPORT void SetMinLogLevel(int level); + +// Gets the current log level. +BASE_EXPORT int GetMinLogLevel(); + +// Gets the VLOG default verbosity level. +BASE_EXPORT int GetVlogVerbosity(); + +// Gets the current vlog level for the given file (usually taken from +// __FILE__). + +// Note that |N| is the size *with* the null terminator. +BASE_EXPORT int GetVlogLevelHelper(const char* file_start, size_t N); + +template <size_t N> +int GetVlogLevel(const char (&file)[N]) { + return GetVlogLevelHelper(file, N); +} + +// Sets the common items you want to be prepended to each log message. +// process and thread IDs default to off, the timestamp defaults to on. +// If this function is not called, logging defaults to writing the timestamp +// only. +BASE_EXPORT void SetLogItems(bool enable_process_id, bool enable_thread_id, + bool enable_timestamp, bool enable_tickcount); + +// Sets whether or not you'd like to see fatal debug messages popped up in +// a dialog box or not. +// Dialogs are not shown by default. +BASE_EXPORT void SetShowErrorDialogs(bool enable_dialogs); + +// Sets the Log Assert Handler that will be used to notify of check failures. +// The default handler shows a dialog box and then terminate the process, +// however clients can use this function to override with their own handling +// (e.g. a silent one for Unit Tests) +typedef void (*LogAssertHandlerFunction)(const std::string& str); +BASE_EXPORT void SetLogAssertHandler(LogAssertHandlerFunction handler); + +// Sets the Log Report Handler that will be used to notify of check failures +// in non-debug mode. The default handler shows a dialog box and continues +// the execution, however clients can use this function to override with their +// own handling. +typedef void (*LogReportHandlerFunction)(const std::string& str); +BASE_EXPORT void SetLogReportHandler(LogReportHandlerFunction handler); + +// Sets the Log Message Handler that gets passed every log message before +// it's sent to other log destinations (if any). +// Returns true to signal that it handled the message and the message +// should not be sent to other log destinations. +typedef bool (*LogMessageHandlerFunction)(int severity, + const char* file, int line, size_t message_start, const std::string& str); +BASE_EXPORT void SetLogMessageHandler(LogMessageHandlerFunction handler); +BASE_EXPORT LogMessageHandlerFunction GetLogMessageHandler(); + +typedef int LogSeverity; +const LogSeverity LOG_VERBOSE = -1; // This is level 1 verbosity +// Note: the log severities are used to index into the array of names, +// see log_severity_names. +const LogSeverity LOG_INFO = 0; +const LogSeverity LOG_WARNING = 1; +const LogSeverity LOG_ERROR = 2; +const LogSeverity LOG_ERROR_REPORT = 3; +const LogSeverity LOG_FATAL = 4; +const LogSeverity LOG_NUM_SEVERITIES = 5; + +// LOG_DFATAL is LOG_FATAL in debug mode, ERROR in normal mode +#if defined(NDEBUG) && !defined(__LB_SHELL__FORCE_LOGGING__) +const LogSeverity LOG_DFATAL = LOG_ERROR; +#else +const LogSeverity LOG_DFATAL = LOG_FATAL; +#endif + +// A few definitions of macros that don't generate much code. These are used +// by LOG() and LOG_IF, etc. Since these are used all over our code, it's +// better to have compact code for these operations. +#define COMPACT_GOOGLE_LOG_EX_INFO(ClassName, ...) \ + logging::ClassName(__FILE__, __LINE__, logging::LOG_INFO , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_EX_WARNING(ClassName, ...) \ + logging::ClassName(__FILE__, __LINE__, logging::LOG_WARNING , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_EX_ERROR(ClassName, ...) \ + logging::ClassName(__FILE__, __LINE__, logging::LOG_ERROR , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_EX_ERROR_REPORT(ClassName, ...) \ + logging::ClassName(__FILE__, __LINE__, \ + logging::LOG_ERROR_REPORT , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_EX_FATAL(ClassName, ...) \ + logging::ClassName(__FILE__, __LINE__, logging::LOG_FATAL , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_EX_DFATAL(ClassName, ...) \ + logging::ClassName(__FILE__, __LINE__, logging::LOG_DFATAL , ##__VA_ARGS__) + +#define COMPACT_GOOGLE_LOG_INFO \ + COMPACT_GOOGLE_LOG_EX_INFO(LogMessage) +#define COMPACT_GOOGLE_LOG_WARNING \ + COMPACT_GOOGLE_LOG_EX_WARNING(LogMessage) +#define COMPACT_GOOGLE_LOG_ERROR \ + COMPACT_GOOGLE_LOG_EX_ERROR(LogMessage) +#define COMPACT_GOOGLE_LOG_ERROR_REPORT \ + COMPACT_GOOGLE_LOG_EX_ERROR_REPORT(LogMessage) +#define COMPACT_GOOGLE_LOG_FATAL \ + COMPACT_GOOGLE_LOG_EX_FATAL(LogMessage) +#define COMPACT_GOOGLE_LOG_DFATAL \ + COMPACT_GOOGLE_LOG_EX_DFATAL(LogMessage) + +// wingdi.h defines ERROR to be 0. When we call LOG(ERROR), it gets +// substituted with 0, and it expands to COMPACT_GOOGLE_LOG_0. To allow us +// to keep using this syntax, we define this macro to do the same thing +// as COMPACT_GOOGLE_LOG_ERROR, and also define ERROR the same way that +// the Windows SDK does for consistency. +#define ERROR 0 +#define COMPACT_GOOGLE_LOG_EX_0(ClassName, ...) \ + COMPACT_GOOGLE_LOG_EX_ERROR(ClassName , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_0 COMPACT_GOOGLE_LOG_ERROR +// Needed for LOG_IS_ON(ERROR). +const LogSeverity LOG_0 = LOG_ERROR; + +// As special cases, we can assume that LOG_IS_ON(ERROR_REPORT) and +// LOG_IS_ON(FATAL) always hold. Also, LOG_IS_ON(DFATAL) always holds +// in debug mode. In particular, CHECK()s will always fire if they +// fail. +#define LOG_IS_ON(severity) \ + ((::logging::LOG_ ## severity) >= ::logging::GetMinLogLevel()) + +// We can't do any caching tricks with VLOG_IS_ON() like the +// google-glog version since it requires GCC extensions. This means +// that using the v-logging functions in conjunction with --vmodule +// may be slow. +#define VLOG_IS_ON(verboselevel) \ + ((verboselevel) <= ::logging::GetVlogLevel(__FILE__)) + +// Helper macro which avoids evaluating the arguments to a stream if +// the condition doesn't hold. +#define LAZY_STREAM(stream, condition) \ + !(condition) ? (void) 0 : ::logging::LogMessageVoidify() & (stream) + +// We use the preprocessor's merging operator, "##", so that, e.g., +// LOG(INFO) becomes the token COMPACT_GOOGLE_LOG_INFO. There's some funny +// subtle difference between ostream member streaming functions (e.g., +// ostream::operator<<(int) and ostream non-member streaming functions +// (e.g., ::operator<<(ostream&, string&): it turns out that it's +// impossible to stream something like a string directly to an unnamed +// ostream. We employ a neat hack by calling the stream() member +// function of LogMessage which seems to avoid the problem. +#define LOG_STREAM(severity) COMPACT_GOOGLE_LOG_ ## severity.stream() + +#define LOG(severity) LAZY_STREAM(LOG_STREAM(severity), LOG_IS_ON(severity)) +#define LOG_IF(severity, condition) \ + LAZY_STREAM(LOG_STREAM(severity), LOG_IS_ON(severity) && (condition)) + +#define SYSLOG(severity) LOG(severity) +#define SYSLOG_IF(severity, condition) LOG_IF(severity, condition) + +// The VLOG macros log with negative verbosities. +#define VLOG_STREAM(verbose_level) \ + logging::LogMessage(__FILE__, __LINE__, -verbose_level).stream() + +#define VLOG(verbose_level) \ + LAZY_STREAM(VLOG_STREAM(verbose_level), VLOG_IS_ON(verbose_level)) + +#define VLOG_IF(verbose_level, condition) \ + LAZY_STREAM(VLOG_STREAM(verbose_level), \ + VLOG_IS_ON(verbose_level) && (condition)) + +#if defined (OS_WIN) +#define VPLOG_STREAM(verbose_level) \ + logging::Win32ErrorLogMessage(__FILE__, __LINE__, -verbose_level, \ + ::logging::GetLastSystemErrorCode()).stream() +#elif defined(OS_POSIX) +#define VPLOG_STREAM(verbose_level) \ + logging::ErrnoLogMessage(__FILE__, __LINE__, -verbose_level, \ + ::logging::GetLastSystemErrorCode()).stream() +#elif defined(OS_STARBOARD) +#define VPLOG_STREAM(verbose_level) \ + logging::StarboardLogMessage(__FILE__, __LINE__, -verbose_level, \ + ::logging::GetLastSystemErrorCode()).stream() +#endif + +#define VPLOG(verbose_level) \ + LAZY_STREAM(VPLOG_STREAM(verbose_level), VLOG_IS_ON(verbose_level)) + +#define VPLOG_IF(verbose_level, condition) \ + LAZY_STREAM(VPLOG_STREAM(verbose_level), \ + VLOG_IS_ON(verbose_level) && (condition)) + +// TODO(akalin): Add more VLOG variants, e.g. VPLOG. + +#define LOG_ASSERT(condition) \ + LOG_IF(FATAL, !(condition)) << "Assert failed: " #condition ". " +#define SYSLOG_ASSERT(condition) \ + SYSLOG_IF(FATAL, !(condition)) << "Assert failed: " #condition ". " + +#if defined(OS_WIN) +#define LOG_GETLASTERROR_STREAM(severity) \ + COMPACT_GOOGLE_LOG_EX_ ## severity(Win32ErrorLogMessage, \ + ::logging::GetLastSystemErrorCode()).stream() +#define LOG_GETLASTERROR(severity) \ + LAZY_STREAM(LOG_GETLASTERROR_STREAM(severity), LOG_IS_ON(severity)) +#define LOG_GETLASTERROR_MODULE_STREAM(severity, module) \ + COMPACT_GOOGLE_LOG_EX_ ## severity(Win32ErrorLogMessage, \ + ::logging::GetLastSystemErrorCode(), module).stream() +#define LOG_GETLASTERROR_MODULE(severity, module) \ + LAZY_STREAM(LOG_GETLASTERROR_STREAM(severity, module), \ + LOG_IS_ON(severity)) +// PLOG_STREAM is used by PLOG, which is the usual error logging macro +// for each platform. +#define PLOG_STREAM(severity) LOG_GETLASTERROR_STREAM(severity) +#elif defined(OS_POSIX) +#define LOG_ERRNO_STREAM(severity) \ + COMPACT_GOOGLE_LOG_EX_ ## severity(ErrnoLogMessage, \ + ::logging::GetLastSystemErrorCode()).stream() +#define LOG_ERRNO(severity) \ + LAZY_STREAM(LOG_ERRNO_STREAM(severity), LOG_IS_ON(severity)) +// PLOG_STREAM is used by PLOG, which is the usual error logging macro +// for each platform. +#define PLOG_STREAM(severity) LOG_ERRNO_STREAM(severity) +#elif defined(OS_STARBOARD) +#define LOG_SB_ERROR_STREAM(severity) \ + COMPACT_GOOGLE_LOG_EX_ ## severity(StarboardLogMessage, \ + ::logging::GetLastSystemErrorCode()).stream() +#define LOG_SB_ERROR(severity) \ + LAZY_STREAM(LOG_SB_ERROR_STREAM(severity), LOG_IS_ON(severity)) +// PLOG_STREAM is used by PLOG, which is the usual error logging macro +// for each platform. +#define PLOG_STREAM(severity) LOG_SB_ERROR_STREAM(severity) +#endif + +#define PLOG(severity) \ + LAZY_STREAM(PLOG_STREAM(severity), LOG_IS_ON(severity)) + +#define PLOG_IF(severity, condition) \ + LAZY_STREAM(PLOG_STREAM(severity), LOG_IS_ON(severity) && (condition)) + +// http://crbug.com/16512 is open for a real fix for this. For now, Windows +// uses OFFICIAL_BUILD and other platforms use the branding flag when NDEBUG is +// defined. +#if ( defined(OS_WIN) && defined(OFFICIAL_BUILD)) || \ + (!defined(OS_WIN) && defined(NDEBUG) && defined(GOOGLE_CHROME_BUILD)) || \ + defined(__LB_SHELL__FOR_RELEASE__) +#define LOGGING_IS_OFFICIAL_BUILD 1 +#else +#define LOGGING_IS_OFFICIAL_BUILD 0 +#endif + +// The actual stream used isn't important. +#define EAT_STREAM_PARAMETERS \ + true ? (void) 0 : ::logging::LogMessageVoidify() & LOG_STREAM(FATAL) + +// CHECK dies with a fatal error if condition is not true. It is *not* +// controlled by NDEBUG, so the check will be executed regardless of +// compilation mode. +// +// We make sure CHECK et al. always evaluates their arguments, as +// doing CHECK(FunctionWithSideEffect()) is a common idiom. + +#if LOGGING_IS_OFFICIAL_BUILD + +// Make all CHECK functions discard their log strings to reduce code +// bloat for official builds. + +// TODO(akalin): This would be more valuable if there were some way to +// remove BreakDebugger() from the backtrace, perhaps by turning it +// into a macro (like __debugbreak() on Windows). +#define CHECK(condition) \ + !(condition) ? ::base::debug::BreakDebugger() : EAT_STREAM_PARAMETERS + +#define PCHECK(condition) CHECK(condition) + +#define CHECK_OP(name, op, val1, val2) CHECK((val1) op (val2)) + +#else + +// Help Microsoft code analysis tool to understand that if CHECK()/PCHECK() +// did not terminate the execution, the condition they checked must be true. +// Note that the code under _PREFAST_ macro is not actually executed, only +// analyzed. +#if defined(COBALT) && defined(_PREFAST_) +#define CHECK(condition) \ + __analysis_assume(condition), EAT_STREAM_PARAMETERS + +#define PCHECK(condition) \ + __analysis_assume(condition), EAT_STREAM_PARAMETERS +#else +#define CHECK(condition) \ + LAZY_STREAM(LOG_STREAM(FATAL), !(condition)) \ + << "Check failed: " #condition ". " + +#define PCHECK(condition) \ + LAZY_STREAM(PLOG_STREAM(FATAL), !(condition)) \ + << "Check failed: " #condition ". " +#endif + +// Helper macro for binary operators. +// Don't use this macro directly in your code, use CHECK_EQ et al below. +// +// TODO(akalin): Rewrite this so that constructs like if (...) +// CHECK_EQ(...) else { ... } work properly. +#define CHECK_OP(name, op, val1, val2) \ + if (std::string* _result = \ + logging::Check##name##Impl((val1), (val2), \ + #val1 " " #op " " #val2)) \ + logging::LogMessage(__FILE__, __LINE__, _result).stream() + +#endif + +// Build the error message string. This is separate from the "Impl" +// function template because it is not performance critical and so can +// be out of line, while the "Impl" code should be inline. Caller +// takes ownership of the returned string. +template<class t1, class t2> +std::string* MakeCheckOpString(const t1& v1, const t2& v2, const char* names) { + std::ostringstream ss; + ss << names << " (" << v1 << " vs. " << v2 << ")"; + std::string* msg = new std::string(ss.str()); + return msg; +} + +// MSVC doesn't like complex extern templates and DLLs. +#if !defined(COMPILER_MSVC) && !defined(COMPILER_GHS) +// Commonly used instantiations of MakeCheckOpString<>. Explicitly instantiated +// in logging.cc. +extern template BASE_EXPORT std::string* MakeCheckOpString<int, int>( + const int&, const int&, const char* names); +extern template BASE_EXPORT +std::string* MakeCheckOpString<unsigned long, unsigned long>( + const unsigned long&, const unsigned long&, const char* names); +extern template BASE_EXPORT +std::string* MakeCheckOpString<unsigned long, unsigned int>( + const unsigned long&, const unsigned int&, const char* names); +extern template BASE_EXPORT +std::string* MakeCheckOpString<unsigned int, unsigned long>( + const unsigned int&, const unsigned long&, const char* names); +extern template BASE_EXPORT +std::string* MakeCheckOpString<std::string, std::string>( + const std::string&, const std::string&, const char* name); +#endif + +// Helper functions for CHECK_OP macro. +// The (int, int) specialization works around the issue that the compiler +// will not instantiate the template version of the function on values of +// unnamed enum type - see comment below. +#define DEFINE_CHECK_OP_IMPL(name, op) \ + template <class t1, class t2> \ + inline std::string* Check##name##Impl(const t1& v1, const t2& v2, \ + const char* names) { \ + if (v1 op v2) return NULL; \ + else return MakeCheckOpString(v1, v2, names); \ + } \ + inline std::string* Check##name##Impl(int v1, int v2, const char* names) { \ + if (v1 op v2) return NULL; \ + else return MakeCheckOpString(v1, v2, names); \ + } + +// Turn off sign-conversion warnings for DCHECK_EQ. This is for +// consistency with EXPECT_EQ and ASSERT_EQ in gtest. +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4389) +#elif defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-compare" +#endif + +DEFINE_CHECK_OP_IMPL(EQ, ==) + +#if defined(_MSC_VER) +#pragma warning(pop) +#elif defined(__clang__) +#pragma clang diagnostic pop +#endif + +DEFINE_CHECK_OP_IMPL(NE, !=) +DEFINE_CHECK_OP_IMPL(LE, <=) +DEFINE_CHECK_OP_IMPL(LT, < ) +DEFINE_CHECK_OP_IMPL(GE, >=) +DEFINE_CHECK_OP_IMPL(GT, > ) +#undef DEFINE_CHECK_OP_IMPL + +#define CHECK_EQ(val1, val2) CHECK_OP(EQ, ==, val1, val2) +#define CHECK_NE(val1, val2) CHECK_OP(NE, !=, val1, val2) +#define CHECK_LE(val1, val2) CHECK_OP(LE, <=, val1, val2) +#define CHECK_LT(val1, val2) CHECK_OP(LT, < , val1, val2) +#define CHECK_GE(val1, val2) CHECK_OP(GE, >=, val1, val2) +#define CHECK_GT(val1, val2) CHECK_OP(GT, > , val1, val2) + +#if LOGGING_IS_OFFICIAL_BUILD || \ + (defined(__LB_SHELL__FOR_QA__) && !defined(__LB_SHELL__FORCE_LOGGING__)) +// In order to have optimized code for official builds, remove DLOGs and +// DCHECKs. +#define ENABLE_DLOG 0 +#define ENABLE_DCHECK 0 + +#elif defined(NDEBUG) && !defined(__LB_SHELL__FORCE_LOGGING__) +// Otherwise, if we're a release build, remove DLOGs but not DCHECKs +// (since those can still be turned on via a command-line flag). +#define ENABLE_DLOG 0 +#define ENABLE_DCHECK 1 + +#else +// Otherwise, we're a debug build so enable DLOGs and DCHECKs. +#define ENABLE_DLOG 1 +#define ENABLE_DCHECK 1 +#endif + +// Definitions for DLOG et al. + +#if ENABLE_DLOG + +#define DLOG_IS_ON(severity) LOG_IS_ON(severity) +#define DLOG_IF(severity, condition) LOG_IF(severity, condition) +#define DLOG_ASSERT(condition) LOG_ASSERT(condition) +#define DPLOG_IF(severity, condition) PLOG_IF(severity, condition) +#define DVLOG_IF(verboselevel, condition) VLOG_IF(verboselevel, condition) +#define DVPLOG_IF(verboselevel, condition) VPLOG_IF(verboselevel, condition) + +#else // ENABLE_DLOG + +// If ENABLE_DLOG is off, we want to avoid emitting any references to +// |condition| (which may reference a variable defined only if NDEBUG +// is not defined). Contrast this with DCHECK et al., which has +// different behavior. + +#define DLOG_IS_ON(severity) false +#define DLOG_IF(severity, condition) EAT_STREAM_PARAMETERS +#define DLOG_ASSERT(condition) EAT_STREAM_PARAMETERS +#define DPLOG_IF(severity, condition) EAT_STREAM_PARAMETERS +#define DVLOG_IF(verboselevel, condition) EAT_STREAM_PARAMETERS +#define DVPLOG_IF(verboselevel, condition) EAT_STREAM_PARAMETERS + +#endif // ENABLE_DLOG + +// DEBUG_MODE is for uses like +// if (DEBUG_MODE) foo.CheckThatFoo(); +// instead of +// #ifndef NDEBUG +// foo.CheckThatFoo(); +// #endif +// +// We tie its state to ENABLE_DLOG. +enum { DEBUG_MODE = ENABLE_DLOG }; + +#undef ENABLE_DLOG + +#define DLOG(severity) \ + LAZY_STREAM(LOG_STREAM(severity), DLOG_IS_ON(severity)) + +#if defined(OS_WIN) +#define DLOG_GETLASTERROR(severity) \ + LAZY_STREAM(LOG_GETLASTERROR_STREAM(severity), DLOG_IS_ON(severity)) +#define DLOG_GETLASTERROR_MODULE(severity, module) \ + LAZY_STREAM(LOG_GETLASTERROR_STREAM(severity, module), \ + DLOG_IS_ON(severity)) +#elif defined(OS_POSIX) +#define DLOG_ERRNO(severity) \ + LAZY_STREAM(LOG_ERRNO_STREAM(severity), DLOG_IS_ON(severity)) +#endif + +#define DPLOG(severity) \ + LAZY_STREAM(PLOG_STREAM(severity), DLOG_IS_ON(severity)) + +#define DVLOG(verboselevel) DVLOG_IF(verboselevel, VLOG_IS_ON(verboselevel)) + +#define DVPLOG(verboselevel) DVPLOG_IF(verboselevel, VLOG_IS_ON(verboselevel)) + +// Definitions for DCHECK et al. + +#if ENABLE_DCHECK + +#if defined(NDEBUG) && !defined(__LB_SHELL__FORCE_LOGGING__) + +BASE_EXPORT extern DcheckState g_dcheck_state; + +#if defined(DCHECK_ALWAYS_ON) + +#define DCHECK_IS_ON() true +#define COMPACT_GOOGLE_LOG_EX_DCHECK(ClassName, ...) \ + COMPACT_GOOGLE_LOG_EX_FATAL(ClassName , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_DCHECK COMPACT_GOOGLE_LOG_FATAL +const LogSeverity LOG_DCHECK = LOG_FATAL; + +#else + +#define COMPACT_GOOGLE_LOG_EX_DCHECK(ClassName, ...) \ + COMPACT_GOOGLE_LOG_EX_ERROR_REPORT(ClassName , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_DCHECK COMPACT_GOOGLE_LOG_ERROR_REPORT +const LogSeverity LOG_DCHECK = LOG_ERROR_REPORT; +#define DCHECK_IS_ON() \ + ((::logging::g_dcheck_state == \ + ::logging::ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS) && \ + LOG_IS_ON(DCHECK)) + +#endif // defined(DCHECK_ALWAYS_ON) + +#else // defined(NDEBUG) && !defined(__LB_SHELL__FORCE_LOGGING__) + +#if defined(NDEBUG) && defined(__LB_SHELL__FORCE_LOGGING__) +BASE_EXPORT extern DcheckState g_dcheck_state; +#endif + +// On a regular debug build, we want to have DCHECKs enabled. +#define COMPACT_GOOGLE_LOG_EX_DCHECK(ClassName, ...) \ + COMPACT_GOOGLE_LOG_EX_FATAL(ClassName , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_DCHECK COMPACT_GOOGLE_LOG_FATAL +const LogSeverity LOG_DCHECK = LOG_FATAL; +#define DCHECK_IS_ON() true + +#endif // defined(NDEBUG) && !defined(__LB_SHELL__FORCE_LOGGING__) + +#else // ENABLE_DCHECK + +// These are just dummy values since DCHECK_IS_ON() is always false in +// this case. +#define COMPACT_GOOGLE_LOG_EX_DCHECK(ClassName, ...) \ + COMPACT_GOOGLE_LOG_EX_INFO(ClassName , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_DCHECK COMPACT_GOOGLE_LOG_INFO +const LogSeverity LOG_DCHECK = LOG_INFO; +#define DCHECK_IS_ON() false + +#endif // ENABLE_DCHECK +#undef ENABLE_DCHECK + +// DCHECK et al. make sure to reference |condition| regardless of +// whether DCHECKs are enabled; this is so that we don't get unused +// variable warnings if the only use of a variable is in a DCHECK. +// This behavior is different from DLOG_IF et al. + +// Help Microsoft code analysis tool to understand that if DCHECK()/DPCHECK() +// did not terminate the execution, the condition they checked must be true. +// Note that the code under _PREFAST_ macro is not actually executed, only +// analyzed. +// Use !!(condition) to work around error C2088: +// '__assume': illegal for class +#if defined(COBALT) && defined(_PREFAST_) +#define DCHECK(condition) \ + __analysis_assume(!!(condition)), EAT_STREAM_PARAMETERS + +#define DPCHECK(condition) \ + __analysis_assume(!!(condition)), EAT_STREAM_PARAMETERS +#else +#define DCHECK(condition) \ + LAZY_STREAM(LOG_STREAM(DCHECK), DCHECK_IS_ON() ? !(condition) : false) \ + << "Check failed: " #condition ". " + +#define DPCHECK(condition) \ + LAZY_STREAM(PLOG_STREAM(DCHECK), DCHECK_IS_ON() ? !(condition) : false) \ + << "Check failed: " #condition ". " +#endif + +// Helper macro for binary operators. +// Don't use this macro directly in your code, use DCHECK_EQ et al below. +#define DCHECK_OP(name, op, val1, val2) \ + if (DCHECK_IS_ON()) \ + if (std::string* _result = \ + logging::Check##name##Impl((val1), (val2), \ + #val1 " " #op " " #val2)) \ + logging::LogMessage( \ + __FILE__, __LINE__, ::logging::LOG_DCHECK, \ + _result).stream() + +// Equality/Inequality checks - compare two values, and log a +// LOG_DCHECK message including the two values when the result is not +// as expected. The values must have operator<<(ostream, ...) +// defined. +// +// You may append to the error message like so: +// DCHECK_NE(1, 2) << ": The world must be ending!"; +// +// We are very careful to ensure that each argument is evaluated exactly +// once, and that anything which is legal to pass as a function argument is +// legal here. In particular, the arguments may be temporary expressions +// which will end up being destroyed at the end of the apparent statement, +// for example: +// DCHECK_EQ(string("abc")[1], 'b'); +// +// WARNING: These may not compile correctly if one of the arguments is a pointer +// and the other is NULL. To work around this, simply static_cast NULL to the +// type of the desired pointer. + +#define DCHECK_EQ(val1, val2) DCHECK_OP(EQ, ==, val1, val2) +#define DCHECK_NE(val1, val2) DCHECK_OP(NE, !=, val1, val2) +#define DCHECK_LE(val1, val2) DCHECK_OP(LE, <=, val1, val2) +#define DCHECK_LT(val1, val2) DCHECK_OP(LT, < , val1, val2) +#define DCHECK_GE(val1, val2) DCHECK_OP(GE, >=, val1, val2) +#define DCHECK_GT(val1, val2) DCHECK_OP(GT, > , val1, val2) + +#define NOTREACHED() DCHECK(false) + +// Redefine the standard assert to use our nice log files +#undef assert +#define assert(x) DLOG_ASSERT(x) + +// This class more or less represents a particular log message. You +// create an instance of LogMessage and then stream stuff to it. +// When you finish streaming to it, ~LogMessage is called and the +// full message gets streamed to the appropriate destination. +// +// You shouldn't actually use LogMessage's constructor to log things, +// though. You should use the LOG() macro (and variants thereof) +// above. +class BASE_EXPORT LogMessage { + public: + LogMessage(const char* file, int line, LogSeverity severity, int ctr); + + // Two special constructors that generate reduced amounts of code at + // LOG call sites for common cases. + // + // Used for LOG(INFO): Implied are: + // severity = LOG_INFO, ctr = 0 + // + // Using this constructor instead of the more complex constructor above + // saves a couple of bytes per call site. + LogMessage(const char* file, int line); + + // Used for LOG(severity) where severity != INFO. Implied + // are: ctr = 0 + // + // Using this constructor instead of the more complex constructor above + // saves a couple of bytes per call site. + LogMessage(const char* file, int line, LogSeverity severity); + + // A special constructor used for check failures. Takes ownership + // of the given string. + // Implied severity = LOG_FATAL + LogMessage(const char* file, int line, std::string* result); + + // A special constructor used for check failures, with the option to + // specify severity. Takes ownership of the given string. + LogMessage(const char* file, int line, LogSeverity severity, + std::string* result); + + ~LogMessage(); + + std::ostream& stream() { return stream_; } + + private: + void Init(const char* file, int line); + + LogSeverity severity_; + std::ostringstream stream_; + size_t message_start_; // Offset of the start of the message (past prefix + // info). + // The file and line information passed in to the constructor. + const char* file_; + const int line_; + +#if defined(OS_WIN) + // Stores the current value of GetLastError in the constructor and restores + // it in the destructor by calling SetLastError. + // This is useful since the LogMessage class uses a lot of Win32 calls + // that will lose the value of GLE and the code that called the log function + // will have lost the thread error value when the log call returns. + class SaveLastError { + public: + SaveLastError(); + ~SaveLastError(); + + unsigned long get_error() const { return last_error_; } + + protected: + unsigned long last_error_; + }; + + SaveLastError last_error_; +#endif + + DISALLOW_COPY_AND_ASSIGN(LogMessage); +}; + +// A non-macro interface to the log facility; (useful +// when the logging level is not a compile-time constant). +inline void LogAtLevel(int const log_level, std::string const &msg) { + LogMessage(__FILE__, __LINE__, log_level).stream() << msg; +} + +// This class is used to explicitly ignore values in the conditional +// logging macros. This avoids compiler warnings like "value computed +// is not used" and "statement has no effect". +class LogMessageVoidify { + public: + LogMessageVoidify() { } + // This has to be an operator with a precedence lower than << but + // higher than ?: + void operator&(std::ostream&) { } +}; + +#if defined(OS_WIN) +typedef unsigned long SystemErrorCode; +#elif defined(OS_POSIX) +typedef int SystemErrorCode; +#elif defined(OS_STARBOARD) +typedef SbSystemError SystemErrorCode; +#endif + +// Alias for ::GetLastError() on Windows and errno on POSIX. Avoids having to +// pull in windows.h just for GetLastError() and DWORD. +BASE_EXPORT SystemErrorCode GetLastSystemErrorCode(); + +#if defined(OS_WIN) +// Appends a formatted system message of the GetLastError() type. +class BASE_EXPORT Win32ErrorLogMessage { + public: + Win32ErrorLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err, + const char* module); + + Win32ErrorLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err); + + // Appends the error message before destructing the encapsulated class. + ~Win32ErrorLogMessage(); + + std::ostream& stream() { return log_message_.stream(); } + + private: + SystemErrorCode err_; + // Optional name of the module defining the error. + const char* module_; + LogMessage log_message_; + + DISALLOW_COPY_AND_ASSIGN(Win32ErrorLogMessage); +}; +#elif defined(OS_POSIX) +// Appends a formatted system message of the errno type +class BASE_EXPORT ErrnoLogMessage { + public: + ErrnoLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err); + + // Appends the error message before destructing the encapsulated class. + ~ErrnoLogMessage(); + + std::ostream& stream() { return log_message_.stream(); } + + private: + SystemErrorCode err_; + LogMessage log_message_; + + DISALLOW_COPY_AND_ASSIGN(ErrnoLogMessage); +}; +#elif defined(OS_STARBOARD) +// Appends a formatted system message of the errno type +class BASE_EXPORT StarboardLogMessage { + public: + StarboardLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err); + + // Appends the error message before destructing the encapsulated class. + ~StarboardLogMessage(); + + std::ostream& stream() { return log_message_.stream(); } + + private: + SystemErrorCode err_; + LogMessage log_message_; + + DISALLOW_COPY_AND_ASSIGN(StarboardLogMessage); +}; +#endif // OS_WIN + +// Closes the log file explicitly if open. +// NOTE: Since the log file is opened as necessary by the action of logging +// statements, there's no guarantee that it will stay closed +// after this call. +BASE_EXPORT void CloseLogFile(); + +// Async signal safe logging mechanism. +BASE_EXPORT void RawLog(int level, const char* message); + +#define RAW_LOG(level, message) logging::RawLog(logging::LOG_ ## level, message) + +#define RAW_CHECK(condition) \ + do { \ + if (!(condition)) \ + logging::RawLog(logging::LOG_FATAL, "Check failed: " #condition "\n"); \ + } while (0) + +} // namespace logging + +// These functions are provided as a convenience for logging, which is where we +// use streams (it is against Google style to use streams in other places). It +// is designed to allow you to emit non-ASCII Unicode strings to the log file, +// which is normally ASCII. It is relatively slow, so try not to use it for +// common cases. Non-ASCII characters will be converted to UTF-8 by these +// operators. +BASE_EXPORT std::ostream& operator<<(std::ostream& out, const wchar_t* wstr); +inline std::ostream& operator<<(std::ostream& out, const std::wstring& wstr) { + return out << wstr.c_str(); +} + +#if defined(__LB_SHELL__) || defined(COBALT) +#if defined(__cplusplus_winrt) +// Support for logging C++/CX strings. This function must be inlined because +// the Chromium library is not compiled with C++/CX extensions enabled. +inline std::ostream& operator<<(std::ostream& out, ::Platform::String^ str) { + return out << std::wstring(str->Begin(), str->End()); +} +#endif +#endif + +// The NOTIMPLEMENTED() macro annotates codepaths which have +// not been implemented yet. +// +// The implementation of this macro is controlled by NOTIMPLEMENTED_POLICY: +// 0 -- Do nothing (stripped by compiler) +// 1 -- Warn at compile time +// 2 -- Fail at compile time +// 3 -- Fail at runtime (DCHECK) +// 4 -- [default] LOG(ERROR) at runtime +// 5 -- LOG(ERROR) at runtime, only once per call-site + +#ifndef NOTIMPLEMENTED_POLICY +#if (defined(OS_ANDROID) && defined(OFFICIAL_BUILD)) || \ + defined(__LB_SHELL__FOR_RELEASE__) +#define NOTIMPLEMENTED_POLICY 0 +#elif defined (__LB_SHELL__) || defined(COBALT) + // Only print each message once. +#define NOTIMPLEMENTED_POLICY 5 +#else + // Select default policy: LOG(ERROR) +#define NOTIMPLEMENTED_POLICY 4 +#endif +#endif + +#if defined(COMPILER_GCC) +// On Linux, with GCC, we can use __PRETTY_FUNCTION__ to get the demangled name +// of the current function in the NOTIMPLEMENTED message. +#define NOTIMPLEMENTED_MSG "Not implemented reached in " << __PRETTY_FUNCTION__ +#elif defined(__LB_SHELL__) || defined(COBALT) +#define NOTIMPLEMENTED_MSG "Not implemented reached in " << __FUNCTION__ +#else +#define NOTIMPLEMENTED_MSG "NOT IMPLEMENTED" +#endif + +#if NOTIMPLEMENTED_POLICY == 0 +#define NOTIMPLEMENTED() EAT_STREAM_PARAMETERS +#elif NOTIMPLEMENTED_POLICY == 1 +// TODO, figure out how to generate a warning +#define NOTIMPLEMENTED() COMPILE_ASSERT(false, NOT_IMPLEMENTED) +#elif NOTIMPLEMENTED_POLICY == 2 +#define NOTIMPLEMENTED() COMPILE_ASSERT(false, NOT_IMPLEMENTED) +#elif NOTIMPLEMENTED_POLICY == 3 +#define NOTIMPLEMENTED() NOTREACHED() +#elif NOTIMPLEMENTED_POLICY == 4 +#define NOTIMPLEMENTED() LOG(ERROR) << NOTIMPLEMENTED_MSG +#elif NOTIMPLEMENTED_POLICY == 5 +#define NOTIMPLEMENTED() do {\ + static int count = 0;\ + LOG_IF(ERROR, 0 == count++) << NOTIMPLEMENTED_MSG;\ +} while(0);\ +EAT_STREAM_PARAMETERS +#endif + +#endif // BASE_LOGGING_H_
diff --git a/src/base/logging_unittest.cc b/src/base/logging_unittest.cc new file mode 100644 index 0000000..f0e9f56 --- /dev/null +++ b/src/base/logging_unittest.cc
@@ -0,0 +1,279 @@ +// 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/basictypes.h" +#include "base/logging.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace logging { + +namespace { + +using ::testing::Return; + +// Needs to be global since log assert handlers can't maintain state. +int log_sink_call_count = 0; + +void LogSink(const std::string& str) { + ++log_sink_call_count; +} + +// Class to make sure any manipulations we do to the min log level are +// contained (i.e., do not affect other unit tests). +class LogStateSaver { + public: + LogStateSaver() : old_min_log_level_(GetMinLogLevel()) {} + + ~LogStateSaver() { + SetMinLogLevel(old_min_log_level_); + SetLogAssertHandler(NULL); + SetLogReportHandler(NULL); + log_sink_call_count = 0; + } + + private: + int old_min_log_level_; + + DISALLOW_COPY_AND_ASSIGN(LogStateSaver); +}; + +class LoggingTest : public testing::Test { + private: + LogStateSaver log_state_saver_; +}; + +class MockLogSource { + public: + MOCK_METHOD0(Log, const char*()); +}; + +TEST_F(LoggingTest, BasicLogging) { + MockLogSource mock_log_source; + const int kExpectedDebugOrReleaseCalls = 6; + const int kExpectedDebugCalls = 6; + const int kExpectedCalls = + kExpectedDebugOrReleaseCalls + (DEBUG_MODE ? kExpectedDebugCalls : 0); + EXPECT_CALL(mock_log_source, Log()).Times(kExpectedCalls). + WillRepeatedly(Return("log message")); + + SetMinLogLevel(LOG_INFO); + + EXPECT_TRUE(LOG_IS_ON(INFO)); + // As of g++-4.5, the first argument to EXPECT_EQ cannot be a + // constant expression. + const bool kIsDebugMode = (DEBUG_MODE != 0); + EXPECT_EQ(kIsDebugMode, DLOG_IS_ON(INFO)); + EXPECT_TRUE(VLOG_IS_ON(0)); + + LOG(INFO) << mock_log_source.Log(); + LOG_IF(INFO, true) << mock_log_source.Log(); + PLOG(INFO) << mock_log_source.Log(); + PLOG_IF(INFO, true) << mock_log_source.Log(); + VLOG(0) << mock_log_source.Log(); + VLOG_IF(0, true) << mock_log_source.Log(); + + DLOG(INFO) << mock_log_source.Log(); + DLOG_IF(INFO, true) << mock_log_source.Log(); + DPLOG(INFO) << mock_log_source.Log(); + DPLOG_IF(INFO, true) << mock_log_source.Log(); + DVLOG(0) << mock_log_source.Log(); + DVLOG_IF(0, true) << mock_log_source.Log(); +} + +TEST_F(LoggingTest, LogIsOn) { +#if defined(NDEBUG) && !defined(__LB_SHELL__FORCE_LOGGING__) + const bool kDfatalIsFatal = false; +#else // defined(NDEBUG) + const bool kDfatalIsFatal = true; +#endif // defined(NDEBUG) + + SetMinLogLevel(LOG_INFO); + EXPECT_TRUE(LOG_IS_ON(INFO)); + EXPECT_TRUE(LOG_IS_ON(WARNING)); + EXPECT_TRUE(LOG_IS_ON(ERROR)); + EXPECT_TRUE(LOG_IS_ON(ERROR_REPORT)); + EXPECT_TRUE(LOG_IS_ON(FATAL)); + EXPECT_TRUE(LOG_IS_ON(DFATAL)); + + SetMinLogLevel(LOG_WARNING); + EXPECT_FALSE(LOG_IS_ON(INFO)); + EXPECT_TRUE(LOG_IS_ON(WARNING)); + EXPECT_TRUE(LOG_IS_ON(ERROR)); + EXPECT_TRUE(LOG_IS_ON(ERROR_REPORT)); + EXPECT_TRUE(LOG_IS_ON(FATAL)); + EXPECT_TRUE(LOG_IS_ON(DFATAL)); + + SetMinLogLevel(LOG_ERROR); + EXPECT_FALSE(LOG_IS_ON(INFO)); + EXPECT_FALSE(LOG_IS_ON(WARNING)); + EXPECT_TRUE(LOG_IS_ON(ERROR)); + EXPECT_TRUE(LOG_IS_ON(ERROR_REPORT)); + EXPECT_TRUE(LOG_IS_ON(FATAL)); + EXPECT_TRUE(LOG_IS_ON(DFATAL)); + + SetMinLogLevel(LOG_ERROR_REPORT); + EXPECT_FALSE(LOG_IS_ON(INFO)); + EXPECT_FALSE(LOG_IS_ON(WARNING)); + EXPECT_FALSE(LOG_IS_ON(ERROR)); + EXPECT_TRUE(LOG_IS_ON(ERROR_REPORT)); + EXPECT_TRUE(LOG_IS_ON(FATAL)); + EXPECT_EQ(kDfatalIsFatal, LOG_IS_ON(DFATAL)); + + // LOG_IS_ON(ERROR_REPORT) should always be true. + SetMinLogLevel(LOG_FATAL); + EXPECT_FALSE(LOG_IS_ON(INFO)); + EXPECT_FALSE(LOG_IS_ON(WARNING)); + EXPECT_FALSE(LOG_IS_ON(ERROR)); + EXPECT_TRUE(LOG_IS_ON(ERROR_REPORT)); + EXPECT_TRUE(LOG_IS_ON(FATAL)); + EXPECT_EQ(kDfatalIsFatal, LOG_IS_ON(DFATAL)); + + // So should LOG_IS_ON(FATAL). + SetMinLogLevel(LOG_FATAL + 1); + EXPECT_FALSE(LOG_IS_ON(INFO)); + EXPECT_FALSE(LOG_IS_ON(WARNING)); + EXPECT_FALSE(LOG_IS_ON(ERROR)); + EXPECT_TRUE(LOG_IS_ON(ERROR_REPORT)); + EXPECT_TRUE(LOG_IS_ON(FATAL)); + EXPECT_EQ(kDfatalIsFatal, LOG_IS_ON(DFATAL)); +} + +TEST_F(LoggingTest, LoggingIsLazy) { + MockLogSource mock_log_source; + EXPECT_CALL(mock_log_source, Log()).Times(0); + + SetMinLogLevel(LOG_WARNING); + + EXPECT_FALSE(LOG_IS_ON(INFO)); + EXPECT_FALSE(DLOG_IS_ON(INFO)); + EXPECT_FALSE(VLOG_IS_ON(1)); + + LOG(INFO) << mock_log_source.Log(); + LOG_IF(INFO, false) << mock_log_source.Log(); + PLOG(INFO) << mock_log_source.Log(); + PLOG_IF(INFO, false) << mock_log_source.Log(); + VLOG(1) << mock_log_source.Log(); + VLOG_IF(1, true) << mock_log_source.Log(); + + DLOG(INFO) << mock_log_source.Log(); + DLOG_IF(INFO, true) << mock_log_source.Log(); + DPLOG(INFO) << mock_log_source.Log(); + DPLOG_IF(INFO, true) << mock_log_source.Log(); + DVLOG(1) << mock_log_source.Log(); + DVLOG_IF(1, true) << mock_log_source.Log(); +} + +// Official builds have CHECKs directly call BreakDebugger. +#if !defined(LOGGING_IS_OFFICIAL_BUILD) + +TEST_F(LoggingTest, CheckStreamsAreLazy) { + MockLogSource mock_log_source, uncalled_mock_log_source; + EXPECT_CALL(mock_log_source, Log()).Times(8). + WillRepeatedly(Return("check message")); + EXPECT_CALL(uncalled_mock_log_source, Log()).Times(0); + + SetLogAssertHandler(&LogSink); + + CHECK(mock_log_source.Log()) << uncalled_mock_log_source.Log(); + PCHECK(!mock_log_source.Log()) << mock_log_source.Log(); + CHECK_EQ(mock_log_source.Log(), mock_log_source.Log()) + << uncalled_mock_log_source.Log(); + CHECK_NE(mock_log_source.Log(), mock_log_source.Log()) + << mock_log_source.Log(); +} + +#endif + +TEST_F(LoggingTest, DebugLoggingReleaseBehavior) { +#if !defined(NDEBUG) || defined(__LB_SHELL__FORCE_LOGGING__) + int debug_only_variable = 1; +#endif + // These should avoid emitting references to |debug_only_variable| + // in release mode. + DLOG_IF(INFO, debug_only_variable) << "test"; + DLOG_ASSERT(debug_only_variable) << "test"; + DPLOG_IF(INFO, debug_only_variable) << "test"; + DVLOG_IF(1, debug_only_variable) << "test"; +} + +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) + // Unofficial release build without dcheck enabled. + g_dcheck_state = DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS; + DCHECK(mock_log_source.Log()) << mock_log_source.Log(); + DPCHECK(mock_log_source.Log()) << mock_log_source.Log(); + DCHECK_EQ(0, 0) << mock_log_source.Log(); + DCHECK_EQ(mock_log_source.Log(), static_cast<const char*>(NULL)) + << mock_log_source.Log(); +#endif +} + +TEST_F(LoggingTest, Dcheck) { +#if LOGGING_IS_OFFICIAL_BUILD + // Official build. + EXPECT_FALSE(DCHECK_IS_ON()); + EXPECT_FALSE(DLOG_IS_ON(DCHECK)); +#elif defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) && !defined(__LB_SHELL__FORCE_LOGGING__) + // Unofficial release build. + g_dcheck_state = ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS; + SetLogReportHandler(&LogSink); + EXPECT_TRUE(DCHECK_IS_ON()); + EXPECT_FALSE(DLOG_IS_ON(DCHECK)); +#elif defined(NDEBUG) && defined(DCHECK_ALWAYS_ON) + // Unofficial release build with real DCHECKS. + g_dcheck_state = ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS; + SetLogAssertHandler(&LogSink); + EXPECT_TRUE(DCHECK_IS_ON()); + EXPECT_FALSE(DLOG_IS_ON(DCHECK)); +#elif defined(NDEBUG) && defined(__LB_SHELL__FORCE_LOGGING__) + // Unofficial release build with real DCHECKS. + g_dcheck_state = ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS; + SetLogAssertHandler(&LogSink); + EXPECT_TRUE(DCHECK_IS_ON()); + // FORCE_LOGGING implies DLOG is on. + EXPECT_TRUE(DLOG_IS_ON(DCHECK)); +#else + // Unofficial debug build. + SetLogAssertHandler(&LogSink); + EXPECT_TRUE(DCHECK_IS_ON()); + EXPECT_TRUE(DLOG_IS_ON(DCHECK)); +#endif // defined(LOGGING_IS_OFFICIAL_BUILD) + +#if !defined(OS_STARBOARD) +#if defined(__LB_SHELL__) + // These only break when the Logging class thinks a debugger is attached. + // Unfortunately, LB_SHELL assumes a debugger is attached as long as + // NDEBUG is not defined for some platforms (see debugger_posix.cc:BeingDebugged()). + if (!base::debug::BeingDebugged()) { +#endif + EXPECT_EQ(0, log_sink_call_count); + DCHECK(false); + EXPECT_EQ(DCHECK_IS_ON() ? 1 : 0, log_sink_call_count); + DPCHECK(false); + EXPECT_EQ(DCHECK_IS_ON() ? 2 : 0, log_sink_call_count); + DCHECK_EQ(0, 1); + EXPECT_EQ(DCHECK_IS_ON() ? 3 : 0, log_sink_call_count); +#if defined(__LB_SHELL__) + } +#endif +#endif +} + +TEST_F(LoggingTest, DcheckReleaseBehavior) { + int some_variable = 1; + // These should still reference |some_variable| so we don't get + // unused variable warnings. + DCHECK(some_variable) << "test"; + DPCHECK(some_variable) << "test"; + DCHECK_EQ(some_variable, 1) << "test"; +} + +} // namespace + +} // namespace logging
diff --git a/src/base/logging_win.cc b/src/base/logging_win.cc new file mode 100644 index 0000000..a714665 --- /dev/null +++ b/src/base/logging_win.cc
@@ -0,0 +1,139 @@ +// 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/logging_win.h" +#include "base/memory/singleton.h" +#include <initguid.h> // NOLINT + +namespace logging { + +using base::win::EtwEventLevel; +using base::win::EtwMofEvent; + +DEFINE_GUID(kLogEventId, + 0x7fe69228, 0x633e, 0x4f06, 0x80, 0xc1, 0x52, 0x7f, 0xea, 0x23, 0xe3, 0xa7); + +LogEventProvider::LogEventProvider() : old_log_level_(LOG_NONE) { +} + +LogEventProvider* LogEventProvider::GetInstance() { + return Singleton<LogEventProvider, + StaticMemorySingletonTraits<LogEventProvider> >::get(); +} + +bool LogEventProvider::LogMessage(logging::LogSeverity severity, + const char* file, int line, size_t message_start, + const std::string& message) { + EtwEventLevel level = TRACE_LEVEL_NONE; + + // Convert the log severity to the most appropriate ETW trace level. + if (severity >= 0) { + switch (severity) { + case LOG_INFO: + level = TRACE_LEVEL_INFORMATION; + break; + case LOG_WARNING: + level = TRACE_LEVEL_WARNING; + break; + case LOG_ERROR: + case LOG_ERROR_REPORT: + level = TRACE_LEVEL_ERROR; + break; + case LOG_FATAL: + level = TRACE_LEVEL_FATAL; + break; + } + } else { // severity < 0 is VLOG verbosity levels. + level = TRACE_LEVEL_INFORMATION - severity; + } + + // Bail if we're not logging, not at that level, + // or if we're post-atexit handling. + LogEventProvider* provider = LogEventProvider::GetInstance(); + if (provider == NULL || level > provider->enable_level()) + return false; + + // And now log the event. + if (provider->enable_flags() & ENABLE_LOG_MESSAGE_ONLY) { + EtwMofEvent<1> event(kLogEventId, LOG_MESSAGE, level); + event.SetField(0, message.length() + 1 - message_start, + message.c_str() + message_start); + + provider->Log(event.get()); + } else { + const size_t kMaxBacktraceDepth = 32; + void* backtrace[kMaxBacktraceDepth]; + DWORD depth = 0; + + // Capture a stack trace if one is requested. + // requested per our enable flags. + if (provider->enable_flags() & ENABLE_STACK_TRACE_CAPTURE) + depth = CaptureStackBackTrace(2, kMaxBacktraceDepth, backtrace, NULL); + + EtwMofEvent<5> event(kLogEventId, LOG_MESSAGE_FULL, level); + if (file == NULL) + file = ""; + + // Add the stack trace. + event.SetField(0, sizeof(depth), &depth); + event.SetField(1, sizeof(backtrace[0]) * depth, &backtrace); + // The line. + event.SetField(2, sizeof(line), &line); + // The file. + event.SetField(3, strlen(file) + 1, file); + // And finally the message. + event.SetField(4, message.length() + 1 - message_start, + message.c_str() + message_start); + + provider->Log(event.get()); + } + + // Don't increase verbosity in other log destinations. + if (severity < provider->old_log_level_) + return true; + + return false; +} + +void LogEventProvider::Initialize(const GUID& provider_name) { + LogEventProvider* provider = LogEventProvider::GetInstance(); + + provider->set_provider_name(provider_name); + provider->Register(); + + // Register our message handler with logging. + SetLogMessageHandler(LogMessage); +} + +void LogEventProvider::Uninitialize() { + LogEventProvider::GetInstance()->Unregister(); +} + +void LogEventProvider::OnEventsEnabled() { + // Grab the old log level so we can restore it later. + old_log_level_ = GetMinLogLevel(); + + // Convert the new trace level to a logging severity + // and enable logging at that level. + EtwEventLevel level = enable_level(); + if (level == TRACE_LEVEL_NONE || level == TRACE_LEVEL_FATAL) { + SetMinLogLevel(LOG_FATAL); + } else if (level == TRACE_LEVEL_ERROR) { + SetMinLogLevel(LOG_ERROR); + } else if (level == TRACE_LEVEL_WARNING) { + SetMinLogLevel(LOG_WARNING); + } else if (level == TRACE_LEVEL_INFORMATION) { + SetMinLogLevel(LOG_INFO); + } else if (level >= TRACE_LEVEL_VERBOSE) { + // Above INFO, we enable verbose levels with negative severities. + SetMinLogLevel(TRACE_LEVEL_INFORMATION - level); + } +} + +void LogEventProvider::OnEventsDisabled() { + // Restore the old log level. + SetMinLogLevel(old_log_level_); +} + +} // namespace logging
diff --git a/src/base/logging_win.h b/src/base/logging_win.h new file mode 100644 index 0000000..0720231 --- /dev/null +++ b/src/base/logging_win.h
@@ -0,0 +1,80 @@ +// 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_LOGGING_WIN_H_ +#define BASE_LOGGING_WIN_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/win/event_trace_provider.h" +#include "base/logging.h" + +template <typename Type> +struct StaticMemorySingletonTraits; + +namespace logging { + +// Event ID for the log messages we generate. +BASE_EXPORT extern const GUID kLogEventId; + +// Feature enable mask for LogEventProvider. +enum LogEnableMask { + // If this bit is set in our provider enable mask, we will include + // a stack trace with every log message. + ENABLE_STACK_TRACE_CAPTURE = 0x0001, + // If this bit is set in our provider enable mask, the provider will log + // a LOG message with only the textual content of the message, and no + // stack trace. + ENABLE_LOG_MESSAGE_ONLY = 0x0002, +}; + +// The message types our log event provider generates. +// ETW likes user message types to start at 10. +enum LogMessageTypes { + // A textual only log message, contains a zero-terminated string. + LOG_MESSAGE = 10, + // A message with a stack trace, followed by the zero-terminated + // message text. + LOG_MESSAGE_WITH_STACKTRACE = 11, + // A message with: + // a stack trace, + // the line number as a four byte integer, + // the file as a zero terminated UTF8 string, + // the zero-terminated UTF8 message text. + LOG_MESSAGE_FULL = 12, +}; + +// Trace provider class to drive log control and transport +// with Event Tracing for Windows. +class BASE_EXPORT LogEventProvider : public base::win::EtwTraceProvider { + public: + static LogEventProvider* GetInstance(); + + static bool LogMessage(logging::LogSeverity severity, const char* file, + int line, size_t message_start, const std::string& str); + + static void Initialize(const GUID& provider_name); + static void Uninitialize(); + + protected: + // Overridden to manipulate the log level on ETW control callbacks. + virtual void OnEventsEnabled(); + virtual void OnEventsDisabled(); + + private: + LogEventProvider(); + + // The log severity prior to OnEventsEnabled, + // restored in OnEventsDisabled. + logging::LogSeverity old_log_level_; + + friend struct StaticMemorySingletonTraits<LogEventProvider>; + DISALLOW_COPY_AND_ASSIGN(LogEventProvider); +}; + +} // namespace logging + +#endif // BASE_LOGGING_WIN_H_
diff --git a/src/base/mac/authorization_util.h b/src/base/mac/authorization_util.h new file mode 100644 index 0000000..b34348d --- /dev/null +++ b/src/base/mac/authorization_util.h
@@ -0,0 +1,73 @@ +// 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_MAC_AUTHORIZATION_UTIL_H_ +#define BASE_MAC_AUTHORIZATION_UTIL_H_ + +// AuthorizationExecuteWithPrivileges fork()s and exec()s the tool, but it +// does not wait() for it. It also doesn't provide the caller with access to +// the forked pid. If used irresponsibly, zombie processes will accumulate. +// +// Apple's really gotten us between a rock and a hard place, here. +// +// Fortunately, AuthorizationExecuteWithPrivileges does give access to the +// tool's stdout (and stdin) via a FILE* pipe. The tool can output its pid +// to this pipe, and the main program can read it, and then have something +// that it can wait() for. +// +// The contract is that any tool executed by the wrappers declared in this +// file must print its pid to stdout on a line by itself before doing anything +// else. +// +// http://developer.apple.com/library/mac/#samplecode/BetterAuthorizationSample/Listings/BetterAuthorizationSampleLib_c.html +// (Look for "What's This About Zombies?") + +#include <CoreFoundation/CoreFoundation.h> +#include <Security/Authorization.h> +#include <stdio.h> +#include <sys/types.h> + +#include "base/base_export.h" + +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, +// |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. +BASE_EXPORT +AuthorizationRef AuthorizationCreateToRunAsRoot(CFStringRef prompt); + +// Calls straight through to AuthorizationExecuteWithPrivileges. If that +// call succeeds, |pid| will be set to the pid of the executed tool. If the +// pid can't be determined, |pid| will be set to -1. |pid| must not be NULL. +// |pipe| may be NULL, but the tool will always be executed with a pipe in +// order to read the pid from its stdout. +BASE_EXPORT +OSStatus ExecuteWithPrivilegesAndGetPID(AuthorizationRef authorization, + const char* tool_path, + AuthorizationFlags options, + const char** arguments, + FILE** pipe, + pid_t* pid); + +// Calls ExecuteWithPrivilegesAndGetPID, and if that call succeeds, calls +// waitpid() to wait for the process to exit. If waitpid() succeeds, the +// exit status is placed in |exit_status|, otherwise, -1 is stored. +// |exit_status| may be NULL and this function will still wait for the process +// to exit. +BASE_EXPORT +OSStatus ExecuteWithPrivilegesAndWait(AuthorizationRef authorization, + const char* tool_path, + AuthorizationFlags options, + const char** arguments, + FILE** pipe, + int* exit_status); + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_AUTHORIZATION_UTIL_H_
diff --git a/src/base/mac/authorization_util.mm b/src/base/mac/authorization_util.mm new file mode 100644 index 0000000..62a2074 --- /dev/null +++ b/src/base/mac/authorization_util.mm
@@ -0,0 +1,187 @@ +// 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/mac/authorization_util.h" + +#import <Foundation/Foundation.h> +#include <sys/wait.h> + +#include <string> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/mac/bundle_locations.h" +#include "base/mac/mac_logging.h" +#import "base/mac/mac_util.h" +#include "base/mac/scoped_authorizationref.h" +#include "base/posix/eintr_wrapper.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" + +namespace base { +namespace mac { + +AuthorizationRef AuthorizationCreateToRunAsRoot(CFStringRef prompt) { + // Create an empty AuthorizationRef. + ScopedAuthorizationRef authorization; + OSStatus status = AuthorizationCreate(NULL, + kAuthorizationEmptyEnvironment, + kAuthorizationFlagDefaults, + &authorization); + if (status != errAuthorizationSuccess) { + OSSTATUS_LOG(ERROR, status) << "AuthorizationCreate"; + 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}; + + // product_logo_32.png is used instead of app.icns because Authorization + // Services can't deal with .icns files. + NSString* icon_path = + [base::mac::FrameworkBundle() pathForResource:@"product_logo_32" + ofType:@"png"]; + const char* icon_path_c = [icon_path fileSystemRepresentation]; + size_t icon_path_length = icon_path_c ? strlen(icon_path_c) : 0; + + // The OS will append " Type an administrator's name and password to allow + // <CFBundleDisplayName> to make changes." + NSString* prompt_ns = base::mac::CFToNSCast(prompt); + const char* prompt_c = [prompt_ns UTF8String]; + size_t prompt_length = prompt_c ? strlen(prompt_c) : 0; + + AuthorizationItem environment_items[] = { + {kAuthorizationEnvironmentIcon, icon_path_length, (void*)icon_path_c, 0}, + {kAuthorizationEnvironmentPrompt, prompt_length, (void*)prompt_c, 0} + }; + + AuthorizationEnvironment environment = {arraysize(environment_items), + environment_items}; + + AuthorizationFlags flags = kAuthorizationFlagDefaults | + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagExtendRights | + kAuthorizationFlagPreAuthorize; + + status = AuthorizationCopyRights(authorization, + &rights, + &environment, + flags, + NULL); + if (status != errAuthorizationSuccess) { + if (status != errAuthorizationCanceled) { + OSSTATUS_LOG(ERROR, status) << "AuthorizationCopyRights"; + } + return NULL; + } + + return authorization.release(); +} + +OSStatus ExecuteWithPrivilegesAndGetPID(AuthorizationRef authorization, + const char* tool_path, + AuthorizationFlags options, + const char** arguments, + FILE** pipe, + pid_t* pid) { + // pipe may be NULL, but this function needs one. In that case, use a local + // pipe. + FILE* local_pipe; + FILE** pipe_pointer; + if (pipe) { + pipe_pointer = pipe; + } else { + pipe_pointer = &local_pipe; + } + + // AuthorizationExecuteWithPrivileges wants |char* const*| for |arguments|, + // but it doesn't actually modify the arguments, and that type is kind of + // silly and callers probably aren't dealing with that. Put the cast here + // to make things a little easier on callers. + OSStatus status = AuthorizationExecuteWithPrivileges(authorization, + tool_path, + options, + (char* const*)arguments, + pipe_pointer); + if (status != errAuthorizationSuccess) { + return status; + } + + int line_pid = -1; + size_t line_length = 0; + char* line_c = fgetln(*pipe_pointer, &line_length); + if (line_c) { + if (line_length > 0 && line_c[line_length - 1] == '\n') { + // line_c + line_length is the start of the next line if there is one. + // Back up one character. + --line_length; + } + std::string line(line_c, line_length); + if (!base::StringToInt(line, &line_pid)) { + // StringToInt may have set line_pid to something, but if the conversion + // was imperfect, use -1. + LOG(ERROR) << "ExecuteWithPrivilegesAndGetPid: funny line: " << line; + line_pid = -1; + } + } else { + LOG(ERROR) << "ExecuteWithPrivilegesAndGetPid: no line"; + } + + if (!pipe) { + fclose(*pipe_pointer); + } + + if (pid) { + *pid = line_pid; + } + + return status; +} + +OSStatus ExecuteWithPrivilegesAndWait(AuthorizationRef authorization, + const char* tool_path, + AuthorizationFlags options, + const char** arguments, + FILE** pipe, + int* exit_status) { + pid_t pid; + OSStatus status = ExecuteWithPrivilegesAndGetPID(authorization, + tool_path, + options, + arguments, + pipe, + &pid); + if (status != errAuthorizationSuccess) { + return status; + } + + // exit_status may be NULL, but this function needs it. In that case, use a + // local version. + int local_exit_status; + int* exit_status_pointer; + if (exit_status) { + exit_status_pointer = exit_status; + } else { + exit_status_pointer = &local_exit_status; + } + + if (pid != -1) { + pid_t wait_result = HANDLE_EINTR(waitpid(pid, exit_status_pointer, 0)); + if (wait_result != pid) { + PLOG(ERROR) << "waitpid"; + *exit_status_pointer = -1; + } + } else { + *exit_status_pointer = -1; + } + + return status; +} + +} // namespace mac +} // namespace base
diff --git a/src/base/mac/bind_objc_block.h b/src/base/mac/bind_objc_block.h new file mode 100644 index 0000000..2d48c7d --- /dev/null +++ b/src/base/mac/bind_objc_block.h
@@ -0,0 +1,18 @@ +// 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_MAC_BIND_OBJC_BLOCK_H_ +#define BASE_MAC_BIND_OBJC_BLOCK_H_ + +#include "base/base_export.h" +#include "base/callback_forward.h" + +namespace base { + +// Construct a closure from an objective-C block. +BASE_EXPORT base::Closure BindBlock(void(^block)()); + +} // namespace base + +#endif // BASE_MAC_BIND_OBJC_BLOCK_H_
diff --git a/src/base/mac/bind_objc_block.mm b/src/base/mac/bind_objc_block.mm new file mode 100644 index 0000000..81472d0 --- /dev/null +++ b/src/base/mac/bind_objc_block.mm
@@ -0,0 +1,26 @@ +// 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. + +#import "base/mac/bind_objc_block.h" + +#include "base/bind.h" +#include "base/memory/scoped_nsobject.h" + +namespace { + +// Run the block contained in the parameter. +void RunBlock(scoped_nsobject<id> block) { + void(^extracted_block)() = block.get(); + extracted_block(); +} + +} // namespace + +namespace base { + +base::Closure BindBlock(void(^block)()) { + return base::Bind(&RunBlock, scoped_nsobject<id>([block copy])); +} + +} // namespace base
diff --git a/src/base/mac/bind_objc_block_unittest.mm b/src/base/mac/bind_objc_block_unittest.mm new file mode 100644 index 0000000..17fbe6e --- /dev/null +++ b/src/base/mac/bind_objc_block_unittest.mm
@@ -0,0 +1,42 @@ +// 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. + +#import "base/mac/bind_objc_block.h" + +#include "base/callback.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +TEST(BindObjcBlockTest, TestScopedClosureRunnerExitScope) { + int run_count = 0; + int* ptr = &run_count; + { + base::ScopedClosureRunner runner(base::BindBlock(^{ + (*ptr)++; + })); + EXPECT_EQ(0, run_count); + } + EXPECT_EQ(1, run_count); +} + +TEST(BindObjcBlockTest, TestScopedClosureRunnerRelease) { + int run_count = 0; + int* ptr = &run_count; + base::Closure c; + { + base::ScopedClosureRunner runner(base::BindBlock(^{ + (*ptr)++; + })); + c = runner.Release(); + EXPECT_EQ(0, run_count); + } + EXPECT_EQ(0, run_count); + c.Run(); + EXPECT_EQ(1, run_count); +} + +} // namespace
diff --git a/src/base/mac/bundle_locations.h b/src/base/mac/bundle_locations.h new file mode 100644 index 0000000..dd84b59 --- /dev/null +++ b/src/base/mac/bundle_locations.h
@@ -0,0 +1,66 @@ +// 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_MAC_BUNDLE_LOCATIONS_H_ +#define BASE_MAC_BUNDLE_LOCATIONS_H_ + +#include "base/base_export.h" +#include "base/file_path.h" + +#if defined(__OBJC__) +#import <Foundation/Foundation.h> +#else // __OBJC__ +class NSBundle; +class NSString; +#endif // __OBJC__ + +class FilePath; + +namespace base { +namespace mac { + +// This file provides several functions to explicitly request the various +// component bundles of Chrome. Please use these methods rather than calling +// +[NSBundle mainBundle] or CFBundleGetMainBundle(). +// +// Terminology +// - "Outer Bundle" - This is the main bundle for Chrome; it's what +// +[NSBundle mainBundle] returns when Chrome is launched normally. +// +// - "Main Bundle" - This is the bundle from which Chrome was launched. +// This will be the same as the outer bundle except when Chrome is launched +// via an app shortcut, in which case this will return the app shortcut's +// bundle rather than the main Chrome bundle. +// +// - "Framework Bundle" - This is the bundle corresponding to the Chrome +// framework. +// +// Guidelines for use: +// - To access a resource, the Framework bundle should be used. +// - If the choice is between the Outer or Main bundles then please choose +// carefully. Most often the Outer bundle will be the right choice, but for +// cases such as adding an app to the "launch on startup" list, the Main +// bundle is probably the one to use. + +// Methods for retrieving the various bundles. +BASE_EXPORT NSBundle* MainBundle(); +BASE_EXPORT FilePath MainBundlePath(); +BASE_EXPORT NSBundle* OuterBundle(); +BASE_EXPORT FilePath OuterBundlePath(); +BASE_EXPORT NSBundle* FrameworkBundle(); +BASE_EXPORT FilePath FrameworkBundlePath(); + +// Set the bundle that the preceding functions will return, overriding the +// default values. Restore the default by passing in |nil|. +BASE_EXPORT void SetOverrideOuterBundle(NSBundle* bundle); +BASE_EXPORT void SetOverrideFrameworkBundle(NSBundle* bundle); + +// Same as above but accepting a FilePath argument. +BASE_EXPORT void SetOverrideOuterBundlePath(const FilePath& file_path); +BASE_EXPORT void SetOverrideFrameworkBundlePath(const FilePath& file_path); + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_BUNDLE_LOCATIONS_H_
diff --git a/src/base/mac/bundle_locations.mm b/src/base/mac/bundle_locations.mm new file mode 100644 index 0000000..363b7ea --- /dev/null +++ b/src/base/mac/bundle_locations.mm
@@ -0,0 +1,83 @@ +// 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/mac/bundle_locations.h" + +#include "base/logging.h" +#include "base/mac/foundation_util.h" +#include "base/sys_string_conversions.h" + +namespace base { +namespace mac { + +// NSBundle isn't threadsafe, all functions in this file must be called on the +// main thread. +static NSBundle* g_override_framework_bundle = nil; +static NSBundle* g_override_outer_bundle = nil; + +NSBundle* MainBundle() { + return [NSBundle mainBundle]; +} + +FilePath MainBundlePath() { + NSBundle* bundle = MainBundle(); + return NSStringToFilePath([bundle bundlePath]); +} + +NSBundle* OuterBundle() { + if (g_override_outer_bundle) + return g_override_outer_bundle; + return [NSBundle mainBundle]; +} + +FilePath OuterBundlePath() { + NSBundle* bundle = OuterBundle(); + return NSStringToFilePath([bundle bundlePath]); +} + +NSBundle* FrameworkBundle() { + if (g_override_framework_bundle) + return g_override_framework_bundle; + return [NSBundle mainBundle]; +} + +FilePath FrameworkBundlePath() { + NSBundle* bundle = FrameworkBundle(); + return NSStringToFilePath([bundle bundlePath]); +} + +static void AssignOverrideBundle(NSBundle* new_bundle, + NSBundle** override_bundle) { + if (new_bundle != *override_bundle) { + [*override_bundle release]; + *override_bundle = [new_bundle retain]; + } +} + +static void AssignOverridePath(const FilePath& file_path, + NSBundle** override_bundle) { + NSString* path = base::SysUTF8ToNSString(file_path.value()); + NSBundle* new_bundle = [NSBundle bundleWithPath:path]; + DCHECK(new_bundle) << "Failed to load the bundle at " << file_path.value(); + AssignOverrideBundle(new_bundle, override_bundle); +} + +void SetOverrideOuterBundle(NSBundle* bundle) { + AssignOverrideBundle(bundle, &g_override_outer_bundle); +} + +void SetOverrideFrameworkBundle(NSBundle* bundle) { + AssignOverrideBundle(bundle, &g_override_framework_bundle); +} + +void SetOverrideOuterBundlePath(const FilePath& file_path) { + AssignOverridePath(file_path, &g_override_outer_bundle); +} + +void SetOverrideFrameworkBundlePath(const FilePath& file_path) { + AssignOverridePath(file_path, &g_override_framework_bundle); +} + +} // namespace mac +} // namespace base
diff --git a/src/base/mac/cocoa_protocols.h b/src/base/mac/cocoa_protocols.h new file mode 100644 index 0000000..314e7fa --- /dev/null +++ b/src/base/mac/cocoa_protocols.h
@@ -0,0 +1,41 @@ +// 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_COCOA_PROTOCOLS_MAC_H_ +#define BASE_COCOA_PROTOCOLS_MAC_H_ + +#import <Cocoa/Cocoa.h> + +// GTM also maintinas a list of empty protocols, but only the ones the library +// requires. Augment that below. +#import "third_party/GTM/GTMDefines.h" + +// New Mac OS X SDKs introduce new protocols used for delegates. These +// protocol defintions aren't not present in earlier releases of the Mac OS X +// SDK. In order to support building against the new SDK, which requires +// delegates to conform to these protocols, and earlier SDKs, which do not +// define these protocols at all, this file will provide empty protocol +// definitions when used with earlier SDK versions. + +#define DEFINE_EMPTY_PROTOCOL(p) \ +@protocol p \ +@end + +#if !defined(MAC_OS_X_VERSION_10_7) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 + +DEFINE_EMPTY_PROTOCOL(NSDraggingDestination) + +#endif // MAC_OS_X_VERSION_10_7 + +#if !defined(MAC_OS_X_VERSION_10_8) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8 + +DEFINE_EMPTY_PROTOCOL(NSUserNotificationCenterDelegate) + +#endif // MAC_OS_X_VERSION_10_8 + +#undef DEFINE_EMPTY_PROTOCOL + +#endif // BASE_COCOA_PROTOCOLS_MAC_H_
diff --git a/src/base/mac/crash_logging.h b/src/base/mac/crash_logging.h new file mode 100644 index 0000000..7e83eae --- /dev/null +++ b/src/base/mac/crash_logging.h
@@ -0,0 +1,58 @@ +// 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_MAC_CRASH_LOGGING_H_ +#define BASE_MAC_CRASH_LOGGING_H_ + +#include "base/base_export.h" + +#if __OBJC__ +#import "base/memory/scoped_nsobject.h" + +@class NSString; +#else +class NSString; +#endif + +namespace base { +namespace mac { + +typedef void (*SetCrashKeyValueFuncPtr)(NSString*, NSString*); +typedef void (*ClearCrashKeyValueFuncPtr)(NSString*); + +// Set the low level functions used to supply crash keys to Breakpad. +BASE_EXPORT void SetCrashKeyFunctions(SetCrashKeyValueFuncPtr set_key_func, + ClearCrashKeyValueFuncPtr clear_key_func); + +// Set and clear meta information for Minidump. +// IMPORTANT: On OS X, the key/value pairs are sent to the crash server +// out of bounds and not recorded on disk in the minidump, this means +// that if you look at the minidump file locally you won't see them! +BASE_EXPORT void SetCrashKeyValue(NSString* key, NSString* val); +BASE_EXPORT void ClearCrashKey(NSString* key); + +// Format |count| items from |addresses| using %p, and set the +// resulting string as value for crash key |key|. A maximum of 23 +// items will be encoded, since breakpad limits values to 255 bytes. +BASE_EXPORT void SetCrashKeyFromAddresses(NSString* key, + const void* const* addresses, + size_t count); + +#if __OBJC__ + +class BASE_EXPORT ScopedCrashKey { + public: + ScopedCrashKey(NSString* key, NSString* value); + ~ScopedCrashKey(); + private: + scoped_nsobject<NSString> crash_key_; + DISALLOW_COPY_AND_ASSIGN(ScopedCrashKey); +}; + +#endif // __OBJC__ + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_CRASH_LOGGING_H_
diff --git a/src/base/mac/crash_logging.mm b/src/base/mac/crash_logging.mm new file mode 100644 index 0000000..e18a3fb --- /dev/null +++ b/src/base/mac/crash_logging.mm
@@ -0,0 +1,69 @@ +// 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/mac/crash_logging.h" + +#import <Foundation/Foundation.h> + +#include "base/logging.h" + +namespace base { +namespace mac { + +static SetCrashKeyValueFuncPtr g_set_key_func; +static ClearCrashKeyValueFuncPtr g_clear_key_func; + +void SetCrashKeyFunctions(SetCrashKeyValueFuncPtr set_key_func, + ClearCrashKeyValueFuncPtr clear_key_func) { + g_set_key_func = set_key_func; + g_clear_key_func = clear_key_func; +} + +void SetCrashKeyValue(NSString* key, NSString* val) { + if (g_set_key_func) + g_set_key_func(key, val); +} + +void ClearCrashKey(NSString* key) { + if (g_clear_key_func) + g_clear_key_func(key); +} + +void SetCrashKeyFromAddresses(NSString* key, + const void* const* addresses, + size_t count) { + NSString* value = @"<null>"; + if (addresses && count) { + const size_t kBreakpadValueMax = 255; + + NSMutableArray* hexBacktrace = [NSMutableArray arrayWithCapacity:count]; + size_t length = 0; + for (size_t i = 0; i < count; ++i) { + NSString* s = [NSString stringWithFormat:@"%p", addresses[i]]; + length += 1 + [s length]; + if (length > kBreakpadValueMax) + break; + [hexBacktrace addObject:s]; + } + value = [hexBacktrace componentsJoinedByString:@" "]; + + // Warn someone if this exceeds the breakpad limits. + DCHECK_LE(strlen([value UTF8String]), kBreakpadValueMax); + } + base::mac::SetCrashKeyValue(key, value); +} + +ScopedCrashKey::ScopedCrashKey(NSString* key, NSString* value) + : crash_key_([key retain]) { + if (g_set_key_func) + g_set_key_func(crash_key_, value); +} + +ScopedCrashKey::~ScopedCrashKey() { + if (g_clear_key_func) + g_clear_key_func(crash_key_); +} + +} // namespace mac +} // namespace base
diff --git a/src/base/mac/foundation_util.h b/src/base/mac/foundation_util.h new file mode 100644 index 0000000..e6ad784 --- /dev/null +++ b/src/base/mac/foundation_util.h
@@ -0,0 +1,358 @@ +// 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_MAC_FOUNDATION_UTIL_H_ +#define BASE_MAC_FOUNDATION_UTIL_H_ + +#include <CoreFoundation/CoreFoundation.h> + +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/logging.h" +#include "base/mac/scoped_cftyperef.h" + +#if defined(__OBJC__) +#import <Foundation/Foundation.h> +#else // __OBJC__ +#include <CoreFoundation/CoreFoundation.h> +class NSBundle; +class NSString; +#endif // __OBJC__ + +#if defined(OS_IOS) +#include <CoreText/CoreText.h> +#else +#include <ApplicationServices/ApplicationServices.h> +#endif + +class FilePath; + +// Adapted from NSPathUtilities.h and NSObjCRuntime.h. +#if __LP64__ || NS_BUILD_32_LIKE_64 +typedef unsigned long NSSearchPathDirectory; +typedef unsigned long NSSearchPathDomainMask; +#else +typedef unsigned int NSSearchPathDirectory; +typedef unsigned int NSSearchPathDomainMask; +#endif + +typedef struct OpaqueSecTrustRef* SecACLRef; +typedef struct OpaqueSecTrustedApplicationRef* SecTrustedApplicationRef; + +namespace base { +namespace mac { + +// Returns true if the application is running from a bundle +BASE_EXPORT bool AmIBundled(); +BASE_EXPORT void SetOverrideAmIBundled(bool value); + +// Returns true if this process is marked as a "Background only process". +BASE_EXPORT bool IsBackgroundOnlyProcess(); + +// Returns the path to a resource within the framework bundle. +BASE_EXPORT FilePath PathForFrameworkBundleResource(CFStringRef resourceName); + +// Returns the creator code associated with the CFBundleRef at bundle. +OSType CreatorCodeForCFBundleRef(CFBundleRef bundle); + +// Returns the creator code associated with this application, by calling +// CreatorCodeForCFBundleRef for the application's main bundle. If this +// information cannot be determined, returns kUnknownType ('????'). This +// does not respect the override app bundle because it's based on CFBundle +// instead of NSBundle, and because callers probably don't want the override +// app bundle's creator code anyway. +BASE_EXPORT OSType CreatorCodeForApplication(); + +// Searches for directories for the given key in only the given |domain_mask|. +// If found, fills result (which must always be non-NULL) with the +// first found directory and returns true. Otherwise, returns false. +BASE_EXPORT bool GetSearchPathDirectory(NSSearchPathDirectory directory, + NSSearchPathDomainMask domain_mask, + FilePath* result); + +// Searches for directories for the given key in only the local domain. +// If found, fills result (which must always be non-NULL) with the +// first found directory and returns true. Otherwise, returns false. +BASE_EXPORT bool GetLocalDirectory(NSSearchPathDirectory directory, + FilePath* result); + +// Searches for directories for the given key in only the user domain. +// If found, fills result (which must always be non-NULL) with the +// first found directory and returns true. Otherwise, returns false. +BASE_EXPORT bool GetUserDirectory(NSSearchPathDirectory directory, + FilePath* result); + +// Returns the ~/Library directory. +BASE_EXPORT FilePath GetUserLibraryPath(); + +// Takes a path to an (executable) binary and tries to provide the path to an +// application bundle containing it. It takes the outermost bundle that it can +// find (so for "/Foo/Bar.app/.../Baz.app/..." it produces "/Foo/Bar.app"). +// |exec_name| - path to the binary +// returns - path to the application bundle, or empty on error +BASE_EXPORT FilePath GetAppBundlePath(const FilePath& exec_name); + +#define TYPE_NAME_FOR_CF_TYPE_DECL(TypeCF) \ +BASE_EXPORT std::string TypeNameForCFType(TypeCF##Ref); + +TYPE_NAME_FOR_CF_TYPE_DECL(CFArray); +TYPE_NAME_FOR_CF_TYPE_DECL(CFBag); +TYPE_NAME_FOR_CF_TYPE_DECL(CFBoolean); +TYPE_NAME_FOR_CF_TYPE_DECL(CFData); +TYPE_NAME_FOR_CF_TYPE_DECL(CFDate); +TYPE_NAME_FOR_CF_TYPE_DECL(CFDictionary); +TYPE_NAME_FOR_CF_TYPE_DECL(CFNull); +TYPE_NAME_FOR_CF_TYPE_DECL(CFNumber); +TYPE_NAME_FOR_CF_TYPE_DECL(CFSet); +TYPE_NAME_FOR_CF_TYPE_DECL(CFString); +TYPE_NAME_FOR_CF_TYPE_DECL(CFURL); +TYPE_NAME_FOR_CF_TYPE_DECL(CFUUID); + +TYPE_NAME_FOR_CF_TYPE_DECL(CGColor); + +TYPE_NAME_FOR_CF_TYPE_DECL(CTFont); +TYPE_NAME_FOR_CF_TYPE_DECL(CTRun); + +#undef TYPE_NAME_FOR_CF_TYPE_DECL + +// Retain/release calls for memory management in C++. +BASE_EXPORT void NSObjectRetain(void* obj); +BASE_EXPORT void NSObjectRelease(void* obj); + +// CFTypeRefToNSObjectAutorelease transfers ownership of a Core Foundation +// object (one derived from CFTypeRef) to the Foundation memory management +// system. In a traditional managed-memory environment, cf_object is +// autoreleased and returned as an NSObject. In a garbage-collected +// environment, cf_object is marked as eligible for garbage collection. +// +// This function should only be used to convert a concrete CFTypeRef type to +// its equivalent "toll-free bridged" NSObject subclass, for example, +// converting a CFStringRef to NSString. +// +// By calling this function, callers relinquish any ownership claim to +// cf_object. In a managed-memory environment, the object's ownership will be +// managed by the innermost NSAutoreleasePool, so after this function returns, +// callers should not assume that cf_object is valid any longer than the +// returned NSObject. +// +// Returns an id, typed here for C++'s sake as a void*. +BASE_EXPORT void* CFTypeRefToNSObjectAutorelease(CFTypeRef cf_object); + +// Returns the base bundle ID, which can be set by SetBaseBundleID but +// defaults to a reasonable string. This never returns NULL. BaseBundleID +// returns a pointer to static storage that must not be freed. +BASE_EXPORT const char* BaseBundleID(); + +// Sets the base bundle ID to override the default. The implementation will +// make its own copy of new_base_bundle_id. +BASE_EXPORT void SetBaseBundleID(const char* new_base_bundle_id); + +} // namespace mac +} // namespace base + +#if !defined(__OBJC__) +#define OBJC_CPP_CLASS_DECL(x) class x; +#else // __OBJC__ +#define OBJC_CPP_CLASS_DECL(x) +#endif // __OBJC__ + +// Convert toll-free bridged CFTypes to NSTypes and vice-versa. This does not +// autorelease |cf_val|. This is useful for the case where there is a CFType in +// a call that expects an NSType and the compiler is complaining about const +// casting problems. +// The calls are used like this: +// NSString *foo = CFToNSCast(CFSTR("Hello")); +// CFStringRef foo2 = NSToCFCast(@"Hello"); +// The macro magic below is to enforce safe casting. It could possibly have +// been done using template function specialization, but template function +// specialization doesn't always work intuitively, +// (http://www.gotw.ca/publications/mill17.htm) so the trusty combination +// of macros and function overloading is used instead. + +#define CF_TO_NS_CAST_DECL(TypeCF, TypeNS) \ +OBJC_CPP_CLASS_DECL(TypeNS) \ +\ +namespace base { \ +namespace mac { \ +BASE_EXPORT TypeNS* CFToNSCast(TypeCF##Ref cf_val); \ +BASE_EXPORT TypeCF##Ref NSToCFCast(TypeNS* ns_val); \ +} \ +} + +#define CF_TO_NS_MUTABLE_CAST_DECL(name) \ +CF_TO_NS_CAST_DECL(CF##name, NS##name) \ +OBJC_CPP_CLASS_DECL(NSMutable##name) \ +\ +namespace base { \ +namespace mac { \ +BASE_EXPORT NSMutable##name* CFToNSCast(CFMutable##name##Ref cf_val); \ +BASE_EXPORT CFMutable##name##Ref NSToCFCast(NSMutable##name* ns_val); \ +} \ +} + +// List of toll-free bridged types taken from: +// http://www.cocoadev.com/index.pl?TollFreeBridged + +CF_TO_NS_MUTABLE_CAST_DECL(Array); +CF_TO_NS_MUTABLE_CAST_DECL(AttributedString); +CF_TO_NS_CAST_DECL(CFCalendar, NSCalendar); +CF_TO_NS_MUTABLE_CAST_DECL(CharacterSet); +CF_TO_NS_MUTABLE_CAST_DECL(Data); +CF_TO_NS_CAST_DECL(CFDate, NSDate); +CF_TO_NS_MUTABLE_CAST_DECL(Dictionary); +CF_TO_NS_CAST_DECL(CFError, NSError); +CF_TO_NS_CAST_DECL(CFLocale, NSLocale); +CF_TO_NS_CAST_DECL(CFNumber, NSNumber); +CF_TO_NS_CAST_DECL(CFRunLoopTimer, NSTimer); +CF_TO_NS_CAST_DECL(CFTimeZone, NSTimeZone); +CF_TO_NS_MUTABLE_CAST_DECL(Set); +CF_TO_NS_CAST_DECL(CFReadStream, NSInputStream); +CF_TO_NS_CAST_DECL(CFWriteStream, NSOutputStream); +CF_TO_NS_MUTABLE_CAST_DECL(String); +CF_TO_NS_CAST_DECL(CFURL, NSURL); + +#undef CF_TO_NS_CAST_DECL +#undef CF_TO_NS_MUTABLE_CAST_DECL +#undef OBJC_CPP_CLASS_DECL + +namespace base { +namespace mac { + +// CFCast<>() and CFCastStrict<>() cast a basic CFTypeRef to a more +// specific CoreFoundation type. The compatibility of the passed +// object is found by comparing its opaque type against the +// requested type identifier. If the supplied object is not +// compatible with the requested return type, CFCast<>() returns +// NULL and CFCastStrict<>() will DCHECK. Providing a NULL pointer +// to either variant results in NULL being returned without +// triggering any DCHECK. +// +// Example usage: +// CFNumberRef some_number = base::mac::CFCast<CFNumberRef>( +// CFArrayGetValueAtIndex(array, index)); +// +// CFTypeRef hello = CFSTR("hello world"); +// CFStringRef some_string = base::mac::CFCastStrict<CFStringRef>(hello); + +template<typename T> +T CFCast(const CFTypeRef& cf_val); + +template<typename T> +T CFCastStrict(const CFTypeRef& cf_val); + +#define CF_CAST_DECL(TypeCF) \ +template<> BASE_EXPORT TypeCF##Ref \ +CFCast<TypeCF##Ref>(const CFTypeRef& cf_val);\ +\ +template<> BASE_EXPORT TypeCF##Ref \ +CFCastStrict<TypeCF##Ref>(const CFTypeRef& cf_val); + +CF_CAST_DECL(CFArray); +CF_CAST_DECL(CFBag); +CF_CAST_DECL(CFBoolean); +CF_CAST_DECL(CFData); +CF_CAST_DECL(CFDate); +CF_CAST_DECL(CFDictionary); +CF_CAST_DECL(CFNull); +CF_CAST_DECL(CFNumber); +CF_CAST_DECL(CFSet); +CF_CAST_DECL(CFString); +CF_CAST_DECL(CFURL); +CF_CAST_DECL(CFUUID); + +CF_CAST_DECL(CGColor); + +CF_CAST_DECL(CTFont); +CF_CAST_DECL(CTRun); + +CF_CAST_DECL(SecACL); +CF_CAST_DECL(SecTrustedApplication); + +#undef CF_CAST_DECL + +#if defined(__OBJC__) + +// ObjCCast<>() and ObjCCastStrict<>() cast a basic id to a more +// specific (NSObject-derived) type. The compatibility of the passed +// object is found by checking if it's a kind of the requested type +// identifier. If the supplied object is not compatible with the +// requested return type, ObjCCast<>() returns nil and +// ObjCCastStrict<>() will DCHECK. Providing a nil pointer to either +// variant results in nil being returned without triggering any DCHECK. +// +// The strict variant is useful when retrieving a value from a +// collection which only has values of a specific type, e.g. an +// NSArray of NSStrings. The non-strict variant is useful when +// retrieving values from data that you can't fully control. For +// example, a plist read from disk may be beyond your exclusive +// control, so you'd only want to check that the values you retrieve +// from it are of the expected types, but not crash if they're not. +// +// Example usage: +// NSString* version = base::mac::ObjCCast<NSString>( +// [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]); +// +// NSString* str = base::mac::ObjCCastStrict<NSString>( +// [ns_arr_of_ns_strs objectAtIndex:0]); +template<typename T> +T* ObjCCast(id objc_val) { + if ([objc_val isKindOfClass:[T class]]) { + return reinterpret_cast<T*>(objc_val); + } + return nil; +} + +template<typename T> +T* ObjCCastStrict(id objc_val) { + T* rv = ObjCCast<T>(objc_val); + DCHECK(objc_val == nil || rv); + return rv; +} + +#endif // defined(__OBJC__) + +// Helper function for GetValueFromDictionary to create the error message +// that appears when a type mismatch is encountered. +BASE_EXPORT std::string GetValueFromDictionaryErrorMessage( + CFStringRef key, const std::string& expected_type, CFTypeRef value); + +// Utility function to pull out a value from a dictionary, check its type, and +// return it. Returns NULL if the key is not present or of the wrong type. +template<typename T> +T GetValueFromDictionary(CFDictionaryRef dict, CFStringRef key) { + CFTypeRef value = CFDictionaryGetValue(dict, key); + T value_specific = CFCast<T>(value); + + if (value && !value_specific) { + std::string expected_type = TypeNameForCFType(value_specific); + DLOG(WARNING) << GetValueFromDictionaryErrorMessage(key, + expected_type, + value); + } + + return value_specific; +} + +// Converts |path| to an autoreleased NSString. Returns nil if |path| is empty. +BASE_EXPORT NSString* FilePathToNSString(const FilePath& path); + +// Converts |str| to a FilePath. Returns an empty path if |str| is nil. +BASE_EXPORT FilePath NSStringToFilePath(NSString* str); + +} // namespace mac +} // namespace base + +// Stream operations for CFTypes. They can be used with NSTypes as well +// by using the NSToCFCast methods above. +// e.g. LOG(INFO) << base::mac::NSToCFCast(@"foo"); +// Operator << can not be overloaded for ObjectiveC types as the compiler +// can not distinguish between overloads for id with overloads for void*. +BASE_EXPORT extern std::ostream& operator<<(std::ostream& o, + const CFErrorRef err); +BASE_EXPORT extern std::ostream& operator<<(std::ostream& o, + const CFStringRef str); + +#endif // BASE_MAC_FOUNDATION_UTIL_H_
diff --git a/src/base/mac/foundation_util.mm b/src/base/mac/foundation_util.mm new file mode 100644 index 0000000..3fd9a57 --- /dev/null +++ b/src/base/mac/foundation_util.mm
@@ -0,0 +1,401 @@ +// 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/mac/foundation_util.h" + +#include <stdlib.h> +#include <string.h> + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/mac/bundle_locations.h" +#include "base/mac/mac_logging.h" +#include "base/sys_string_conversions.h" + +#if !defined(OS_IOS) +extern "C" { +CFTypeID SecACLGetTypeID(); +CFTypeID SecTrustedApplicationGetTypeID(); +} // extern "C" +#endif + +namespace base { +namespace mac { + +static bool g_override_am_i_bundled = false; +static bool g_override_am_i_bundled_value = false; + +// Adapted from http://developer.apple.com/carbon/tipsandtricks.html#AmIBundled +static bool UncachedAmIBundled() { +#if defined(OS_IOS) + // All apps are bundled on iOS + return true; +#else + if (g_override_am_i_bundled) + return g_override_am_i_bundled_value; + + ProcessSerialNumber psn = {0, kCurrentProcess}; + + FSRef fsref; + OSStatus pbErr; + if ((pbErr = GetProcessBundleLocation(&psn, &fsref)) != noErr) { + OSSTATUS_DLOG(ERROR, pbErr) << "GetProcessBundleLocation failed"; + return false; + } + + FSCatalogInfo info; + OSErr fsErr; + if ((fsErr = FSGetCatalogInfo(&fsref, kFSCatInfoNodeFlags, &info, + NULL, NULL, NULL)) != noErr) { + OSSTATUS_DLOG(ERROR, fsErr) << "FSGetCatalogInfo failed"; + return false; + } + + return info.nodeFlags & kFSNodeIsDirectoryMask; +#endif +} + +bool AmIBundled() { + // If the return value is not cached, this function will return different + // values depending on when it's called. This confuses some client code, see + // http://crbug.com/63183 . + static bool result = UncachedAmIBundled(); + DCHECK_EQ(result, UncachedAmIBundled()) + << "The return value of AmIBundled() changed. This will confuse tests. " + << "Call SetAmIBundled() override manually if your test binary " + << "delay-loads the framework."; + return result; +} + +void SetOverrideAmIBundled(bool value) { +#if defined(OS_IOS) + // It doesn't make sense not to be bundled on iOS. + if (!value) + NOTREACHED(); +#endif + g_override_am_i_bundled = true; + g_override_am_i_bundled_value = value; +} + +bool IsBackgroundOnlyProcess() { + // This function really does want to examine NSBundle's idea of the main + // bundle dictionary. It needs to look at the actual running .app's + // Info.plist to access its LSUIElement property. + NSDictionary* info_dictionary = [base::mac::MainBundle() infoDictionary]; + return [[info_dictionary objectForKey:@"LSUIElement"] boolValue] != NO; +} + +FilePath PathForFrameworkBundleResource(CFStringRef resourceName) { + NSBundle* bundle = base::mac::FrameworkBundle(); + NSString* resourcePath = [bundle pathForResource:(NSString*)resourceName + ofType:nil]; + return NSStringToFilePath(resourcePath); +} + +OSType CreatorCodeForCFBundleRef(CFBundleRef bundle) { + OSType creator = kUnknownType; + CFBundleGetPackageInfo(bundle, NULL, &creator); + return creator; +} + +OSType CreatorCodeForApplication() { + CFBundleRef bundle = CFBundleGetMainBundle(); + if (!bundle) + return kUnknownType; + + return CreatorCodeForCFBundleRef(bundle); +} + +bool GetSearchPathDirectory(NSSearchPathDirectory directory, + NSSearchPathDomainMask domain_mask, + FilePath* result) { + DCHECK(result); + NSArray* dirs = + NSSearchPathForDirectoriesInDomains(directory, domain_mask, YES); + if ([dirs count] < 1) { + return false; + } + *result = NSStringToFilePath([dirs objectAtIndex:0]); + return true; +} + +bool GetLocalDirectory(NSSearchPathDirectory directory, FilePath* result) { + return GetSearchPathDirectory(directory, NSLocalDomainMask, result); +} + +bool GetUserDirectory(NSSearchPathDirectory directory, FilePath* result) { + return GetSearchPathDirectory(directory, NSUserDomainMask, result); +} + +FilePath GetUserLibraryPath() { + FilePath user_library_path; + if (!GetUserDirectory(NSLibraryDirectory, &user_library_path)) { + DLOG(WARNING) << "Could not get user library path"; + } + return user_library_path; +} + +// Takes a path to an (executable) binary and tries to provide the path to an +// application bundle containing it. It takes the outermost bundle that it can +// find (so for "/Foo/Bar.app/.../Baz.app/..." it produces "/Foo/Bar.app"). +// |exec_name| - path to the binary +// returns - path to the application bundle, or empty on error +FilePath GetAppBundlePath(const FilePath& exec_name) { + const char kExt[] = ".app"; + const size_t kExtLength = arraysize(kExt) - 1; + + // Split the path into components. + std::vector<std::string> components; + exec_name.GetComponents(&components); + + // It's an error if we don't get any components. + if (!components.size()) + return FilePath(); + + // Don't prepend '/' to the first component. + std::vector<std::string>::const_iterator it = components.begin(); + std::string bundle_name = *it; + DCHECK_GT(it->length(), 0U); + // If the first component ends in ".app", we're already done. + if (it->length() > kExtLength && + !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength)) + return FilePath(bundle_name); + + // The first component may be "/" or "//", etc. Only append '/' if it doesn't + // already end in '/'. + if (bundle_name[bundle_name.length() - 1] != '/') + bundle_name += '/'; + + // Go through the remaining components. + for (++it; it != components.end(); ++it) { + DCHECK_GT(it->length(), 0U); + + bundle_name += *it; + + // If the current component ends in ".app", we're done. + if (it->length() > kExtLength && + !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength)) + return FilePath(bundle_name); + + // Separate this component from the next one. + bundle_name += '/'; + } + + return FilePath(); +} + +#define TYPE_NAME_FOR_CF_TYPE_DEFN(TypeCF) \ +std::string TypeNameForCFType(TypeCF##Ref) { \ + return #TypeCF; \ +} + +TYPE_NAME_FOR_CF_TYPE_DEFN(CFArray); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFBag); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFBoolean); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFData); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFDate); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFDictionary); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFNull); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFNumber); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFSet); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFString); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFURL); +TYPE_NAME_FOR_CF_TYPE_DEFN(CFUUID); + +TYPE_NAME_FOR_CF_TYPE_DEFN(CGColor); + +TYPE_NAME_FOR_CF_TYPE_DEFN(CTFont); +TYPE_NAME_FOR_CF_TYPE_DEFN(CTRun); + +#undef TYPE_NAME_FOR_CF_TYPE_DEFN + +void NSObjectRetain(void* obj) { + id<NSObject> nsobj = static_cast<id<NSObject> >(obj); + [nsobj retain]; +} + +void NSObjectRelease(void* obj) { + id<NSObject> nsobj = static_cast<id<NSObject> >(obj); + [nsobj release]; +} + +void* CFTypeRefToNSObjectAutorelease(CFTypeRef cf_object) { + // When GC is on, NSMakeCollectable marks cf_object for GC and autorelease + // is a no-op. + // + // In the traditional GC-less environment, NSMakeCollectable is a no-op, + // and cf_object is autoreleased, balancing out the caller's ownership claim. + // + // NSMakeCollectable returns nil when used on a NULL object. + return [NSMakeCollectable(cf_object) autorelease]; +} + +static const char* base_bundle_id; + +const char* BaseBundleID() { + if (base_bundle_id) { + return base_bundle_id; + } + +#if defined(GOOGLE_CHROME_BUILD) + return "com.google.Chrome"; +#else + return "org.chromium.Chromium"; +#endif +} + +void SetBaseBundleID(const char* new_base_bundle_id) { + if (new_base_bundle_id != base_bundle_id) { + free((void*)base_bundle_id); + base_bundle_id = new_base_bundle_id ? strdup(new_base_bundle_id) : NULL; + } +} + +// Definitions for the corresponding CF_TO_NS_CAST_DECL macros in +// foundation_util.h. +#define CF_TO_NS_CAST_DEFN(TypeCF, TypeNS) \ +\ +TypeNS* CFToNSCast(TypeCF##Ref cf_val) { \ + DCHECK(!cf_val || TypeCF##GetTypeID() == CFGetTypeID(cf_val)); \ + TypeNS* ns_val = \ + const_cast<TypeNS*>(reinterpret_cast<const TypeNS*>(cf_val)); \ + return ns_val; \ +} \ +\ +TypeCF##Ref NSToCFCast(TypeNS* ns_val) { \ + TypeCF##Ref cf_val = reinterpret_cast<TypeCF##Ref>(ns_val); \ + DCHECK(!cf_val || TypeCF##GetTypeID() == CFGetTypeID(cf_val)); \ + return cf_val; \ +} + +#define CF_TO_NS_MUTABLE_CAST_DEFN(name) \ +CF_TO_NS_CAST_DEFN(CF##name, NS##name) \ +\ +NSMutable##name* CFToNSCast(CFMutable##name##Ref cf_val) { \ + DCHECK(!cf_val || CF##name##GetTypeID() == CFGetTypeID(cf_val)); \ + NSMutable##name* ns_val = reinterpret_cast<NSMutable##name*>(cf_val); \ + return ns_val; \ +} \ +\ +CFMutable##name##Ref NSToCFCast(NSMutable##name* ns_val) { \ + CFMutable##name##Ref cf_val = \ + reinterpret_cast<CFMutable##name##Ref>(ns_val); \ + DCHECK(!cf_val || CF##name##GetTypeID() == CFGetTypeID(cf_val)); \ + return cf_val; \ +} + +CF_TO_NS_MUTABLE_CAST_DEFN(Array); +CF_TO_NS_MUTABLE_CAST_DEFN(AttributedString); +CF_TO_NS_CAST_DEFN(CFCalendar, NSCalendar); +CF_TO_NS_MUTABLE_CAST_DEFN(CharacterSet); +CF_TO_NS_MUTABLE_CAST_DEFN(Data); +CF_TO_NS_CAST_DEFN(CFDate, NSDate); +CF_TO_NS_MUTABLE_CAST_DEFN(Dictionary); +CF_TO_NS_CAST_DEFN(CFError, NSError); +CF_TO_NS_CAST_DEFN(CFLocale, NSLocale); +CF_TO_NS_CAST_DEFN(CFNumber, NSNumber); +CF_TO_NS_CAST_DEFN(CFRunLoopTimer, NSTimer); +CF_TO_NS_CAST_DEFN(CFTimeZone, NSTimeZone); +CF_TO_NS_MUTABLE_CAST_DEFN(Set); +CF_TO_NS_CAST_DEFN(CFReadStream, NSInputStream); +CF_TO_NS_CAST_DEFN(CFWriteStream, NSOutputStream); +CF_TO_NS_MUTABLE_CAST_DEFN(String); +CF_TO_NS_CAST_DEFN(CFURL, NSURL); + +#undef CF_TO_NS_CAST_DEFN +#undef CF_TO_NS_MUTABLE_CAST_DEFN + +#define CF_CAST_DEFN(TypeCF) \ +template<> TypeCF##Ref \ +CFCast<TypeCF##Ref>(const CFTypeRef& cf_val) { \ + if (cf_val == NULL) { \ + return NULL; \ + } \ + if (CFGetTypeID(cf_val) == TypeCF##GetTypeID()) { \ + return (TypeCF##Ref)(cf_val); \ + } \ + return NULL; \ +} \ +\ +template<> TypeCF##Ref \ +CFCastStrict<TypeCF##Ref>(const CFTypeRef& cf_val) { \ + TypeCF##Ref rv = CFCast<TypeCF##Ref>(cf_val); \ + DCHECK(cf_val == NULL || rv); \ + return rv; \ +} + +CF_CAST_DEFN(CFArray); +CF_CAST_DEFN(CFBag); +CF_CAST_DEFN(CFBoolean); +CF_CAST_DEFN(CFData); +CF_CAST_DEFN(CFDate); +CF_CAST_DEFN(CFDictionary); +CF_CAST_DEFN(CFNull); +CF_CAST_DEFN(CFNumber); +CF_CAST_DEFN(CFSet); +CF_CAST_DEFN(CFString); +CF_CAST_DEFN(CFURL); +CF_CAST_DEFN(CFUUID); + +CF_CAST_DEFN(CGColor); + +CF_CAST_DEFN(CTFont); +CF_CAST_DEFN(CTRun); + +#if !defined(OS_IOS) +CF_CAST_DEFN(SecACL); +CF_CAST_DEFN(SecTrustedApplication); +#endif + +#undef CF_CAST_DEFN + +std::string GetValueFromDictionaryErrorMessage( + CFStringRef key, const std::string& expected_type, CFTypeRef value) { + ScopedCFTypeRef<CFStringRef> actual_type_ref( + CFCopyTypeIDDescription(CFGetTypeID(value))); + return "Expected value for key " + + base::SysCFStringRefToUTF8(key) + + " to be " + + expected_type + + " but it was " + + base::SysCFStringRefToUTF8(actual_type_ref) + + " instead"; +} + +NSString* FilePathToNSString(const FilePath& path) { + if (path.empty()) + return nil; + return [NSString stringWithUTF8String:path.value().c_str()]; +} + +FilePath NSStringToFilePath(NSString* str) { + if (![str length]) + return FilePath(); + return FilePath([str fileSystemRepresentation]); +} + +} // namespace mac +} // namespace base + +std::ostream& operator<<(std::ostream& o, const CFStringRef string) { + return o << base::SysCFStringRefToUTF8(string); +} + +std::ostream& operator<<(std::ostream& o, const CFErrorRef err) { + base::mac::ScopedCFTypeRef<CFStringRef> desc(CFErrorCopyDescription(err)); + base::mac::ScopedCFTypeRef<CFDictionaryRef> user_info( + CFErrorCopyUserInfo(err)); + CFStringRef errorDesc = NULL; + if (user_info.get()) { + errorDesc = reinterpret_cast<CFStringRef>( + CFDictionaryGetValue(user_info.get(), kCFErrorDescriptionKey)); + } + o << "Code: " << CFErrorGetCode(err) + << " Domain: " << CFErrorGetDomain(err) + << " Desc: " << desc.get(); + if(errorDesc) { + o << "(" << errorDesc << ")"; + } + return o; +}
diff --git a/src/base/mac/foundation_util_unittest.mm b/src/base/mac/foundation_util_unittest.mm new file mode 100644 index 0000000..4653006 --- /dev/null +++ b/src/base/mac/foundation_util_unittest.mm
@@ -0,0 +1,334 @@ +// 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/mac/foundation_util.h" + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/mac/scoped_nsautorelease_pool.h" +#include "testing/gtest/include/gtest/gtest.h" +#import "testing/gtest_mac.h" + +TEST(FoundationUtilTest, CFCast) { + // Build out the CF types to be tested as empty containers. + base::mac::ScopedCFTypeRef<CFTypeRef> test_array( + CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks)); + base::mac::ScopedCFTypeRef<CFTypeRef> test_array_mutable( + CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)); + base::mac::ScopedCFTypeRef<CFTypeRef> test_bag( + CFBagCreate(NULL, NULL, 0, &kCFTypeBagCallBacks)); + base::mac::ScopedCFTypeRef<CFTypeRef> test_bag_mutable( + CFBagCreateMutable(NULL, 0, &kCFTypeBagCallBacks)); + CFTypeRef test_bool = kCFBooleanTrue; + base::mac::ScopedCFTypeRef<CFTypeRef> test_data( + CFDataCreate(NULL, NULL, 0)); + base::mac::ScopedCFTypeRef<CFTypeRef> test_data_mutable( + CFDataCreateMutable(NULL, 0)); + base::mac::ScopedCFTypeRef<CFTypeRef> test_date( + CFDateCreate(NULL, 0)); + base::mac::ScopedCFTypeRef<CFTypeRef> test_dict( + CFDictionaryCreate(NULL, NULL, NULL, 0, + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + base::mac::ScopedCFTypeRef<CFTypeRef> test_dict_mutable( + CFDictionaryCreateMutable(NULL, 0, + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + int int_val = 256; + base::mac::ScopedCFTypeRef<CFTypeRef> test_number( + CFNumberCreate(NULL, kCFNumberIntType, &int_val)); + CFTypeRef test_null = kCFNull; + base::mac::ScopedCFTypeRef<CFTypeRef> test_set( + CFSetCreate(NULL, NULL, 0, &kCFTypeSetCallBacks)); + base::mac::ScopedCFTypeRef<CFTypeRef> test_set_mutable( + CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks)); + base::mac::ScopedCFTypeRef<CFTypeRef> test_str( + CFStringCreateWithBytes(NULL, NULL, 0, kCFStringEncodingASCII, + false)); + CFTypeRef test_str_const = CFSTR("hello"); + base::mac::ScopedCFTypeRef<CFTypeRef> test_str_mutable( + CFStringCreateMutable(NULL, 0)); + + // Make sure the allocations of CF types are good. + EXPECT_TRUE(test_array); + EXPECT_TRUE(test_array_mutable); + EXPECT_TRUE(test_bag); + EXPECT_TRUE(test_bag_mutable); + EXPECT_TRUE(test_bool); + EXPECT_TRUE(test_data); + EXPECT_TRUE(test_data_mutable); + EXPECT_TRUE(test_date); + EXPECT_TRUE(test_dict); + EXPECT_TRUE(test_dict_mutable); + EXPECT_TRUE(test_number); + EXPECT_TRUE(test_null); + EXPECT_TRUE(test_set); + EXPECT_TRUE(test_set_mutable); + EXPECT_TRUE(test_str); + EXPECT_TRUE(test_str_const); + EXPECT_TRUE(test_str_mutable); + + // Casting the CFTypeRef objects correctly provides the same pointer. + EXPECT_EQ(test_array, base::mac::CFCast<CFArrayRef>(test_array)); + EXPECT_EQ(test_array_mutable, + base::mac::CFCast<CFArrayRef>(test_array_mutable)); + EXPECT_EQ(test_bag, base::mac::CFCast<CFBagRef>(test_bag)); + EXPECT_EQ(test_bag_mutable, + base::mac::CFCast<CFBagRef>(test_bag_mutable)); + EXPECT_EQ(test_bool, base::mac::CFCast<CFBooleanRef>(test_bool)); + EXPECT_EQ(test_data, base::mac::CFCast<CFDataRef>(test_data)); + EXPECT_EQ(test_data_mutable, + base::mac::CFCast<CFDataRef>(test_data_mutable)); + EXPECT_EQ(test_date, base::mac::CFCast<CFDateRef>(test_date)); + EXPECT_EQ(test_dict, base::mac::CFCast<CFDictionaryRef>(test_dict)); + EXPECT_EQ(test_dict_mutable, + base::mac::CFCast<CFDictionaryRef>(test_dict_mutable)); + EXPECT_EQ(test_number, base::mac::CFCast<CFNumberRef>(test_number)); + EXPECT_EQ(test_null, base::mac::CFCast<CFNullRef>(test_null)); + EXPECT_EQ(test_set, base::mac::CFCast<CFSetRef>(test_set)); + EXPECT_EQ(test_set_mutable, base::mac::CFCast<CFSetRef>(test_set_mutable)); + EXPECT_EQ(test_str, base::mac::CFCast<CFStringRef>(test_str)); + EXPECT_EQ(test_str_const, base::mac::CFCast<CFStringRef>(test_str_const)); + EXPECT_EQ(test_str_mutable, + base::mac::CFCast<CFStringRef>(test_str_mutable)); + + // When given an incorrect CF cast, provide NULL. + EXPECT_FALSE(base::mac::CFCast<CFStringRef>(test_array)); + EXPECT_FALSE(base::mac::CFCast<CFStringRef>(test_array_mutable)); + EXPECT_FALSE(base::mac::CFCast<CFStringRef>(test_bag)); + EXPECT_FALSE(base::mac::CFCast<CFSetRef>(test_bag_mutable)); + EXPECT_FALSE(base::mac::CFCast<CFSetRef>(test_bool)); + EXPECT_FALSE(base::mac::CFCast<CFNullRef>(test_data)); + EXPECT_FALSE(base::mac::CFCast<CFDictionaryRef>(test_data_mutable)); + EXPECT_FALSE(base::mac::CFCast<CFDictionaryRef>(test_date)); + EXPECT_FALSE(base::mac::CFCast<CFNumberRef>(test_dict)); + EXPECT_FALSE(base::mac::CFCast<CFDateRef>(test_dict_mutable)); + EXPECT_FALSE(base::mac::CFCast<CFDataRef>(test_number)); + EXPECT_FALSE(base::mac::CFCast<CFDataRef>(test_null)); + EXPECT_FALSE(base::mac::CFCast<CFBooleanRef>(test_set)); + EXPECT_FALSE(base::mac::CFCast<CFBagRef>(test_set_mutable)); + EXPECT_FALSE(base::mac::CFCast<CFBagRef>(test_str)); + EXPECT_FALSE(base::mac::CFCast<CFArrayRef>(test_str_const)); + EXPECT_FALSE(base::mac::CFCast<CFArrayRef>(test_str_mutable)); + + // Giving a NULL provides a NULL. + EXPECT_FALSE(base::mac::CFCast<CFArrayRef>(NULL)); + EXPECT_FALSE(base::mac::CFCast<CFBagRef>(NULL)); + EXPECT_FALSE(base::mac::CFCast<CFBooleanRef>(NULL)); + EXPECT_FALSE(base::mac::CFCast<CFDataRef>(NULL)); + EXPECT_FALSE(base::mac::CFCast<CFDateRef>(NULL)); + EXPECT_FALSE(base::mac::CFCast<CFDictionaryRef>(NULL)); + EXPECT_FALSE(base::mac::CFCast<CFNullRef>(NULL)); + EXPECT_FALSE(base::mac::CFCast<CFNumberRef>(NULL)); + EXPECT_FALSE(base::mac::CFCast<CFSetRef>(NULL)); + EXPECT_FALSE(base::mac::CFCast<CFStringRef>(NULL)); + + // CFCastStrict: correct cast results in correct pointer being returned. + EXPECT_EQ(test_array, base::mac::CFCastStrict<CFArrayRef>(test_array)); + EXPECT_EQ(test_array_mutable, + base::mac::CFCastStrict<CFArrayRef>(test_array_mutable)); + EXPECT_EQ(test_bag, base::mac::CFCastStrict<CFBagRef>(test_bag)); + EXPECT_EQ(test_bag_mutable, + base::mac::CFCastStrict<CFBagRef>(test_bag_mutable)); + EXPECT_EQ(test_bool, base::mac::CFCastStrict<CFBooleanRef>(test_bool)); + EXPECT_EQ(test_data, base::mac::CFCastStrict<CFDataRef>(test_data)); + EXPECT_EQ(test_data_mutable, + base::mac::CFCastStrict<CFDataRef>(test_data_mutable)); + EXPECT_EQ(test_date, base::mac::CFCastStrict<CFDateRef>(test_date)); + EXPECT_EQ(test_dict, base::mac::CFCastStrict<CFDictionaryRef>(test_dict)); + EXPECT_EQ(test_dict_mutable, + base::mac::CFCastStrict<CFDictionaryRef>(test_dict_mutable)); + EXPECT_EQ(test_number, base::mac::CFCastStrict<CFNumberRef>(test_number)); + EXPECT_EQ(test_null, base::mac::CFCastStrict<CFNullRef>(test_null)); + EXPECT_EQ(test_set, base::mac::CFCastStrict<CFSetRef>(test_set)); + EXPECT_EQ(test_set_mutable, + base::mac::CFCastStrict<CFSetRef>(test_set_mutable)); + EXPECT_EQ(test_str, base::mac::CFCastStrict<CFStringRef>(test_str)); + EXPECT_EQ(test_str_const, + base::mac::CFCastStrict<CFStringRef>(test_str_const)); + EXPECT_EQ(test_str_mutable, + base::mac::CFCastStrict<CFStringRef>(test_str_mutable)); + + // CFCastStrict: Giving a NULL provides a NULL. + EXPECT_FALSE(base::mac::CFCastStrict<CFArrayRef>(NULL)); + EXPECT_FALSE(base::mac::CFCastStrict<CFBagRef>(NULL)); + EXPECT_FALSE(base::mac::CFCastStrict<CFBooleanRef>(NULL)); + EXPECT_FALSE(base::mac::CFCastStrict<CFDataRef>(NULL)); + EXPECT_FALSE(base::mac::CFCastStrict<CFDateRef>(NULL)); + EXPECT_FALSE(base::mac::CFCastStrict<CFDictionaryRef>(NULL)); + EXPECT_FALSE(base::mac::CFCastStrict<CFNullRef>(NULL)); + EXPECT_FALSE(base::mac::CFCastStrict<CFNumberRef>(NULL)); + EXPECT_FALSE(base::mac::CFCastStrict<CFSetRef>(NULL)); + EXPECT_FALSE(base::mac::CFCastStrict<CFStringRef>(NULL)); +} + +TEST(FoundationUtilTest, ObjCCast) { + base::mac::ScopedNSAutoreleasePool pool; + + id test_array = [NSArray array]; + id test_array_mutable = [NSMutableArray array]; + id test_data = [NSData data]; + id test_data_mutable = [NSMutableData dataWithCapacity:10]; + id test_date = [NSDate date]; + id test_dict = + [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:42] + forKey:@"meaning"]; + id test_dict_mutable = [NSMutableDictionary dictionaryWithCapacity:10]; + id test_number = [NSNumber numberWithInt:42]; + id test_null = [NSNull null]; + id test_set = [NSSet setWithObject:@"string object"]; + id test_set_mutable = [NSMutableSet setWithCapacity:10]; + id test_str = [NSString string]; + id test_str_const = @"bonjour"; + id test_str_mutable = [NSMutableString stringWithCapacity:10]; + + // Make sure the allocations of NS types are good. + EXPECT_TRUE(test_array); + EXPECT_TRUE(test_array_mutable); + EXPECT_TRUE(test_data); + EXPECT_TRUE(test_data_mutable); + EXPECT_TRUE(test_date); + EXPECT_TRUE(test_dict); + EXPECT_TRUE(test_dict_mutable); + EXPECT_TRUE(test_number); + EXPECT_TRUE(test_null); + EXPECT_TRUE(test_set); + EXPECT_TRUE(test_set_mutable); + EXPECT_TRUE(test_str); + EXPECT_TRUE(test_str_const); + EXPECT_TRUE(test_str_mutable); + + // Casting the id correctly provides the same pointer. + EXPECT_EQ(test_array, base::mac::ObjCCast<NSArray>(test_array)); + EXPECT_EQ(test_array_mutable, + base::mac::ObjCCast<NSArray>(test_array_mutable)); + EXPECT_EQ(test_data, base::mac::ObjCCast<NSData>(test_data)); + EXPECT_EQ(test_data_mutable, + base::mac::ObjCCast<NSData>(test_data_mutable)); + EXPECT_EQ(test_date, base::mac::ObjCCast<NSDate>(test_date)); + EXPECT_EQ(test_dict, base::mac::ObjCCast<NSDictionary>(test_dict)); + EXPECT_EQ(test_dict_mutable, + base::mac::ObjCCast<NSDictionary>(test_dict_mutable)); + EXPECT_EQ(test_number, base::mac::ObjCCast<NSNumber>(test_number)); + EXPECT_EQ(test_null, base::mac::ObjCCast<NSNull>(test_null)); + EXPECT_EQ(test_set, base::mac::ObjCCast<NSSet>(test_set)); + EXPECT_EQ(test_set_mutable, base::mac::ObjCCast<NSSet>(test_set_mutable)); + EXPECT_EQ(test_str, base::mac::ObjCCast<NSString>(test_str)); + EXPECT_EQ(test_str_const, base::mac::ObjCCast<NSString>(test_str_const)); + EXPECT_EQ(test_str_mutable, + base::mac::ObjCCast<NSString>(test_str_mutable)); + + // When given an incorrect ObjC cast, provide nil. + EXPECT_FALSE(base::mac::ObjCCast<NSString>(test_array)); + EXPECT_FALSE(base::mac::ObjCCast<NSString>(test_array_mutable)); + EXPECT_FALSE(base::mac::ObjCCast<NSString>(test_data)); + EXPECT_FALSE(base::mac::ObjCCast<NSString>(test_data_mutable)); + EXPECT_FALSE(base::mac::ObjCCast<NSSet>(test_date)); + EXPECT_FALSE(base::mac::ObjCCast<NSSet>(test_dict)); + EXPECT_FALSE(base::mac::ObjCCast<NSNumber>(test_dict_mutable)); + EXPECT_FALSE(base::mac::ObjCCast<NSNull>(test_number)); + EXPECT_FALSE(base::mac::ObjCCast<NSDictionary>(test_null)); + EXPECT_FALSE(base::mac::ObjCCast<NSDictionary>(test_set)); + EXPECT_FALSE(base::mac::ObjCCast<NSDate>(test_set_mutable)); + EXPECT_FALSE(base::mac::ObjCCast<NSData>(test_str)); + EXPECT_FALSE(base::mac::ObjCCast<NSData>(test_str_const)); + EXPECT_FALSE(base::mac::ObjCCast<NSArray>(test_str_mutable)); + + // Giving a nil provides a nil. + EXPECT_FALSE(base::mac::ObjCCast<NSArray>(nil)); + EXPECT_FALSE(base::mac::ObjCCast<NSData>(nil)); + EXPECT_FALSE(base::mac::ObjCCast<NSDate>(nil)); + EXPECT_FALSE(base::mac::ObjCCast<NSDictionary>(nil)); + EXPECT_FALSE(base::mac::ObjCCast<NSNull>(nil)); + EXPECT_FALSE(base::mac::ObjCCast<NSNumber>(nil)); + EXPECT_FALSE(base::mac::ObjCCast<NSSet>(nil)); + EXPECT_FALSE(base::mac::ObjCCast<NSString>(nil)); + + // ObjCCastStrict: correct cast results in correct pointer being returned. + EXPECT_EQ(test_array, base::mac::ObjCCastStrict<NSArray>(test_array)); + EXPECT_EQ(test_array_mutable, + base::mac::ObjCCastStrict<NSArray>(test_array_mutable)); + EXPECT_EQ(test_data, base::mac::ObjCCastStrict<NSData>(test_data)); + EXPECT_EQ(test_data_mutable, + base::mac::ObjCCastStrict<NSData>(test_data_mutable)); + EXPECT_EQ(test_date, base::mac::ObjCCastStrict<NSDate>(test_date)); + EXPECT_EQ(test_dict, base::mac::ObjCCastStrict<NSDictionary>(test_dict)); + EXPECT_EQ(test_dict_mutable, + base::mac::ObjCCastStrict<NSDictionary>(test_dict_mutable)); + EXPECT_EQ(test_number, base::mac::ObjCCastStrict<NSNumber>(test_number)); + EXPECT_EQ(test_null, base::mac::ObjCCastStrict<NSNull>(test_null)); + EXPECT_EQ(test_set, base::mac::ObjCCastStrict<NSSet>(test_set)); + EXPECT_EQ(test_set_mutable, + base::mac::ObjCCastStrict<NSSet>(test_set_mutable)); + EXPECT_EQ(test_str, base::mac::ObjCCastStrict<NSString>(test_str)); + EXPECT_EQ(test_str_const, + base::mac::ObjCCastStrict<NSString>(test_str_const)); + EXPECT_EQ(test_str_mutable, + base::mac::ObjCCastStrict<NSString>(test_str_mutable)); + + // ObjCCastStrict: Giving a nil provides a nil. + EXPECT_FALSE(base::mac::ObjCCastStrict<NSArray>(nil)); + EXPECT_FALSE(base::mac::ObjCCastStrict<NSData>(nil)); + EXPECT_FALSE(base::mac::ObjCCastStrict<NSDate>(nil)); + EXPECT_FALSE(base::mac::ObjCCastStrict<NSDictionary>(nil)); + EXPECT_FALSE(base::mac::ObjCCastStrict<NSNull>(nil)); + EXPECT_FALSE(base::mac::ObjCCastStrict<NSNumber>(nil)); + EXPECT_FALSE(base::mac::ObjCCastStrict<NSSet>(nil)); + EXPECT_FALSE(base::mac::ObjCCastStrict<NSString>(nil)); +} + +TEST(FoundationUtilTest, GetValueFromDictionary) { + int one = 1, two = 2, three = 3; + + base::mac::ScopedCFTypeRef<CFNumberRef> cf_one( + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &one)); + base::mac::ScopedCFTypeRef<CFNumberRef> cf_two( + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &two)); + base::mac::ScopedCFTypeRef<CFNumberRef> cf_three( + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &three)); + + CFStringRef keys[] = { CFSTR("one"), CFSTR("two"), CFSTR("three") }; + CFNumberRef values[] = { cf_one, cf_two, cf_three }; + + COMPILE_ASSERT(arraysize(keys) == arraysize(values), + keys_and_values_arraysizes_are_different); + + base::mac::ScopedCFTypeRef<CFDictionaryRef> test_dict( + CFDictionaryCreate(kCFAllocatorDefault, + reinterpret_cast<const void**>(keys), + reinterpret_cast<const void**>(values), + arraysize(values), + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + // base::mac::GetValueFromDictionary<>(_, _) should produce the correct + // expected output. + EXPECT_EQ(values[0], + base::mac::GetValueFromDictionary<CFNumberRef>(test_dict, + CFSTR("one"))); + EXPECT_EQ(values[1], + base::mac::GetValueFromDictionary<CFNumberRef>(test_dict, + CFSTR("two"))); + EXPECT_EQ(values[2], + base::mac::GetValueFromDictionary<CFNumberRef>(test_dict, + CFSTR("three"))); + + // Bad input should produce bad output. + EXPECT_FALSE(base::mac::GetValueFromDictionary<CFNumberRef>(test_dict, + CFSTR("four"))); + EXPECT_FALSE(base::mac::GetValueFromDictionary<CFStringRef>(test_dict, + CFSTR("one"))); +} + +TEST(FoundationUtilTest, FilePathToNSString) { + EXPECT_NSEQ(nil, base::mac::FilePathToNSString(FilePath())); + EXPECT_NSEQ(@"/a/b", base::mac::FilePathToNSString(FilePath("/a/b"))); +} + +TEST(FoundationUtilTest, NSStringToFilePath) { + EXPECT_EQ(FilePath(), base::mac::NSStringToFilePath(nil)); + EXPECT_EQ(FilePath(), base::mac::NSStringToFilePath(@"")); + EXPECT_EQ(FilePath("/a/b"), base::mac::NSStringToFilePath(@"/a/b")); +}
diff --git a/src/base/mac/launchd.cc b/src/base/mac/launchd.cc new file mode 100644 index 0000000..1d384c9 --- /dev/null +++ b/src/base/mac/launchd.cc
@@ -0,0 +1,75 @@ +// 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/mac/launchd.h" + +#include "base/logging.h" +#include "base/mac/scoped_launch_data.h" + +namespace base { +namespace mac { + +// MessageForJob sends a single message to launchd with a simple dictionary +// mapping |operation| to |job_label|, and returns the result of calling +// launch_msg to send that message. On failure, returns NULL. The caller +// assumes ownership of the returned launch_data_t object. +launch_data_t MessageForJob(const std::string& job_label, + const char* operation) { + // launch_data_alloc returns something that needs to be freed. + ScopedLaunchData message(launch_data_alloc(LAUNCH_DATA_DICTIONARY)); + if (!message) { + LOG(ERROR) << "launch_data_alloc"; + return NULL; + } + + // launch_data_new_string returns something that needs to be freed, but + // the dictionary will assume ownership when launch_data_dict_insert is + // called, so put it in a scoper and .release() it when given to the + // dictionary. + ScopedLaunchData job_label_launchd(launch_data_new_string(job_label.c_str())); + if (!job_label_launchd) { + LOG(ERROR) << "launch_data_new_string"; + return NULL; + } + + if (!launch_data_dict_insert(message, + job_label_launchd.release(), + operation)) { + return NULL; + } + + return launch_msg(message); +} + +pid_t PIDForJob(const std::string& job_label) { + ScopedLaunchData response(MessageForJob(job_label, LAUNCH_KEY_GETJOB)); + if (!response) { + return -1; + } + + launch_data_type_t response_type = launch_data_get_type(response); + if (response_type != LAUNCH_DATA_DICTIONARY) { + if (response_type == LAUNCH_DATA_ERRNO) { + LOG(ERROR) << "PIDForJob: error " << launch_data_get_errno(response); + } else { + LOG(ERROR) << "PIDForJob: expected dictionary, got " << response_type; + } + return -1; + } + + launch_data_t pid_data = launch_data_dict_lookup(response, + LAUNCH_JOBKEY_PID); + if (!pid_data) + return 0; + + if (launch_data_get_type(pid_data) != LAUNCH_DATA_INTEGER) { + LOG(ERROR) << "PIDForJob: expected integer"; + return -1; + } + + return launch_data_get_integer(pid_data); +} + +} // namespace mac +} // namespace base
diff --git a/src/base/mac/launchd.h b/src/base/mac/launchd.h new file mode 100644 index 0000000..9e4514e --- /dev/null +++ b/src/base/mac/launchd.h
@@ -0,0 +1,34 @@ +// 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_MAC_LAUNCHD_H_ +#define BASE_MAC_LAUNCHD_H_ + +#include <launch.h> +#include <sys/types.h> + +#include <string> + +#include "base/base_export.h" + +namespace base { +namespace mac { + +// MessageForJob sends a single message to launchd with a simple dictionary +// mapping |operation| to |job_label|, and returns the result of calling +// launch_msg to send that message. On failure, returns NULL. The caller +// assumes ownership of the returned launch_data_t object. +BASE_EXPORT +launch_data_t MessageForJob(const std::string& job_label, + const char* operation); + +// Returns the process ID for |job_label| if the job is running, 0 if the job +// is loaded but not running, or -1 on error. +BASE_EXPORT +pid_t PIDForJob(const std::string& job_label); + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_LAUNCHD_H_
diff --git a/src/base/mac/libdispatch_task_runner.cc b/src/base/mac/libdispatch_task_runner.cc new file mode 100644 index 0000000..4b5abaf --- /dev/null +++ b/src/base/mac/libdispatch_task_runner.cc
@@ -0,0 +1,80 @@ +// 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/mac/libdispatch_task_runner.h" + +#include "base/callback.h" + +namespace base { +namespace mac { + +LibDispatchTaskRunner::LibDispatchTaskRunner(const char* name) + : queue_(dispatch_queue_create(name, NULL)), + queue_finalized_(false, false) { + dispatch_set_context(queue_, this); + dispatch_set_finalizer_f(queue_, &LibDispatchTaskRunner::Finalizer); +} + +bool LibDispatchTaskRunner::PostDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + base::TimeDelta delay) { + if (!queue_) + return false; + + // The block runtime would implicitly copy the reference, not the object + // it's referencing. Copy the closure into block storage so it's available + // to run. + __block const Closure task_copy = task; + void(^run_task)(void) = ^{ + task_copy.Run(); + }; + + int64 delay_nano = + delay.InMicroseconds() * base::Time::kNanosecondsPerMicrosecond; + if (delay_nano > 0) { + dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, delay_nano); + dispatch_after(time, queue_, run_task); + } else { + dispatch_async(queue_, run_task); + } + return true; +} + +bool LibDispatchTaskRunner::RunsTasksOnCurrentThread() const { + return queue_ == dispatch_get_current_queue(); +} + +bool LibDispatchTaskRunner::PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + base::TimeDelta delay) { + return PostDelayedTask(from_here, task, delay); +} + +void LibDispatchTaskRunner::Shutdown() { + dispatch_release(queue_); + queue_ = NULL; + queue_finalized_.Wait(); +} + +dispatch_queue_t LibDispatchTaskRunner::GetDispatchQueue() const { + return queue_; +} + +LibDispatchTaskRunner::~LibDispatchTaskRunner() { + if (queue_) { + dispatch_set_context(queue_, NULL); + dispatch_set_finalizer_f(queue_, NULL); + dispatch_release(queue_); + } +} + +void LibDispatchTaskRunner::Finalizer(void* context) { + LibDispatchTaskRunner* self = static_cast<LibDispatchTaskRunner*>(context); + self->queue_finalized_.Signal(); +} + +} // namespace mac +} // namespace base
diff --git a/src/base/mac/libdispatch_task_runner.h b/src/base/mac/libdispatch_task_runner.h new file mode 100644 index 0000000..b1d90e2 --- /dev/null +++ b/src/base/mac/libdispatch_task_runner.h
@@ -0,0 +1,81 @@ +// 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_MAC_LIBDISPATCH_SEQUENCED_TASK_RUNNER_H_ +#define BASE_MAC_LIBDISPATCH_SEQUENCED_TASK_RUNNER_H_ + +#include <dispatch/dispatch.h> + +#include "base/single_thread_task_runner.h" +#include "base/synchronization/waitable_event.h" + +namespace base { +namespace mac { + +// This is an implementation of the TaskRunner interface that runs closures on +// a thread managed by Apple's libdispatch. This has the benefit of being able +// to PostTask() and friends to a dispatch queue, while being reusable as a +// dispatch_queue_t. +// +// One would use this class if an object lives exclusively on one thread but +// needs a dispatch_queue_t for use in a system API. This ensures all dispatch +// callbacks happen on the same thread as Closure tasks. +// +// A LibDispatchTaskRunner will continue to run until all references to the +// underlying dispatch queue are released. +// +// Important Notes: +// - There is no MessageLoop running on this thread, and ::current() returns +// NULL. +// - No nested loops can be run, and all tasks are run non-nested. +// - Work scheduled via libdispatch runs at the same priority as and is +// interleaved with posted tasks, though FIFO order is guaranteed. +// +class BASE_EXPORT LibDispatchTaskRunner : public base::SingleThreadTaskRunner { + public: + // Starts a new serial dispatch queue with a given name. + explicit LibDispatchTaskRunner(const char* name); + + // base::TaskRunner: + virtual bool PostDelayedTask(const tracked_objects::Location& from_here, + const Closure& task, + base::TimeDelta delay) OVERRIDE; + virtual bool RunsTasksOnCurrentThread() const OVERRIDE; + + // base::SequencedTaskRunner: + virtual bool PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + base::TimeDelta delay) OVERRIDE; + + // This blocks the calling thread until all work on the dispatch queue has + // been run and the queue has been destroyed. Destroying a queue requires + // ALL retained references to it to be released. Any new tasks posted to + // this thread after shutdown are dropped. + void Shutdown(); + + // Returns the dispatch queue associated with this task runner, for use with + // system APIs that take dispatch queues. The caller is responsible for + // retaining the result. + // + // All properties (context, finalizer, etc.) are managed by this class, and + // clients should only use the result of this for dispatch_async(). + dispatch_queue_t GetDispatchQueue() const; + + protected: + virtual ~LibDispatchTaskRunner(); + + private: + static void Finalizer(void* context); + + dispatch_queue_t queue_; + + // The event on which Shutdown waits until Finalizer runs. + base::WaitableEvent queue_finalized_; +}; + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_LIBDISPATCH_SEQUENCED_TASK_RUNNER_H_
diff --git a/src/base/mac/libdispatch_task_runner_unittest.cc b/src/base/mac/libdispatch_task_runner_unittest.cc new file mode 100644 index 0000000..c3488d2 --- /dev/null +++ b/src/base/mac/libdispatch_task_runner_unittest.cc
@@ -0,0 +1,221 @@ +// 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/mac/libdispatch_task_runner.h" + +#include "base/bind.h" +#include "base/mac/bind_objc_block.h" +#include "base/message_loop.h" +#include "base/stringprintf.h" +#include "testing/gtest/include/gtest/gtest.h" + +class LibDispatchTaskRunnerTest : public testing::Test { + public: + virtual void SetUp() OVERRIDE { + task_runner_ = new base::mac::LibDispatchTaskRunner( + "org.chromium.LibDispatchTaskRunnerTest"); + } + + // DispatchLastTask is used to run the main test thread's MessageLoop until + // all non-delayed tasks are run on the LibDispatchTaskRunner. + void DispatchLastTask() { + dispatch_async(task_runner_->GetDispatchQueue(), ^{ + (&message_loop_)->PostTask(FROM_HERE, MessageLoop::QuitClosure()); + }); + message_loop_.Run(); + task_runner_->Shutdown(); + } + + // VerifyTaskOrder takes the expectations from TaskOrderMarkers and compares + // them against the recorded values. + void VerifyTaskOrder(const char* const expectations[], + size_t num_expectations) { + size_t actual_size = task_order_.size(); + + for (size_t i = 0; i < num_expectations; ++i) { + if (i >= actual_size) { + EXPECT_LE(i, actual_size) << "Expected " << expectations[i]; + continue; + } + + EXPECT_EQ(expectations[i], task_order_[i]); + } + + if (actual_size > num_expectations) { + EXPECT_LE(actual_size, num_expectations) << "Extra tasks were run:"; + for (size_t i = num_expectations; i < actual_size; ++i) { + EXPECT_EQ("<none>", task_order_[i]) << " (i=" << i << ")"; + } + } + } + + // The message loop for the test main thread. + MessageLoop message_loop_; + + // The task runner under test. + scoped_refptr<base::mac::LibDispatchTaskRunner> task_runner_; + + // Vector that records data from TaskOrderMarker. + std::vector<std::string> task_order_; +}; + +// Scoper that records the beginning and end of a running task. +class TaskOrderMarker { + public: + TaskOrderMarker(LibDispatchTaskRunnerTest* test, const std::string& name) + : test_(test), + name_(name) { + test->task_order_.push_back(std::string("BEGIN ") + name); + } + ~TaskOrderMarker() { + test_->task_order_.push_back(std::string("END ") + name_); + } + + private: + LibDispatchTaskRunnerTest* test_; + std::string name_; +}; + +void RecordTaskOrder(LibDispatchTaskRunnerTest* test, const std::string& name) { + TaskOrderMarker marker(test, name); +} + +// Returns a closure that records the task order. +base::Closure BoundRecordTaskOrder(LibDispatchTaskRunnerTest* test, + const std::string& name) { + return base::Bind(&RecordTaskOrder, base::Unretained(test), name); +} + +TEST_F(LibDispatchTaskRunnerTest, PostTask) { + task_runner_->PostTask(FROM_HERE, BoundRecordTaskOrder(this, "Basic Task")); + DispatchLastTask(); + const char* const expectations[] = { + "BEGIN Basic Task", + "END Basic Task" + }; + VerifyTaskOrder(expectations, arraysize(expectations)); +} + +TEST_F(LibDispatchTaskRunnerTest, PostTaskWithinTask) { + task_runner_->PostTask(FROM_HERE, base::BindBlock(^{ + TaskOrderMarker marker(this, "Outer"); + task_runner_->PostTask(FROM_HERE, BoundRecordTaskOrder(this, "Inner")); + })); + DispatchLastTask(); + + const char* const expectations[] = { + "BEGIN Outer", + "END Outer", + "BEGIN Inner", + "END Inner" + }; + VerifyTaskOrder(expectations, arraysize(expectations)); +} + +TEST_F(LibDispatchTaskRunnerTest, NoMessageLoop) { + task_runner_->PostTask(FROM_HERE, base::BindBlock(^{ + TaskOrderMarker marker(this, + base::StringPrintf("MessageLoop = %p", MessageLoop::current())); + })); + DispatchLastTask(); + + const char* const expectations[] = { + "BEGIN MessageLoop = 0x0", + "END MessageLoop = 0x0" + }; + VerifyTaskOrder(expectations, arraysize(expectations)); +} + +TEST_F(LibDispatchTaskRunnerTest, DispatchAndPostTasks) { + dispatch_async(task_runner_->GetDispatchQueue(), ^{ + TaskOrderMarker marker(this, "First Block"); + }); + task_runner_->PostTask(FROM_HERE, BoundRecordTaskOrder(this, "First Task")); + dispatch_async(task_runner_->GetDispatchQueue(), ^{ + TaskOrderMarker marker(this, "Second Block"); + }); + task_runner_->PostTask(FROM_HERE, BoundRecordTaskOrder(this, "Second Task")); + DispatchLastTask(); + + const char* const expectations[] = { + "BEGIN First Block", + "END First Block", + "BEGIN First Task", + "END First Task", + "BEGIN Second Block", + "END Second Block", + "BEGIN Second Task", + "END Second Task", + }; + VerifyTaskOrder(expectations, arraysize(expectations)); +} + +TEST_F(LibDispatchTaskRunnerTest, NonNestable) { + task_runner_->PostTask(FROM_HERE, base::BindBlock(^{ + TaskOrderMarker marker(this, "First"); + task_runner_->PostNonNestableTask(FROM_HERE, base::BindBlock(^{ + TaskOrderMarker marker(this, "Second NonNestable"); + (&message_loop_)->PostTask(FROM_HERE, MessageLoop::QuitClosure()); + })); + })); + message_loop_.Run(); + task_runner_->Shutdown(); + + const char* const expectations[] = { + "BEGIN First", + "END First", + "BEGIN Second NonNestable", + "END Second NonNestable" + }; + VerifyTaskOrder(expectations, arraysize(expectations)); +} + +TEST_F(LibDispatchTaskRunnerTest, PostDelayed) { + base::TimeTicks post_time; + __block base::TimeTicks run_time; + const base::TimeDelta delta = base::TimeDelta::FromMilliseconds(50); + + task_runner_->PostTask(FROM_HERE, BoundRecordTaskOrder(this, "First")); + post_time = base::TimeTicks::Now(); + task_runner_->PostDelayedTask(FROM_HERE, base::BindBlock(^{ + TaskOrderMarker marker(this, "Timed"); + run_time = base::TimeTicks::Now(); + (&message_loop_)->PostTask(FROM_HERE, MessageLoop::QuitClosure()); + }), delta); + task_runner_->PostTask(FROM_HERE, BoundRecordTaskOrder(this, "Second")); + message_loop_.Run(); + task_runner_->Shutdown(); + + const char* const expectations[] = { + "BEGIN First", + "END First", + "BEGIN Second", + "END Second", + "BEGIN Timed", + "END Timed", + }; + VerifyTaskOrder(expectations, arraysize(expectations)); + + EXPECT_GE(run_time, post_time + delta); +} + +TEST_F(LibDispatchTaskRunnerTest, PostAfterShutdown) { + EXPECT_TRUE(task_runner_->PostTask(FROM_HERE, + BoundRecordTaskOrder(this, "First"))); + EXPECT_TRUE(task_runner_->PostTask(FROM_HERE, + BoundRecordTaskOrder(this, "Second"))); + task_runner_->Shutdown(); + EXPECT_FALSE(task_runner_->PostTask(FROM_HERE, base::BindBlock(^{ + TaskOrderMarker marker(this, "Not Run"); + ADD_FAILURE() << "Should not run a task after Shutdown"; + }))); + + const char* const expectations[] = { + "BEGIN First", + "END First", + "BEGIN Second", + "END Second" + }; + VerifyTaskOrder(expectations, arraysize(expectations)); +}
diff --git a/src/base/mac/mac_logging.cc b/src/base/mac/mac_logging.cc new file mode 100644 index 0000000..d58220f --- /dev/null +++ b/src/base/mac/mac_logging.cc
@@ -0,0 +1,37 @@ +// 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/mac/mac_logging.h" + +#include <iomanip> + +#if !defined(OS_IOS) +#include <CoreServices/CoreServices.h> +#endif + +namespace logging { + +OSStatusLogMessage::OSStatusLogMessage(const char* file_path, + int line, + LogSeverity severity, + OSStatus status) + : LogMessage(file_path, line, severity), + status_(status) { +} + +OSStatusLogMessage::~OSStatusLogMessage() { +#if defined(OS_IOS) + // TODO(ios): Consider using NSError with NSOSStatusErrorDomain to try to + // get a description of the failure. + stream() << ": " << status_; +#else + stream() << ": " + << GetMacOSStatusErrorString(status_) + << " (" + << status_ + << ")"; +#endif +} + +} // namespace logging
diff --git a/src/base/mac/mac_logging.h b/src/base/mac/mac_logging.h new file mode 100644 index 0000000..9a0003e --- /dev/null +++ b/src/base/mac/mac_logging.h
@@ -0,0 +1,87 @@ +// 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_MAC_MAC_LOGGING_H_ +#define BASE_MAC_MAC_LOGGING_H_ + +#include "base/logging.h" +#include "build/build_config.h" + +#if defined(OS_IOS) +#include <MacTypes.h> +#else +#include <libkern/OSTypes.h> +#endif + +// Use the OSSTATUS_LOG family to log messages related to errors in Mac OS X +// system routines that report status via an OSStatus or OSErr value. It is +// similar to the PLOG family which operates on errno, but because there is no +// global (or thread-local) OSStatus or OSErr value, the specific error must +// be supplied as an argument to the OSSTATUS_LOG macro. The message logged +// will contain the symbolic constant name corresponding to the status value, +// along with the value itself. +// +// OSErr is just an older 16-bit form of the newer 32-bit OSStatus. Despite +// the name, OSSTATUS_LOG can be used equally well for OSStatus and OSErr. + +namespace logging { + +class BASE_EXPORT OSStatusLogMessage : public logging::LogMessage { + public: + OSStatusLogMessage(const char* file_path, + int line, + LogSeverity severity, + OSStatus status); + ~OSStatusLogMessage(); + + private: + OSStatus status_; + + DISALLOW_COPY_AND_ASSIGN(OSStatusLogMessage); +}; + +} // namespace logging + +#define OSSTATUS_LOG_STREAM(severity, status) \ + COMPACT_GOOGLE_LOG_EX_ ## severity(OSStatusLogMessage, status).stream() +#define OSSTATUS_VLOG_STREAM(verbose_level, status) \ + logging::OSStatusLogMessage(__FILE__, __LINE__, \ + -verbose_level, status).stream() + +#define OSSTATUS_LOG(severity, status) \ + LAZY_STREAM(OSSTATUS_LOG_STREAM(severity, status), LOG_IS_ON(severity)) +#define OSSTATUS_LOG_IF(severity, condition, status) \ + LAZY_STREAM(OSSTATUS_LOG_STREAM(severity, status), \ + LOG_IS_ON(severity) && (condition)) + +#define OSSTATUS_VLOG(verbose_level, status) \ + LAZY_STREAM(OSSTATUS_VLOG_STREAM(verbose_level, status), \ + VLOG_IS_ON(verbose_level)) +#define OSSTATUS_VLOG_IF(verbose_level, condition, status) \ + LAZY_STREAM(OSSTATUS_VLOG_STREAM(verbose_level, status), \ + VLOG_IS_ON(verbose_level) && (condition)) + +#define OSSTATUS_CHECK(condition, status) \ + LAZY_STREAM(OSSTATUS_LOG_STREAM(FATAL, status), !(condition)) \ + << "Check failed: " # condition << ". " + +#define OSSTATUS_DLOG(severity, status) \ + LAZY_STREAM(OSSTATUS_LOG_STREAM(severity, status), DLOG_IS_ON(severity)) +#define OSSTATUS_DLOG_IF(severity, condition, status) \ + LAZY_STREAM(OSSTATUS_LOG_STREAM(severity, status), \ + DLOG_IS_ON(severity) && (condition)) + +#define OSSTATUS_DVLOG(verbose_level, status) \ + LAZY_STREAM(OSSTATUS_VPLOG_STREAM(verbose_level, status), \ + DVLOG_IS_ON(verbose_level)) +#define OSSTATUS_DVLOG_IF(verbose_level, condition, status) \ + LAZY_STREAM(OSSTATUS_VPLOG_STREAM(verbose_level, status) \ + DVLOG_IS_ON(verbose_level) && (condition)) + +#define OSSTATUS_DCHECK(condition, status) \ + LAZY_STREAM(OSSTATUS_LOG_STREAM(FATAL, status), \ + DCHECK_IS_ON() && !(condition)) \ + << "Check failed: " # condition << ". " + +#endif // BASE_MAC_MAC_LOGGING_H_
diff --git a/src/base/mac/mac_util.h b/src/base/mac/mac_util.h new file mode 100644 index 0000000..ff8b2b2 --- /dev/null +++ b/src/base/mac/mac_util.h
@@ -0,0 +1,195 @@ +// 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_MAC_MAC_UTIL_H_ +#define BASE_MAC_MAC_UTIL_H_ + +#include <AvailabilityMacros.h> +#include <Carbon/Carbon.h> +#include <string> + +#include "base/base_export.h" +#include "base/logging.h" + +// TODO(rohitrao): Clean up sites that include mac_util.h and remove this line. +#include "base/mac/foundation_util.h" + +#if defined(__OBJC__) +#import <Foundation/Foundation.h> +#else // __OBJC__ +class NSImage; +#endif // __OBJC__ + +class FilePath; + +namespace base { +namespace mac { + +// Full screen modes, in increasing order of priority. More permissive modes +// take predecence. +enum FullScreenMode { + kFullScreenModeHideAll = 0, + kFullScreenModeHideDock = 1, + kFullScreenModeAutoHideAll = 2, + kNumFullScreenModes = 3, + + // kFullScreenModeNormal is not a valid FullScreenMode, but it is useful to + // other classes, so we include it here. + kFullScreenModeNormal = 10, +}; + +BASE_EXPORT std::string PathFromFSRef(const FSRef& ref); +BASE_EXPORT bool FSRefFromPath(const std::string& path, FSRef* ref); + +// Returns an sRGB color space. The return value is a static value; do not +// release it! +BASE_EXPORT CGColorSpaceRef GetSRGBColorSpace(); + +// Returns the color space being used by the main display. The return value +// is a static value; do not release it! +BASE_EXPORT CGColorSpaceRef GetSystemColorSpace(); + +// Add a full screen request for the given |mode|. Must be paired with a +// ReleaseFullScreen() call for the same |mode|. This does not by itself create +// a fullscreen window; rather, it manages per-application state related to +// hiding the dock and menubar. Must be called on the main thread. +BASE_EXPORT void RequestFullScreen(FullScreenMode mode); + +// Release a request for full screen mode. Must be matched with a +// RequestFullScreen() call for the same |mode|. As with RequestFullScreen(), +// this does not affect windows directly, but rather manages per-application +// state. For example, if there are no other outstanding +// |kFullScreenModeAutoHideAll| requests, this will reshow the menu bar. Must +// be called on main thread. +BASE_EXPORT void ReleaseFullScreen(FullScreenMode mode); + +// Convenience method to switch the current fullscreen mode. This has the same +// net effect as a ReleaseFullScreen(from_mode) call followed immediately by a +// RequestFullScreen(to_mode). Must be called on the main thread. +BASE_EXPORT void SwitchFullScreenModes(FullScreenMode from_mode, + FullScreenMode to_mode); + +// Set the visibility of the cursor. +BASE_EXPORT void SetCursorVisibility(bool visible); + +// Should windows miniaturize on a double-click (on the title bar)? +BASE_EXPORT bool ShouldWindowsMiniaturizeOnDoubleClick(); + +// Activates the process with the given PID. +BASE_EXPORT void ActivateProcess(pid_t pid); + +// Returns true if this process is in the foreground, meaning that it's the +// frontmost process, the one whose menu bar is shown at the top of the main +// display. +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); + +// Sets the process name as displayed in Activity Monitor to process_name. +BASE_EXPORT void SetProcessName(CFStringRef process_name); + +// 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. +BASE_EXPORT bool CheckLoginItemStatus(bool* is_hidden); + +// Adds current application to the set of Login Items with specified "hide" +// flag. This has the same effect as adding/removing the application in +// SystemPreferences->Accounts->LoginItems or marking Application in the Dock +// as "Options->Open on Login". +// Does nothing if the application is already set up as Login Item with +// specified hide flag. +BASE_EXPORT void AddToLoginItems(bool hide_on_startup); + +// Removes the current application from the list Of Login Items. +BASE_EXPORT void RemoveFromLoginItems(); + +// Returns true if the current process was automatically launched as a +// 'Login Item' or via Lion's Resume. Used to suppress opening windows. +BASE_EXPORT bool WasLaunchedAsLoginOrResumeItem(); + +// Returns true if the current process was automatically launched as a +// 'Login Item' with 'hide on startup' flag. Used to suppress opening windows. +BASE_EXPORT bool WasLaunchedAsHiddenLoginItem(); + +// Run-time OS version checks. Use these instead of +// base::SysInfo::OperatingSystemVersionNumbers. Prefer the "OrEarlier" and +// "OrLater" variants to those that check for a specific version, unless you +// know for sure that you need to check for a specific version. + +// Snow Leopard is Mac OS X 10.6, Darwin 10. +BASE_EXPORT bool IsOSSnowLeopard(); + +// Lion is Mac OS X 10.7, Darwin 11. +BASE_EXPORT bool IsOSLion(); +BASE_EXPORT bool IsOSLionOrEarlier(); +BASE_EXPORT bool IsOSLionOrLater(); + +// Mountain Lion is Mac OS X 10.8, Darwin 12. +BASE_EXPORT bool IsOSMountainLion(); +BASE_EXPORT bool IsOSMountainLionOrLater(); + +// 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(); + +// 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 +// constant-value inline functions. The MAC_OS_X_VERSION_MIN_REQUIRED macro +// contains the value of the deployment target. + +#if defined(MAC_OS_X_VERSION_10_7) && \ + MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7 +#define BASE_MAC_MAC_UTIL_H_INLINED_GE_10_7 +inline bool IsOSSnowLeopard() { return false; } +inline bool IsOSLionOrLater() { return true; } +#endif + +#if defined(MAC_OS_X_VERSION_10_7) && \ + 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) && \ + MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8 +#define BASE_MAC_MAC_UTIL_H_INLINED_GE_10_8 +inline bool IsOSMountainLionOrLater() { return true; } +#endif + +#if defined(MAC_OS_X_VERSION_10_8) && \ + 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 + +// Retrieve the system's model identifier string from the IOKit registry: +// for example, "MacPro4,1", "MacBookPro6,1". Returns empty string upon +// failure. +BASE_EXPORT std::string GetModelIdentifier(); + +// Parse a model identifier string; for example, into ("MacBookPro", 6, 1). +// If any error occurs, none of the input pointers are touched. +BASE_EXPORT bool ParseModelIdentifier(const std::string& ident, + std::string* type, + int32* major, + int32* minor); + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_MAC_UTIL_H_
diff --git a/src/base/mac/mac_util.mm b/src/base/mac/mac_util.mm new file mode 100644 index 0000000..4fb1a42 --- /dev/null +++ b/src/base/mac/mac_util.mm
@@ -0,0 +1,666 @@ +// 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/mac/mac_util.h" + +#import <Cocoa/Cocoa.h> +#import <IOKit/IOKitLib.h> +#include <string.h> +#include <sys/utsname.h> + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/mac/bundle_locations.h" +#include "base/mac/foundation_util.h" +#include "base/mac/mac_logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/memory/scoped_generic_obj.h" +#include "base/memory/scoped_nsobject.h" +#include "base/string_number_conversions.h" +#include "base/string_piece.h" +#include "base/sys_string_conversions.h" + +namespace base { +namespace mac { + +namespace { + +// The current count of outstanding requests for full screen mode from browser +// windows, plugins, etc. +int g_full_screen_requests[kNumFullScreenModes] = { 0, 0, 0}; + +// Sets the appropriate SystemUIMode based on the current full screen requests. +// Since only one SystemUIMode can be active at a given time, full screen +// requests are ordered by priority. If there are no outstanding full screen +// requests, reverts to normal mode. If the correct SystemUIMode is already +// set, does nothing. +void SetUIMode() { + // Get the current UI mode. + SystemUIMode current_mode; + GetSystemUIMode(¤t_mode, NULL); + + // Determine which mode should be active, based on which requests are + // currently outstanding. More permissive requests take precedence. For + // example, plugins request |kFullScreenModeAutoHideAll|, while browser + // windows request |kFullScreenModeHideDock| when the fullscreen overlay is + // down. Precedence goes to plugins in this case, so AutoHideAll wins over + // HideDock. + SystemUIMode desired_mode = kUIModeNormal; + SystemUIOptions desired_options = 0; + if (g_full_screen_requests[kFullScreenModeAutoHideAll] > 0) { + desired_mode = kUIModeAllHidden; + desired_options = kUIOptionAutoShowMenuBar; + } else if (g_full_screen_requests[kFullScreenModeHideDock] > 0) { + desired_mode = kUIModeContentHidden; + } else if (g_full_screen_requests[kFullScreenModeHideAll] > 0) { + desired_mode = kUIModeAllHidden; + } + + if (current_mode != desired_mode) + SetSystemUIMode(desired_mode, desired_options); +} + +// Looks into Shared File Lists corresponding to Login Items for the item +// representing the current application. If such an item is found, returns a +// retained reference to it. Caller is responsible for releasing the reference. +LSSharedFileListItemRef GetLoginItemForApp() { + ScopedCFTypeRef<LSSharedFileListRef> login_items(LSSharedFileListCreate( + NULL, kLSSharedFileListSessionLoginItems, NULL)); + + if (!login_items.get()) { + DLOG(ERROR) << "Couldn't get a Login Items list."; + return NULL; + } + + scoped_nsobject<NSArray> login_items_array( + CFToNSCast(LSSharedFileListCopySnapshot(login_items, NULL))); + + NSURL* url = [NSURL fileURLWithPath:[base::mac::MainBundle() bundlePath]]; + + for(NSUInteger i = 0; i < [login_items_array count]; ++i) { + LSSharedFileListItemRef item = reinterpret_cast<LSSharedFileListItemRef>( + [login_items_array objectAtIndex:i]); + CFURLRef item_url_ref = NULL; + + if (LSSharedFileListItemResolve(item, 0, &item_url_ref, NULL) == noErr) { + ScopedCFTypeRef<CFURLRef> item_url(item_url_ref); + if (CFEqual(item_url, url)) { + CFRetain(item); + return item; + } + } + } + + return NULL; +} + +bool IsHiddenLoginItem(LSSharedFileListItemRef item) { + ScopedCFTypeRef<CFBooleanRef> hidden(reinterpret_cast<CFBooleanRef>( + LSSharedFileListItemCopyProperty(item, + reinterpret_cast<CFStringRef>(kLSSharedFileListLoginItemHidden)))); + + return hidden && hidden == kCFBooleanTrue; +} + +} // namespace + +std::string PathFromFSRef(const FSRef& ref) { + ScopedCFTypeRef<CFURLRef> url( + CFURLCreateFromFSRef(kCFAllocatorDefault, &ref)); + NSString *path_string = [(NSURL *)url.get() path]; + return [path_string fileSystemRepresentation]; +} + +bool FSRefFromPath(const std::string& path, FSRef* ref) { + OSStatus status = FSPathMakeRef((const UInt8*)path.c_str(), + ref, nil); + return status == noErr; +} + +CGColorSpaceRef GetSRGBColorSpace() { + // Leaked. That's OK, it's scoped to the lifetime of the application. + static CGColorSpaceRef g_color_space_sRGB = + CGColorSpaceCreateWithName(kCGColorSpaceSRGB); + DLOG_IF(ERROR, !g_color_space_sRGB) << "Couldn't get the sRGB color space"; + return g_color_space_sRGB; +} + +CGColorSpaceRef GetSystemColorSpace() { + // Leaked. That's OK, it's scoped to the lifetime of the application. + // Try to get the main display's color space. + static CGColorSpaceRef g_system_color_space = + CGDisplayCopyColorSpace(CGMainDisplayID()); + + if (!g_system_color_space) { + // Use a generic RGB color space. This is better than nothing. + g_system_color_space = CGColorSpaceCreateDeviceRGB(); + + if (g_system_color_space) { + DLOG(WARNING) << + "Couldn't get the main display's color space, using generic"; + } else { + DLOG(ERROR) << "Couldn't get any color space"; + } + } + + return g_system_color_space; +} + +// Add a request for full screen mode. Must be called on the main thread. +void RequestFullScreen(FullScreenMode mode) { + DCHECK_LT(mode, kNumFullScreenModes); + if (mode >= kNumFullScreenModes) + return; + + DCHECK_GE(g_full_screen_requests[mode], 0); + g_full_screen_requests[mode] = std::max(g_full_screen_requests[mode] + 1, 1); + SetUIMode(); +} + +// Release a request for full screen mode. Must be called on the main thread. +void ReleaseFullScreen(FullScreenMode mode) { + DCHECK_LT(mode, kNumFullScreenModes); + if (mode >= kNumFullScreenModes) + return; + + DCHECK_GT(g_full_screen_requests[mode], 0); + g_full_screen_requests[mode] = std::max(g_full_screen_requests[mode] - 1, 0); + SetUIMode(); +} + +// Switches full screen modes. Releases a request for |from_mode| and adds a +// new request for |to_mode|. Must be called on the main thread. +void SwitchFullScreenModes(FullScreenMode from_mode, FullScreenMode to_mode) { + DCHECK_LT(from_mode, kNumFullScreenModes); + DCHECK_LT(to_mode, kNumFullScreenModes); + if (from_mode >= kNumFullScreenModes || to_mode >= kNumFullScreenModes) + return; + + DCHECK_GT(g_full_screen_requests[from_mode], 0); + DCHECK_GE(g_full_screen_requests[to_mode], 0); + g_full_screen_requests[from_mode] = + std::max(g_full_screen_requests[from_mode] - 1, 0); + g_full_screen_requests[to_mode] = + std::max(g_full_screen_requests[to_mode] + 1, 1); + SetUIMode(); +} + +void SetCursorVisibility(bool visible) { + if (visible) + [NSCursor unhide]; + else + [NSCursor hide]; +} + +bool ShouldWindowsMiniaturizeOnDoubleClick() { + // We use an undocumented method in Cocoa; if it doesn't exist, default to + // |true|. If it ever goes away, we can do (using an undocumented pref key): + // NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + // return ![defaults objectForKey:@"AppleMiniaturizeOnDoubleClick"] || + // [defaults boolForKey:@"AppleMiniaturizeOnDoubleClick"]; + BOOL methodImplemented = + [NSWindow respondsToSelector:@selector(_shouldMiniaturizeOnDoubleClick)]; + DCHECK(methodImplemented); + return !methodImplemented || + [NSWindow performSelector:@selector(_shouldMiniaturizeOnDoubleClick)]; +} + +void ActivateProcess(pid_t pid) { + ProcessSerialNumber process; + OSStatus status = GetProcessForPID(pid, &process); + if (status == noErr) { + SetFrontProcess(&process); + } else { + OSSTATUS_DLOG(WARNING, status) << "Unable to get process for pid " << pid; + } +} + +bool AmIForeground() { + ProcessSerialNumber foreground_psn = { 0 }; + OSErr err = GetFrontProcess(&foreground_psn); + if (err != noErr) { + OSSTATUS_DLOG(WARNING, err) << "GetFrontProcess"; + return false; + } + + ProcessSerialNumber my_psn = { 0, kCurrentProcess }; + + Boolean result = FALSE; + err = SameProcess(&foreground_psn, &my_psn, &result); + if (err != noErr) { + OSSTATUS_DLOG(WARNING, err) << "SameProcess"; + return false; + } + + return result; +} + +bool SetFileBackupExclusion(const FilePath& file_path) { + NSString* file_path_ns = + [NSString stringWithUTF8String:file_path.value().c_str()]; + NSURL* file_url = [NSURL fileURLWithPath:file_path_ns]; + + // When excludeByPath is true the application must be running with root + // privileges (admin for 10.6 and earlier) but the URL does not have to + // already exist. When excludeByPath is false the URL must already exist but + // can be used in non-root (or admin as above) mode. We use false so that + // non-root (or admin) users don't get their TimeMachine drive filled up with + // unnecessary backups. + OSStatus os_err = + CSBackupSetItemExcluded(base::mac::NSToCFCast(file_url), TRUE, FALSE); + if (os_err != noErr) { + OSSTATUS_DLOG(WARNING, os_err) + << "Failed to set backup exclusion for file '" + << file_path.value().c_str() << "'"; + } + return os_err == noErr; +} + +void SetProcessName(CFStringRef process_name) { + if (!process_name || CFStringGetLength(process_name) == 0) { + NOTREACHED() << "SetProcessName given bad name."; + return; + } + + if (![NSThread isMainThread]) { + NOTREACHED() << "Should only set process name from main thread."; + return; + } + + // Warning: here be dragons! This is SPI reverse-engineered from WebKit's + // plugin host, and could break at any time (although realistically it's only + // likely to break in a new major release). + // When 10.7 is available, check that this still works, and update this + // comment for 10.8. + + // Private CFType used in these LaunchServices calls. + typedef CFTypeRef PrivateLSASN; + typedef PrivateLSASN (*LSGetCurrentApplicationASNType)(); + typedef OSStatus (*LSSetApplicationInformationItemType)(int, PrivateLSASN, + CFStringRef, + CFStringRef, + CFDictionaryRef*); + + static LSGetCurrentApplicationASNType ls_get_current_application_asn_func = + NULL; + static LSSetApplicationInformationItemType + ls_set_application_information_item_func = NULL; + static CFStringRef ls_display_name_key = NULL; + + static bool did_symbol_lookup = false; + if (!did_symbol_lookup) { + did_symbol_lookup = true; + CFBundleRef launch_services_bundle = + CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices")); + if (!launch_services_bundle) { + DLOG(ERROR) << "Failed to look up LaunchServices bundle"; + return; + } + + ls_get_current_application_asn_func = + reinterpret_cast<LSGetCurrentApplicationASNType>( + CFBundleGetFunctionPointerForName( + launch_services_bundle, CFSTR("_LSGetCurrentApplicationASN"))); + if (!ls_get_current_application_asn_func) + DLOG(ERROR) << "Could not find _LSGetCurrentApplicationASN"; + + ls_set_application_information_item_func = + reinterpret_cast<LSSetApplicationInformationItemType>( + CFBundleGetFunctionPointerForName( + launch_services_bundle, + CFSTR("_LSSetApplicationInformationItem"))); + if (!ls_set_application_information_item_func) + DLOG(ERROR) << "Could not find _LSSetApplicationInformationItem"; + + CFStringRef* key_pointer = reinterpret_cast<CFStringRef*>( + CFBundleGetDataPointerForName(launch_services_bundle, + CFSTR("_kLSDisplayNameKey"))); + ls_display_name_key = key_pointer ? *key_pointer : NULL; + if (!ls_display_name_key) + DLOG(ERROR) << "Could not find _kLSDisplayNameKey"; + + // Internally, this call relies on the Mach ports that are started up by the + // Carbon Process Manager. In debug builds this usually happens due to how + // the logging layers are started up; but in release, it isn't started in as + // much of a defined order. So if the symbols had to be loaded, go ahead + // and force a call to make sure the manager has been initialized and hence + // the ports are opened. + ProcessSerialNumber psn; + GetCurrentProcess(&psn); + } + if (!ls_get_current_application_asn_func || + !ls_set_application_information_item_func || + !ls_display_name_key) { + return; + } + + PrivateLSASN asn = ls_get_current_application_asn_func(); + // Constant used by WebKit; what exactly it means is unknown. + const int magic_session_constant = -2; + OSErr err = + ls_set_application_information_item_func(magic_session_constant, asn, + ls_display_name_key, + process_name, + NULL /* optional out param */); + OSSTATUS_DLOG_IF(ERROR, err != noErr, err) + << "Call to set process name failed"; +} + +// 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()) + return false; + + if (is_hidden) + *is_hidden = IsHiddenLoginItem(item); + + return true; +} + +void AddToLoginItems(bool hide_on_startup) { + ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp()); + if (item.get() && (IsHiddenLoginItem(item) == hide_on_startup)) { + return; // Already is a login item with required hide flag. + } + + ScopedCFTypeRef<LSSharedFileListRef> login_items(LSSharedFileListCreate( + NULL, kLSSharedFileListSessionLoginItems, NULL)); + + if (!login_items.get()) { + DLOG(ERROR) << "Couldn't get a Login Items list."; + return; + } + + // Remove the old item, it has wrong hide flag, we'll create a new one. + if (item.get()) { + LSSharedFileListItemRemove(login_items, item); + } + + NSURL* url = [NSURL fileURLWithPath:[base::mac::MainBundle() bundlePath]]; + + BOOL hide = hide_on_startup ? YES : NO; + NSDictionary* properties = + [NSDictionary + dictionaryWithObject:[NSNumber numberWithBool:hide] + forKey:(NSString*)kLSSharedFileListLoginItemHidden]; + + ScopedCFTypeRef<LSSharedFileListItemRef> new_item; + new_item.reset(LSSharedFileListInsertItemURL( + login_items, kLSSharedFileListItemLast, NULL, NULL, + reinterpret_cast<CFURLRef>(url), + reinterpret_cast<CFDictionaryRef>(properties), NULL)); + + if (!new_item.get()) { + DLOG(ERROR) << "Couldn't insert current app into Login Items list."; + } +} + +void RemoveFromLoginItems() { + ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp()); + if (!item.get()) + return; + + ScopedCFTypeRef<LSSharedFileListRef> login_items(LSSharedFileListCreate( + NULL, kLSSharedFileListSessionLoginItems, NULL)); + + if (!login_items.get()) { + DLOG(ERROR) << "Couldn't get a Login Items list."; + return; + } + + LSSharedFileListItemRemove(login_items, item); +} + +bool WasLaunchedAsLoginOrResumeItem() { + ProcessSerialNumber psn = { 0, kCurrentProcess }; + + scoped_nsobject<NSDictionary> process_info( + CFToNSCast(ProcessInformationCopyDictionary(&psn, + kProcessDictionaryIncludeAllInformationMask))); + + long long temp = [[process_info objectForKey:@"ParentPSN"] longLongValue]; + ProcessSerialNumber parent_psn = + { (temp >> 32) & 0x00000000FFFFFFFFLL, temp & 0x00000000FFFFFFFFLL }; + + scoped_nsobject<NSDictionary> parent_info( + CFToNSCast(ProcessInformationCopyDictionary(&parent_psn, + kProcessDictionaryIncludeAllInformationMask))); + + // Check that creator process code is that of loginwindow. + BOOL result = + [[parent_info objectForKey:@"FileCreator"] isEqualToString:@"lgnw"]; + + return result == YES; +} + +bool WasLaunchedAsHiddenLoginItem() { + if (!WasLaunchedAsLoginOrResumeItem()) + return false; + + ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp()); + if (!item.get()) { + // Lion can launch items for the resume feature. So log an error only for + // Snow Leopard or earlier. + if (IsOSSnowLeopard()) + DLOG(ERROR) << + "Process launched at Login but can't access Login Item List."; + + return false; + } + return IsHiddenLoginItem(item); +} + +namespace { + +// Returns the running system's Darwin major version. Don't call this, it's +// an implementation detail and its result is meant to be cached by +// MacOSXMinorVersion. +int DarwinMajorVersionInternal() { + // base::OperatingSystemVersionNumbers calls Gestalt, which is a + // higher-level operation than is needed. It might perform unnecessary + // operations. On 10.6, it was observed to be able to spawn threads (see + // http://crbug.com/53200). It might also read files or perform other + // blocking operations. Actually, nobody really knows for sure just what + // Gestalt might do, or what it might be taught to do in the future. + // + // uname, on the other hand, is implemented as a simple series of sysctl + // system calls to obtain the relevant data from the kernel. The data is + // compiled right into the kernel, so no threads or blocking or other + // funny business is necessary. + + struct utsname uname_info; + if (uname(&uname_info) != 0) { + DPLOG(ERROR) << "uname"; + return 0; + } + + if (strcmp(uname_info.sysname, "Darwin") != 0) { + DLOG(ERROR) << "unexpected uname sysname " << uname_info.sysname; + return 0; + } + + int darwin_major_version = 0; + char* dot = strchr(uname_info.release, '.'); + if (dot) { + if (!base::StringToInt(base::StringPiece(uname_info.release, + dot - uname_info.release), + &darwin_major_version)) { + dot = NULL; + } + } + + if (!dot) { + DLOG(ERROR) << "could not parse uname release " << uname_info.release; + return 0; + } + + return darwin_major_version; +} + +// Returns the running system's Mac OS X minor version. This is the |y| value +// in 10.y or 10.y.z. Don't call this, it's an implementation detail and the +// result is meant to be cached by MacOSXMinorVersion. +int MacOSXMinorVersionInternal() { + int darwin_major_version = DarwinMajorVersionInternal(); + + // The Darwin major version is always 4 greater than the Mac OS X minor + // version for Darwin versions beginning with 6, corresponding to Mac OS X + // 10.2. Since this correspondence may change in the future, warn when + // encountering a version higher than anything seen before. Older Darwin + // versions, or versions that can't be determined, result in + // 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 " + << base::IntToString(darwin_major_version) << " is Mac OS X 10." + << base::IntToString(mac_os_x_minor_version); + + return mac_os_x_minor_version; +} + +// Returns the running system's Mac OS X minor version. This is the |y| value +// in 10.y or 10.y.z. +int MacOSXMinorVersion() { + static int mac_os_x_minor_version = MacOSXMinorVersionInternal(); + return mac_os_x_minor_version; +} + +enum { + SNOW_LEOPARD_MINOR_VERSION = 6, + LION_MINOR_VERSION = 7, + MOUNTAIN_LION_MINOR_VERSION = 8, +}; + +} // namespace + +#if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GE_10_7) +bool IsOSSnowLeopard() { + return MacOSXMinorVersion() == SNOW_LEOPARD_MINOR_VERSION; +} +#endif + +#if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GT_10_7) +bool IsOSLion() { + return MacOSXMinorVersion() == LION_MINOR_VERSION; +} +#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; +} +#endif + +#if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GT_10_8) +bool IsOSMountainLion() { + return MacOSXMinorVersion() == MOUNTAIN_LION_MINOR_VERSION; +} +#endif + +#if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GE_10_8) +bool IsOSMountainLionOrLater() { + return MacOSXMinorVersion() >= MOUNTAIN_LION_MINOR_VERSION; +} +#endif + +#if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GT_10_8) +bool IsOSLaterThanMountainLion_DontCallThis() { + return MacOSXMinorVersion() > MOUNTAIN_LION_MINOR_VERSION; +} +#endif + +namespace { + +// ScopedGenericObj functor for IOObjectRelease(). +class ScopedReleaseIOObject { + public: + void operator()(io_object_t x) const { + IOObjectRelease(x); + } +}; + +} // namespace + +std::string GetModelIdentifier() { + ScopedGenericObj<io_service_t, ScopedReleaseIOObject> + platform_expert(IOServiceGetMatchingService( + kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"))); + if (!platform_expert) + return ""; + ScopedCFTypeRef<CFDataRef> model_data( + static_cast<CFDataRef>(IORegistryEntryCreateCFProperty( + platform_expert, + CFSTR("model"), + kCFAllocatorDefault, + 0))); + if (!model_data) + return ""; + return reinterpret_cast<const char*>( + CFDataGetBytePtr(model_data)); +} + +bool ParseModelIdentifier(const std::string& ident, + std::string* type, + int32* major, + int32* minor) { + size_t number_loc = ident.find_first_of("0123456789"); + if (number_loc == std::string::npos) + return false; + size_t comma_loc = ident.find(',', number_loc); + if (comma_loc == std::string::npos) + return false; + int32 major_tmp, minor_tmp; + std::string::const_iterator begin = ident.begin(); + if (!StringToInt( + StringPiece(begin + number_loc, begin + comma_loc), &major_tmp) || + !StringToInt( + StringPiece(begin + comma_loc + 1, ident.end()), &minor_tmp)) + return false; + *type = ident.substr(0, number_loc); + *major = major_tmp; + *minor = minor_tmp; + return true; +} + +} // namespace mac +} // namespace base
diff --git a/src/base/mac/mac_util_unittest.mm b/src/base/mac/mac_util_unittest.mm new file mode 100644 index 0000000..d69e077 --- /dev/null +++ b/src/base/mac/mac_util_unittest.mm
@@ -0,0 +1,212 @@ +// 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. + +#import <Cocoa/Cocoa.h> + +#include "base/mac/mac_util.h" + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/mac/foundation_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/memory/scoped_nsobject.h" +#include "base/sys_info.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +namespace base { +namespace mac { + +namespace { + +typedef PlatformTest MacUtilTest; + +TEST_F(MacUtilTest, TestFSRef) { + FSRef ref; + std::string path("/System/Library"); + + ASSERT_TRUE(FSRefFromPath(path, &ref)); + EXPECT_EQ(path, PathFromFSRef(ref)); +} + +TEST_F(MacUtilTest, GetUserDirectoryTest) { + // Try a few keys, make sure they come back with non-empty paths. + FilePath caches_dir; + EXPECT_TRUE(GetUserDirectory(NSCachesDirectory, &caches_dir)); + EXPECT_FALSE(caches_dir.empty()); + + FilePath application_support_dir; + EXPECT_TRUE(GetUserDirectory(NSApplicationSupportDirectory, + &application_support_dir)); + EXPECT_FALSE(application_support_dir.empty()); + + FilePath library_dir; + EXPECT_TRUE(GetUserDirectory(NSLibraryDirectory, &library_dir)); + EXPECT_FALSE(library_dir.empty()); +} + +TEST_F(MacUtilTest, TestLibraryPath) { + FilePath library_dir = GetUserLibraryPath(); + // Make sure the string isn't empty. + EXPECT_FALSE(library_dir.value().empty()); +} + +TEST_F(MacUtilTest, TestGetAppBundlePath) { + FilePath out; + + // Make sure it doesn't crash. + out = GetAppBundlePath(FilePath()); + EXPECT_TRUE(out.empty()); + + // Some more invalid inputs. + const char* invalid_inputs[] = { + "/", "/foo", "foo", "/foo/bar.", "foo/bar.", "/foo/bar./bazquux", + "foo/bar./bazquux", "foo/.app", "//foo", + }; + for (size_t i = 0; i < arraysize(invalid_inputs); i++) { + out = GetAppBundlePath(FilePath(invalid_inputs[i])); + EXPECT_TRUE(out.empty()) << "loop: " << i; + } + + // Some valid inputs; this and |expected_outputs| should be in sync. + struct { + const char *in; + const char *expected_out; + } valid_inputs[] = { + { "FooBar.app/", "FooBar.app" }, + { "/FooBar.app", "/FooBar.app" }, + { "/FooBar.app/", "/FooBar.app" }, + { "//FooBar.app", "//FooBar.app" }, + { "/Foo/Bar.app", "/Foo/Bar.app" }, + { "/Foo/Bar.app/", "/Foo/Bar.app" }, + { "/F/B.app", "/F/B.app" }, + { "/F/B.app/", "/F/B.app" }, + { "/Foo/Bar.app/baz", "/Foo/Bar.app" }, + { "/Foo/Bar.app/baz/", "/Foo/Bar.app" }, + { "/Foo/Bar.app/baz/quux.app/quuux", "/Foo/Bar.app" }, + { "/Applications/Google Foo.app/bar/Foo Helper.app/quux/Foo Helper", + "/Applications/Google Foo.app" }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(valid_inputs); i++) { + out = GetAppBundlePath(FilePath(valid_inputs[i].in)); + EXPECT_FALSE(out.empty()) << "loop: " << i; + EXPECT_STREQ(valid_inputs[i].expected_out, + out.value().c_str()) << "loop: " << i; + } +} + +TEST_F(MacUtilTest, TestExcludeFileFromBackups) { + // The file must already exist in order to set its exclusion property. + ScopedTempDir temp_dir_; + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + FilePath dummy_file_path = temp_dir_.path().Append("DummyFile"); + const char dummy_data[] = "All your base are belong to us!"; + // Dump something real into the file. + ASSERT_EQ(static_cast<int>(arraysize(dummy_data)), + file_util::WriteFile(dummy_file_path, dummy_data, arraysize(dummy_data))); + NSString* fileURLString = + [NSString stringWithUTF8String:dummy_file_path.value().c_str()]; + NSURL* fileURL = [NSURL URLWithString:fileURLString]; + // Initial state should be non-excluded. + EXPECT_FALSE(CSBackupIsItemExcluded(base::mac::NSToCFCast(fileURL), NULL)); + // Exclude the file. + EXPECT_TRUE(SetFileBackupExclusion(dummy_file_path)); + // SetFileBackupExclusion never excludes by path. + Boolean excluded_by_path = FALSE; + Boolean excluded = + CSBackupIsItemExcluded(base::mac::NSToCFCast(fileURL), &excluded_by_path); + EXPECT_TRUE(excluded); + EXPECT_FALSE(excluded_by_path); +} + +TEST_F(MacUtilTest, CopyNSImageToCGImage) { + 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) { + scoped_nsobject<NSArray> array([[NSArray alloc] initWithObjects:@"foo", nil]); + EXPECT_EQ(1U, [array retainCount]); + + NSObjectRetain(array); + EXPECT_EQ(2U, [array retainCount]); + + NSObjectRelease(array); + EXPECT_EQ(1U, [array retainCount]); +} + +TEST_F(MacUtilTest, IsOSEllipsis) { + int32 major, minor, bugfix; + base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix); + + if (major == 10) { + if (minor == 6) { + EXPECT_TRUE(IsOSSnowLeopard()); + EXPECT_FALSE(IsOSLion()); + EXPECT_TRUE(IsOSLionOrEarlier()); + EXPECT_FALSE(IsOSLionOrLater()); + EXPECT_FALSE(IsOSMountainLion()); + EXPECT_FALSE(IsOSMountainLionOrLater()); + EXPECT_FALSE(IsOSLaterThanMountainLion_DontCallThis()); + } else if (minor == 7) { + EXPECT_FALSE(IsOSSnowLeopard()); + EXPECT_TRUE(IsOSLion()); + EXPECT_TRUE(IsOSLionOrEarlier()); + EXPECT_TRUE(IsOSLionOrLater()); + EXPECT_FALSE(IsOSMountainLion()); + EXPECT_FALSE(IsOSMountainLionOrLater()); + EXPECT_FALSE(IsOSLaterThanMountainLion_DontCallThis()); + } else if (minor == 8) { + EXPECT_FALSE(IsOSSnowLeopard()); + EXPECT_FALSE(IsOSLion()); + EXPECT_FALSE(IsOSLionOrEarlier()); + EXPECT_TRUE(IsOSLionOrLater()); + EXPECT_TRUE(IsOSMountainLion()); + EXPECT_TRUE(IsOSMountainLionOrLater()); + EXPECT_FALSE(IsOSLaterThanMountainLion_DontCallThis()); + } else { + // Not five, six, seven, or eight. Ah, ah, ah. + EXPECT_TRUE(false); + } + } else { + // Not ten. What you gonna do? + EXPECT_FALSE(true); + } +} + +TEST_F(MacUtilTest, ParseModelIdentifier) { + std::string model; + int32 major = 1, minor = 2; + + EXPECT_FALSE(ParseModelIdentifier("", &model, &major, &minor)); + EXPECT_EQ(0U, model.length()); + EXPECT_EQ(1, major); + EXPECT_EQ(2, minor); + EXPECT_FALSE(ParseModelIdentifier("FooBar", &model, &major, &minor)); + + EXPECT_TRUE(ParseModelIdentifier("MacPro4,1", &model, &major, &minor)); + EXPECT_EQ(model, "MacPro"); + EXPECT_EQ(4, major); + EXPECT_EQ(1, minor); + + EXPECT_TRUE(ParseModelIdentifier("MacBookPro6,2", &model, &major, &minor)); + EXPECT_EQ(model, "MacBookPro"); + EXPECT_EQ(6, major); + EXPECT_EQ(2, minor); +} + +} // namespace + +} // namespace mac +} // namespace base
diff --git a/src/base/mac/objc_property_releaser.h b/src/base/mac/objc_property_releaser.h new file mode 100644 index 0000000..973d793 --- /dev/null +++ b/src/base/mac/objc_property_releaser.h
@@ -0,0 +1,127 @@ +// 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. + +#ifndef BASE_MAC_OBJC_PROPERTY_RELEASER_H_ +#define BASE_MAC_OBJC_PROPERTY_RELEASER_H_ + +#import <Foundation/Foundation.h> + +#include "base/base_export.h" + +namespace base { +namespace mac { + +// ObjCPropertyReleaser is a C++ class that can automatically release +// synthesized Objective-C properties marked "retain" or "copy". The expected +// use is to place an ObjCPropertyReleaser object within an Objective-C class +// definition. When built with the -fobjc-call-cxx-cdtors compiler option, +// the ObjCPropertyReleaser's destructor will be called when the Objective-C +// object that owns it is deallocated, and it will send a -release message to +// the instance variables backing the appropriate properties. If +// -fobjc-call-cxx-cdtors is not in use, ObjCPropertyReleaser's +// ReleaseProperties method can be called from -dealloc to achieve the same +// effect. +// +// Example usage: +// +// @interface AllaysIBF : NSObject { +// @private +// NSString* string_; +// NSMutableDictionary* dictionary_; +// NSString* notAProperty_; +// IBFDelegate* delegate_; // weak +// +// // It's recommended to put the class name into the property releaser's +// // instance variable name to gracefully handle subclassing, where +// // multiple classes in a hierarchy might want their own property +// // releasers. +// base::mac::ObjCPropertyReleaser propertyReleaser_AllaysIBF_; +// } +// +// @property(retain, nonatomic) NSString* string; +// @property(copy, nonatomic) NSMutableDictionary* dictionary; +// @property(assign, nonatomic) IBFDelegate* delegate; +// @property(retain, nonatomic) NSString* autoProp; +// +// @end // @interface AllaysIBF +// +// @implementation AllaysIBF +// +// @synthesize string = string_; +// @synthesize dictionary = dictionary_; +// @synthesize delegate = delegate_; +// @synthesize autoProp; +// +// - (id)init { +// if ((self = [super init])) { +// // Initialize with [AllaysIBF class]. Never use [self class] because +// // in the case of subclassing, it will return the most specific class +// // for |self|, which may not be the same as [AllaysIBF class]. This +// // would cause AllaysIBF's -.cxx_destruct or -dealloc to release +// // instance variables that only exist in subclasses, likely causing +// // mass disaster. +// propertyReleaser_AllaysIBF_.Init(self, [AllaysIBF class]); +// } +// return self; +// } +// +// @end // @implementation AllaysIBF +// +// When an instance of AllaysIBF is deallocated, the ObjCPropertyReleaser will +// send a -release message to string_, dictionary_, and the compiler-created +// autoProp instance variables. No -release will be sent to delegate_ as it +// is marked "assign" and not "retain" or "copy". No -release will be sent to +// notAProperty_ because it doesn't correspond to any declared @property. +// +// Another way of doing this would be to provide a base class that others can +// inherit from, and to have the base class' -dealloc walk the property lists +// of all subclasses in an object to send the -release messages. Since this +// involves a base reaching into its subclasses, it's deemed scary, so don't +// do it. ObjCPropertyReleaser's design ensures that the property releaser +// will only operate on instance variables in the immediate object in which +// the property releaser is placed. + +class BASE_EXPORT ObjCPropertyReleaser { + public: + // ObjCPropertyReleaser can only be owned by an Objective-C object, so its + // memory is always guaranteed to be 0-initialized. Not defining the default + // constructor can prevent an otherwise no-op -.cxx_construct method from + // showing up in Objective-C classes that contain a ObjCPropertyReleaser. + + // Upon destruction (expected to occur from an Objective-C object's + // -.cxx_destruct method), release all properties. + ~ObjCPropertyReleaser() { + ReleaseProperties(); + } + + // Initialize this object so that it's armed to release the properties of + // object |object|, which must be of type |classy|. The class argument must + // be supplied separately and cannot be gleaned from the object's own type + // because an object will allays identify itself as the most-specific type + // that describes it, but the ObjCPropertyReleaser needs to know which class + // type in the class hierarchy it's responsible for releasing properties + // for. For the same reason, Init must be called with a |classy| argument + // initialized using a +class (class) method such as [MyClass class], and + // never a -class (instance) method such as [self class]. + // + // -.cxx_construct can only call the default constructor, but + // ObjCPropertyReleaser needs to know about the Objective-C object that owns + // it, so this can't be handled in a constructor, it needs to be a distinct + // Init method. + void Init(id object, Class classy); + + // Release all of the properties in object_ defined in class_ as either + // "retain" or "copy" and with an identifiable backing instance variable. + // Properties must be synthesized to have identifiable instance variables. + void ReleaseProperties(); + + private: + id object_; + Class class_; +}; + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_OBJC_PROPERTY_RELEASER_H_
diff --git a/src/base/mac/objc_property_releaser.mm b/src/base/mac/objc_property_releaser.mm new file mode 100644 index 0000000..f7ee88f --- /dev/null +++ b/src/base/mac/objc_property_releaser.mm
@@ -0,0 +1,131 @@ +// 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. + +#import "base/mac/objc_property_releaser.h" + +#import <objc/runtime.h> +#include <stdlib.h> + +#include <string> + +#include "base/logging.h" + +namespace base { +namespace mac { + +namespace { + +// Returns the name of the instance variable backing the property, if known, +// if the property is marked "retain" or "copy". If the instance variable name +// is not known (perhaps because it was not automatically associated with the +// property by @synthesize) or if the property is not "retain" or "copy", +// returns an empty string. +std::string ReleasableInstanceName(objc_property_t property) { + // TODO(mark): Starting in newer system releases, the Objective-C runtime + // provides a function to break the property attribute string into + // individual attributes (property_copyAttributeList), as well as a function + // to look up the value of a specific attribute + // (property_copyAttributeValue). When the SDK defining that interface is + // final, this function should be adapted to walk the attribute list as + // returned by property_copyAttributeList when that function is available in + // preference to scanning through the attribute list manually. + + // The format of the string returned by property_getAttributes is documented + // at + // http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW6 + const char* property_attributes = property_getAttributes(property); + + std::string instance_name; + bool releasable = false; + while (*property_attributes) { + char name = *property_attributes; + + const char* value = ++property_attributes; + while (*property_attributes && *property_attributes != ',') { + ++property_attributes; + } + + switch (name) { + // It might seem intelligent to check the type ('T') attribute to verify + // that it identifies an NSObject-derived type (the attribute value + // begins with '@'.) This is a bad idea beacuse it fails to identify + // CFTypeRef-based properties declared as __attribute__((NSObject)), + // which just show up as pointers to their underlying CFType structs. + // + // Quoting + // http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html#//apple_ref/doc/uid/TP30001163-CH17-SW27 + // + // > In Mac OS X v10.6 and later, you can use the __attribute__ keyword + // > to specify that a Core Foundation property should be treated like + // > an Objective-C object for memory management: + // > @property(retain) __attribute__((NSObject)) CFDictionaryRef + // > myDictionary; + case 'C': // copy + case '&': // retain + releasable = true; + break; + case 'V': // instance variable name + // 'V' is specified as the last attribute to occur in the + // documentation, but empirically, it's not always the last. In + // GC-supported or GC-required code, the 'P' (GC-eligible) attribute + // occurs after 'V'. + instance_name.assign(value, property_attributes - value); + break; + } + + if (*property_attributes) { + ++property_attributes; + } + } + + if (releasable) { + return instance_name; + } + + return std::string(); +} + +} // namespace + +void ObjCPropertyReleaser::Init(id object, Class classy) { + DCHECK(!object_); + DCHECK(!class_); + CHECK([object isKindOfClass:classy]); + + object_ = object; + class_ = classy; +} + +void ObjCPropertyReleaser::ReleaseProperties() { + DCHECK(object_); + DCHECK(class_); + + unsigned int property_count = 0; + objc_property_t* properties = class_copyPropertyList(class_, &property_count); + + for (unsigned int property_index = 0; + property_index < property_count; + ++property_index) { + objc_property_t property = properties[property_index]; + std::string instance_name = ReleasableInstanceName(property); + if (!instance_name.empty()) { + id instance_value = nil; + Ivar instance_variable = + object_getInstanceVariable(object_, instance_name.c_str(), + (void**)&instance_value); + DCHECK(instance_variable); + [instance_value release]; + } + } + + free(properties); + + // Clear object_ and class_ in case this ObjCPropertyReleaser will live on. + // It's only expected to release the properties it supervises once per Init. + object_ = nil; + class_ = nil; +} + +} // namespace mac +} // namespace base
diff --git a/src/base/mac/objc_property_releaser_unittest.mm b/src/base/mac/objc_property_releaser_unittest.mm new file mode 100644 index 0000000..50f81a8 --- /dev/null +++ b/src/base/mac/objc_property_releaser_unittest.mm
@@ -0,0 +1,350 @@ +// 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. + +#import <Foundation/Foundation.h> + +#import "base/mac/objc_property_releaser.h" +#import "base/mac/scoped_nsautorelease_pool.h" +#include "testing/gtest/include/gtest/gtest.h" + +// "When I'm alone, I count myself." +// --Count von Count, http://www.youtube.com/watch?v=FKzszqa9WA4 + +namespace { + +// The number of CountVonCounts outstanding. +int ah_ah_ah; + +// NumberHolder exists to exercise the property attribute string parser by +// providing a named struct and an anonymous union. +struct NumberHolder { + union { + long long sixty_four; + int thirty_two; + short sixteen; + char eight; + } what; + enum { + SIXTY_FOUR, + THIRTY_TWO, + SIXTEEN, + EIGHT + } how; +}; + +} // namespace + +@interface CountVonCount : NSObject<NSCopying> + ++ (CountVonCount*)countVonCount; + +@end // @interface CountVonCount + +@implementation CountVonCount + ++ (CountVonCount*)countVonCount { + return [[[CountVonCount alloc] init] autorelease]; +} + +- (id)init { + ++ah_ah_ah; + return [super init]; +} + +- (void)dealloc { + --ah_ah_ah; + [super dealloc]; +} + +- (id)copyWithZone:(NSZone*)zone { + return [[CountVonCount allocWithZone:zone] init]; +} + +@end // @implementation CountVonCount + +@interface ObjCPropertyTestBase : NSObject { + @private + CountVonCount* baseCvcRetain_; + CountVonCount* baseCvcCopy_; + CountVonCount* baseCvcAssign_; + CountVonCount* baseCvcNotProperty_; + CountVonCount* baseCvcNil_; + CountVonCount* baseCvcCustom_; + int baseInt_; + double baseDouble_; + void* basePointer_; + NumberHolder baseStruct_; + + base::mac::ObjCPropertyReleaser propertyReleaser_ObjCPropertyTestBase_; +} + +@property(retain, nonatomic) CountVonCount* baseCvcRetain; +@property(copy, nonatomic) CountVonCount* baseCvcCopy; +@property(assign, nonatomic) CountVonCount* baseCvcAssign; +@property(retain, nonatomic) CountVonCount* baseCvcNil; +@property(retain, nonatomic, getter=baseCustom, setter=setBaseCustom:) + CountVonCount* baseCvcCustom; +@property(retain, nonatomic) CountVonCount* baseCvcDynamic; +@property(assign, nonatomic) int baseInt; +@property(assign, nonatomic) double baseDouble; +@property(assign, nonatomic) void* basePointer; +@property(assign, nonatomic) NumberHolder baseStruct; + +- (void)setBaseCvcNotProperty:(CountVonCount*)cvc; + +@end // @interface ObjCPropertyTestBase + +@implementation ObjCPropertyTestBase + +@synthesize baseCvcRetain = baseCvcRetain_; +@synthesize baseCvcCopy = baseCvcCopy_; +@synthesize baseCvcAssign = baseCvcAssign_; +@synthesize baseCvcNil = baseCvcNil_; +@synthesize baseCvcCustom = baseCvcCustom_; +@dynamic baseCvcDynamic; +@synthesize baseInt = baseInt_; +@synthesize baseDouble = baseDouble_; +@synthesize basePointer = basePointer_; +@synthesize baseStruct = baseStruct_; + +- (id)init { + if ((self = [super init])) { + propertyReleaser_ObjCPropertyTestBase_.Init( + self, [ObjCPropertyTestBase class]); + } + return self; +} + +- (void)dealloc { + [baseCvcNotProperty_ release]; + [super dealloc]; +} + +- (void)setBaseCvcNotProperty:(CountVonCount*)cvc { + if (cvc != baseCvcNotProperty_) { + [baseCvcNotProperty_ release]; + baseCvcNotProperty_ = [cvc retain]; + } +} + +@end // @implementation ObjCPropertyTestBase + +@protocol ObjCPropertyTestProtocol + +@property(retain, nonatomic) CountVonCount* protoCvcRetain; +@property(copy, nonatomic) CountVonCount* protoCvcCopy; +@property(assign, nonatomic) CountVonCount* protoCvcAssign; +@property(retain, nonatomic) CountVonCount* protoCvcNil; +@property(retain, nonatomic, getter=protoCustom, setter=setProtoCustom:) + CountVonCount* protoCvcCustom; +@property(retain, nonatomic) CountVonCount* protoCvcDynamic; +@property(assign, nonatomic) int protoInt; +@property(assign, nonatomic) double protoDouble; +@property(assign, nonatomic) void* protoPointer; +@property(assign, nonatomic) NumberHolder protoStruct; + +@end // @protocol ObjCPropertyTestProtocol + +@interface ObjCPropertyTestDerived + : ObjCPropertyTestBase<ObjCPropertyTestProtocol> { + @private + CountVonCount* derivedCvcRetain_; + CountVonCount* derivedCvcCopy_; + CountVonCount* derivedCvcAssign_; + CountVonCount* derivedCvcNotProperty_; + CountVonCount* derivedCvcNil_; + CountVonCount* derivedCvcCustom_; + int derivedInt_; + double derivedDouble_; + void* derivedPointer_; + NumberHolder derivedStruct_; + + CountVonCount* protoCvcRetain_; + CountVonCount* protoCvcCopy_; + CountVonCount* protoCvcAssign_; + CountVonCount* protoCvcNil_; + CountVonCount* protoCvcCustom_; + int protoInt_; + double protoDouble_; + void* protoPointer_; + NumberHolder protoStruct_; + + base::mac::ObjCPropertyReleaser propertyReleaser_ObjCPropertyTestDerived_; +} + +@property(retain, nonatomic) CountVonCount* derivedCvcRetain; +@property(copy, nonatomic) CountVonCount* derivedCvcCopy; +@property(assign, nonatomic) CountVonCount* derivedCvcAssign; +@property(retain, nonatomic) CountVonCount* derivedCvcNil; +@property(retain, nonatomic, getter=derivedCustom, setter=setDerivedCustom:) + CountVonCount* derivedCvcCustom; +@property(retain, nonatomic) CountVonCount* derivedCvcDynamic; +@property(assign, nonatomic) int derivedInt; +@property(assign, nonatomic) double derivedDouble; +@property(assign, nonatomic) void* derivedPointer; +@property(assign, nonatomic) NumberHolder derivedStruct; + +- (void)setDerivedCvcNotProperty:(CountVonCount*)cvc; + +@end // @interface ObjCPropertyTestDerived + +@implementation ObjCPropertyTestDerived + +@synthesize derivedCvcRetain = derivedCvcRetain_; +@synthesize derivedCvcCopy = derivedCvcCopy_; +@synthesize derivedCvcAssign = derivedCvcAssign_; +@synthesize derivedCvcNil = derivedCvcNil_; +@synthesize derivedCvcCustom = derivedCvcCustom_; +@dynamic derivedCvcDynamic; +@synthesize derivedInt = derivedInt_; +@synthesize derivedDouble = derivedDouble_; +@synthesize derivedPointer = derivedPointer_; +@synthesize derivedStruct = derivedStruct_; + +@synthesize protoCvcRetain = protoCvcRetain_; +@synthesize protoCvcCopy = protoCvcCopy_; +@synthesize protoCvcAssign = protoCvcAssign_; +@synthesize protoCvcNil = protoCvcNil_; +@synthesize protoCvcCustom = protoCvcCustom_; +@dynamic protoCvcDynamic; +@synthesize protoInt = protoInt_; +@synthesize protoDouble = protoDouble_; +@synthesize protoPointer = protoPointer_; +@synthesize protoStruct = protoStruct_; + +- (id)init { + if ((self = [super init])) { + propertyReleaser_ObjCPropertyTestDerived_.Init( + self, [ObjCPropertyTestDerived class]); + } + return self; +} + +- (void)dealloc { + [derivedCvcNotProperty_ release]; + [super dealloc]; +} + +- (void)setDerivedCvcNotProperty:(CountVonCount*)cvc { + if (cvc != derivedCvcNotProperty_) { + [derivedCvcNotProperty_ release]; + derivedCvcNotProperty_ = [cvc retain]; + } +} + +@end // @implementation ObjCPropertyTestDerived + +namespace { + +TEST(ObjCPropertyReleaserTest, SesameStreet) { + ObjCPropertyTestDerived* test_object = [[ObjCPropertyTestDerived alloc] init]; + + // Assure a clean slate. + EXPECT_EQ(0, ah_ah_ah); + EXPECT_EQ(1U, [test_object retainCount]); + + CountVonCount* baseAssign = [[CountVonCount alloc] init]; + CountVonCount* derivedAssign = [[CountVonCount alloc] init]; + CountVonCount* protoAssign = [[CountVonCount alloc] init]; + + // Make sure that worked before things get more involved. + EXPECT_EQ(3, ah_ah_ah); + + { + base::mac::ScopedNSAutoreleasePool pool; + + test_object.baseCvcRetain = [CountVonCount countVonCount]; + test_object.baseCvcCopy = [CountVonCount countVonCount]; + test_object.baseCvcAssign = baseAssign; + test_object.baseCvcCustom = [CountVonCount countVonCount]; + [test_object setBaseCvcNotProperty:[CountVonCount countVonCount]]; + + // That added 4 objects, plus 1 more that was copied. + EXPECT_EQ(8, ah_ah_ah); + + test_object.derivedCvcRetain = [CountVonCount countVonCount]; + test_object.derivedCvcCopy = [CountVonCount countVonCount]; + test_object.derivedCvcAssign = derivedAssign; + test_object.derivedCvcCustom = [CountVonCount countVonCount]; + [test_object setDerivedCvcNotProperty:[CountVonCount countVonCount]]; + + // That added 4 objects, plus 1 more that was copied. + EXPECT_EQ(13, ah_ah_ah); + + test_object.protoCvcRetain = [CountVonCount countVonCount]; + test_object.protoCvcCopy = [CountVonCount countVonCount]; + test_object.protoCvcAssign = protoAssign; + test_object.protoCvcCustom = [CountVonCount countVonCount]; + + // That added 3 objects, plus 1 more that was copied. + EXPECT_EQ(17, ah_ah_ah); + } + + // Now that the autorelease pool has been popped, the 3 objects that were + // copied when placed into the test object will have been deallocated. + EXPECT_EQ(14, ah_ah_ah); + + // Make sure that the setters work and have the expected semantics. + test_object.baseCvcRetain = nil; + test_object.baseCvcCopy = nil; + test_object.baseCvcAssign = nil; + test_object.baseCvcCustom = nil; + test_object.derivedCvcRetain = nil; + test_object.derivedCvcCopy = nil; + test_object.derivedCvcAssign = nil; + test_object.derivedCvcCustom = nil; + test_object.protoCvcRetain = nil; + test_object.protoCvcCopy = nil; + test_object.protoCvcAssign = nil; + test_object.protoCvcCustom = nil; + + // The CountVonCounts marked "retain" and "copy" should have been + // deallocated. Those marked assign should not have been. The only ones that + // should exist now are the ones marked "assign" and the ones held in + // non-property instance variables. + EXPECT_EQ(5, ah_ah_ah); + + { + base::mac::ScopedNSAutoreleasePool pool; + + // Put things back to how they were. + test_object.baseCvcRetain = [CountVonCount countVonCount]; + test_object.baseCvcCopy = [CountVonCount countVonCount]; + test_object.baseCvcAssign = baseAssign; + test_object.baseCvcCustom = [CountVonCount countVonCount]; + test_object.derivedCvcRetain = [CountVonCount countVonCount]; + test_object.derivedCvcCopy = [CountVonCount countVonCount]; + test_object.derivedCvcAssign = derivedAssign; + test_object.derivedCvcCustom = [CountVonCount countVonCount]; + test_object.protoCvcRetain = [CountVonCount countVonCount]; + test_object.protoCvcCopy = [CountVonCount countVonCount]; + test_object.protoCvcAssign = protoAssign; + test_object.protoCvcCustom = [CountVonCount countVonCount]; + + // 9 more CountVonCounts, 3 of which were copied. + EXPECT_EQ(17, ah_ah_ah); + } + + // Now that the autorelease pool has been popped, the 3 copies are gone. + EXPECT_EQ(14, ah_ah_ah); + + // Releasing the test object should get rid of everything that it owns. + [test_object release]; + + // The property releaser should have released all of the CountVonCounts + // associated with properties marked "retain" or "copy". The -dealloc + // methods in each should have released the single non-property objects in + // each. Only the CountVonCounts assigned to the properties marked "assign" + // should remain. + EXPECT_EQ(3, ah_ah_ah); + + [baseAssign release]; + [derivedAssign release]; + [protoAssign release]; + + // Zero! Zero counts! Ah, ah, ah. + EXPECT_EQ(0, ah_ah_ah); +} + +} // namespace
diff --git a/src/base/mac/os_crash_dumps.cc b/src/base/mac/os_crash_dumps.cc new file mode 100644 index 0000000..e6b0996 --- /dev/null +++ b/src/base/mac/os_crash_dumps.cc
@@ -0,0 +1,57 @@ +// Copyright (c) 2010 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/mac/os_crash_dumps.h" + +#include <signal.h> +#include <unistd.h> + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace base { +namespace mac { + +namespace { + +void ExitSignalHandler(int sig) { + // A call to exit() can call atexit() handlers. If we SIGSEGV due + // to a corrupt heap, and if we have an atexit handler that + // allocates or frees memory, we are in trouble if we do not _exit. + _exit(128 + sig); +} + +} // namespace + +void DisableOSCrashDumps() { + // These are the POSIX signals corresponding to the Mach exceptions that + // Apple Crash Reporter handles. See ux_exception() in xnu's + // bsd/uxkern/ux_exception.c and machine_exception() in xnu's + // bsd/dev/*/unix_signal.c. + const int signals_to_intercept[] = { + SIGILL, // EXC_BAD_INSTRUCTION + SIGTRAP, // EXC_BREAKPOINT + SIGFPE, // EXC_ARITHMETIC + SIGBUS, // EXC_BAD_ACCESS + SIGSEGV // EXC_BAD_ACCESS + }; + + // For all these signals, just wire things up so we exit immediately. + for (size_t i = 0; i < arraysize(signals_to_intercept); ++i) { + struct sigaction act = {}; + act.sa_handler = ExitSignalHandler; + + // It is better to allow the signal handler to run on the stack + // registered with sigaltstack(), if one is present. + act.sa_flags = SA_ONSTACK; + + if (sigemptyset(&act.sa_mask) != 0) + DLOG_ERRNO(FATAL) << "sigemptyset() failed"; + if (sigaction(signals_to_intercept[i], &act, NULL) != 0) + DLOG_ERRNO(FATAL) << "sigaction() failed"; + } +} + +} // namespace mac +} // namespace base
diff --git a/src/base/mac/os_crash_dumps.h b/src/base/mac/os_crash_dumps.h new file mode 100644 index 0000000..31d90fb --- /dev/null +++ b/src/base/mac/os_crash_dumps.h
@@ -0,0 +1,22 @@ +// 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. + +#ifndef BASE_MAC_OS_CRASH_DUMPS_H_ +#define BASE_MAC_OS_CRASH_DUMPS_H_ + +#include "base/base_export.h" + +namespace base { +namespace mac { + +// On Mac OS X, it can take a really long time for the OS crash handler to +// process a Chrome crash when debugging symbols are available. This +// translates into a long wait until the process actually dies. This call +// disables Apple Crash Reporter entirely. +BASE_EXPORT void DisableOSCrashDumps(); + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_OS_CRASH_DUMPS_H_
diff --git a/src/base/mac/scoped_aedesc.h b/src/base/mac/scoped_aedesc.h new file mode 100644 index 0000000..a1323c0 --- /dev/null +++ b/src/base/mac/scoped_aedesc.h
@@ -0,0 +1,52 @@ +// Copyright (c) 2010 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_MAC_SCOPED_AEDESC_H_ +#define BASE_MAC_SCOPED_AEDESC_H_ + +#import <CoreServices/CoreServices.h> + +#include "base/basictypes.h" + +namespace base { +namespace mac { + +// The ScopedAEDesc is used to scope AppleEvent descriptors. On creation, +// it will store a NULL descriptor. On destruction, it will dispose of the +// descriptor. +// +// This class is parameterized for additional type safety checks. You can use +// the generic AEDesc type by not providing a template parameter: +// ScopedAEDesc<> desc; +template <typename AEDescType = AEDesc> +class ScopedAEDesc { + public: + ScopedAEDesc() { + AECreateDesc(typeNull, NULL, 0, &desc_); + } + + ~ScopedAEDesc() { + AEDisposeDesc(&desc_); + } + + // Used for in parameters. + operator const AEDescType*() { + return &desc_; + } + + // Used for out parameters. + AEDescType* OutPointer() { + return &desc_; + } + + private: + AEDescType desc_; + + DISALLOW_COPY_AND_ASSIGN(ScopedAEDesc); +}; + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_SCOPED_AEDESC_H_
diff --git a/src/base/mac/scoped_authorizationref.h b/src/base/mac/scoped_authorizationref.h new file mode 100644 index 0000000..6413f2e --- /dev/null +++ b/src/base/mac/scoped_authorizationref.h
@@ -0,0 +1,85 @@ +// 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_MAC_SCOPED_AUTHORIZATIONREF_H_ +#define BASE_MAC_SCOPED_AUTHORIZATIONREF_H_ + +#include <Security/Authorization.h> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" + +// ScopedAuthorizationRef maintains ownership of an AuthorizationRef. It is +// patterned after the scoped_ptr interface. + +namespace base { +namespace mac { + +class ScopedAuthorizationRef { + public: + explicit ScopedAuthorizationRef(AuthorizationRef authorization = NULL) + : authorization_(authorization) { + } + + ~ScopedAuthorizationRef() { + if (authorization_) { + AuthorizationFree(authorization_, kAuthorizationFlagDestroyRights); + } + } + + void reset(AuthorizationRef authorization = NULL) { + if (authorization_ != authorization) { + if (authorization_) { + AuthorizationFree(authorization_, kAuthorizationFlagDestroyRights); + } + authorization_ = authorization; + } + } + + bool operator==(AuthorizationRef that) const { + return authorization_ == that; + } + + bool operator!=(AuthorizationRef that) const { + return authorization_ != that; + } + + operator AuthorizationRef() const { + return authorization_; + } + + AuthorizationRef* operator&() { + return &authorization_; + } + + AuthorizationRef get() const { + return authorization_; + } + + void swap(ScopedAuthorizationRef& that) { + AuthorizationRef temp = that.authorization_; + that.authorization_ = authorization_; + authorization_ = temp; + } + + // ScopedAuthorizationRef::release() is like scoped_ptr<>::release. It is + // NOT a wrapper for AuthorizationFree(). To force a + // ScopedAuthorizationRef object to call AuthorizationFree(), use + // ScopedAuthorizationRef::reset(). + AuthorizationRef release() WARN_UNUSED_RESULT { + AuthorizationRef temp = authorization_; + authorization_ = NULL; + return temp; + } + + private: + AuthorizationRef authorization_; + + DISALLOW_COPY_AND_ASSIGN(ScopedAuthorizationRef); +}; + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_SCOPED_AUTHORIZATIONREF_H_
diff --git a/src/base/mac/scoped_cffiledescriptorref.h b/src/base/mac/scoped_cffiledescriptorref.h new file mode 100644 index 0000000..07196aa --- /dev/null +++ b/src/base/mac/scoped_cffiledescriptorref.h
@@ -0,0 +1,75 @@ +// Copyright 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_MAC_SCOPED_CFFILEDESCRIPTORREF_H_ +#define BASE_MAC_SCOPED_CFFILEDESCRIPTORREF_H_ + +#include <CoreFoundation/CoreFoundation.h> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" + +namespace base { +namespace mac { + +// ScopedCFFileDescriptorRef is designed after ScopedCFTypeRef<>. On +// destruction, it will invalidate the file descriptor. +// ScopedCFFileDescriptorRef (unlike ScopedCFTypeRef<>) does not support RETAIN +// semantics, copying, or assignment, as doing so would increase the chances +// that a file descriptor is invalidated while still in use. +class ScopedCFFileDescriptorRef { + public: + explicit ScopedCFFileDescriptorRef(CFFileDescriptorRef fdref = NULL) + : fdref_(fdref) { + } + + ~ScopedCFFileDescriptorRef() { + if (fdref_) { + CFFileDescriptorInvalidate(fdref_); + CFRelease(fdref_); + } + } + + void reset(CFFileDescriptorRef fdref = NULL) { + if (fdref_ == fdref) + return; + if (fdref_) { + CFFileDescriptorInvalidate(fdref_); + CFRelease(fdref_); + } + fdref_ = fdref; + } + + bool operator==(CFFileDescriptorRef that) const { + return fdref_ == that; + } + + bool operator!=(CFFileDescriptorRef that) const { + return fdref_ != that; + } + + operator CFFileDescriptorRef() const { + return fdref_; + } + + CFFileDescriptorRef get() const { + return fdref_; + } + + CFFileDescriptorRef release() WARN_UNUSED_RESULT { + CFFileDescriptorRef temp = fdref_; + fdref_ = NULL; + return temp; + } + + private: + CFFileDescriptorRef fdref_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCFFileDescriptorRef); +}; + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_SCOPED_CFFILEDESCRIPTORREF_H_
diff --git a/src/base/mac/scoped_cftyperef.h b/src/base/mac/scoped_cftyperef.h new file mode 100644 index 0000000..c6ca46a --- /dev/null +++ b/src/base/mac/scoped_cftyperef.h
@@ -0,0 +1,108 @@ +// 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_MAC_SCOPED_CFTYPEREF_H_ +#define BASE_MAC_SCOPED_CFTYPEREF_H_ + +#include <CoreFoundation/CoreFoundation.h> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_policy.h" + +namespace base { +namespace mac { + +// ScopedCFTypeRef<> is patterned after scoped_ptr<>, but maintains ownership +// of a CoreFoundation object: any object that can be represented as a +// CFTypeRef. Style deviations here are solely for compatibility with +// scoped_ptr<>'s interface, with which everyone is already familiar. +// +// By default, ScopedCFTypeRef<> takes ownership of an object (in the +// constructor or in reset()) by taking over the caller's existing ownership +// claim. The caller must own the object it gives to ScopedCFTypeRef<>, and +// relinquishes an ownership claim to that object. ScopedCFTypeRef<> does not +// call CFRetain(). This behavior is parameterized by the |OwnershipPolicy| +// enum. If the value |RETAIN| is passed (in the constructor or in reset()), +// then ScopedCFTypeRef<> will call CFRetain() on the object, and the initial +// ownership is not changed. + +template<typename CFT> +class ScopedCFTypeRef { + public: + typedef CFT element_type; + + explicit ScopedCFTypeRef( + CFT object = NULL, + base::scoped_policy::OwnershipPolicy policy = base::scoped_policy::ASSUME) + : object_(object) { + if (object_ && policy == base::scoped_policy::RETAIN) + CFRetain(object_); + } + + ScopedCFTypeRef(const ScopedCFTypeRef<CFT>& that) + : object_(that.object_) { + if (object_) + CFRetain(object_); + } + + ~ScopedCFTypeRef() { + if (object_) + CFRelease(object_); + } + + ScopedCFTypeRef& operator=(const ScopedCFTypeRef<CFT>& that) { + reset(that.get(), base::scoped_policy::RETAIN); + return *this; + } + + void reset(CFT object = NULL, + base::scoped_policy::OwnershipPolicy policy = + base::scoped_policy::ASSUME) { + if (object && policy == base::scoped_policy::RETAIN) + CFRetain(object); + if (object_) + CFRelease(object_); + object_ = object; + } + + bool operator==(CFT that) const { + return object_ == that; + } + + bool operator!=(CFT that) const { + return object_ != that; + } + + operator CFT() const { + return object_; + } + + CFT get() const { + return object_; + } + + void swap(ScopedCFTypeRef& that) { + CFT temp = that.object_; + that.object_ = object_; + object_ = temp; + } + + // ScopedCFTypeRef<>::release() is like scoped_ptr<>::release. It is NOT + // a wrapper for CFRelease(). To force a ScopedCFTypeRef<> object to call + // CFRelease(), use ScopedCFTypeRef<>::reset(). + CFT release() WARN_UNUSED_RESULT { + CFT temp = object_; + object_ = NULL; + return temp; + } + + private: + CFT object_; +}; + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_SCOPED_CFTYPEREF_H_
diff --git a/src/base/mac/scoped_ioobject.h b/src/base/mac/scoped_ioobject.h new file mode 100644 index 0000000..854039b --- /dev/null +++ b/src/base/mac/scoped_ioobject.h
@@ -0,0 +1,74 @@ +// 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_MAC_SCOPED_IOOBJECT_H_ +#define BASE_MAC_SCOPED_IOOBJECT_H_ + +#include <IOKit/IOKitLib.h> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" + +namespace base { +namespace mac { + +// Just like ScopedCFTypeRef but for io_object_t and subclasses. +template<typename IOT> +class ScopedIOObject { + public: + typedef IOT element_type; + + explicit ScopedIOObject(IOT object = IO_OBJECT_NULL) + : object_(object) { + } + + ~ScopedIOObject() { + if (object_) + IOObjectRelease(object_); + } + + void reset(IOT object = IO_OBJECT_NULL) { + if (object_) + IOObjectRelease(object_); + object_ = object; + } + + bool operator==(IOT that) const { + return object_ == that; + } + + bool operator!=(IOT that) const { + return object_ != that; + } + + operator IOT() const { + return object_; + } + + IOT get() const { + return object_; + } + + void swap(ScopedIOObject& that) { + IOT temp = that.object_; + that.object_ = object_; + object_ = temp; + } + + IOT release() WARN_UNUSED_RESULT { + IOT temp = object_; + object_ = IO_OBJECT_NULL; + return temp; + } + + private: + IOT object_; + + DISALLOW_COPY_AND_ASSIGN(ScopedIOObject); +}; + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_SCOPED_IOOBJECT_H_
diff --git a/src/base/mac/scoped_launch_data.h b/src/base/mac/scoped_launch_data.h new file mode 100644 index 0000000..e4343b8 --- /dev/null +++ b/src/base/mac/scoped_launch_data.h
@@ -0,0 +1,75 @@ +// 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_MAC_SCOPED_LAUNCH_DATA_H_ +#define BASE_MAC_SCOPED_LAUNCH_DATA_H_ + +#include <launch.h> + +#include <algorithm> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" + +namespace base { +namespace mac { + +// Just like scoped_ptr<> but for launch_data_t. +class ScopedLaunchData { + public: + typedef launch_data_t element_type; + + explicit ScopedLaunchData(launch_data_t object = NULL) + : object_(object) { + } + + ~ScopedLaunchData() { + if (object_) + launch_data_free(object_); + } + + void reset(launch_data_t object = NULL) { + if (object != object_) { + if (object_) + launch_data_free(object_); + object_ = object; + } + } + + bool operator==(launch_data_t that) const { + return object_ == that; + } + + bool operator!=(launch_data_t that) const { + return object_ != that; + } + + operator launch_data_t() const { + return object_; + } + + launch_data_t get() const { + return object_; + } + + void swap(ScopedLaunchData& that) { + std::swap(object_, that.object_); + } + + launch_data_t release() WARN_UNUSED_RESULT { + launch_data_t temp = object_; + object_ = NULL; + return temp; + } + + private: + launch_data_t object_; + + DISALLOW_COPY_AND_ASSIGN(ScopedLaunchData); +}; + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_SCOPED_LAUNCH_DATA_H_
diff --git a/src/base/mac/scoped_mach_port.cc b/src/base/mac/scoped_mach_port.cc new file mode 100644 index 0000000..652e3f4 --- /dev/null +++ b/src/base/mac/scoped_mach_port.cc
@@ -0,0 +1,20 @@ +// 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/mac/scoped_mach_port.h" + +namespace base { +namespace mac { + +ScopedMachPort::ScopedMachPort(mach_port_t port) : port_(port) { +} + +ScopedMachPort::~ScopedMachPort() { + if (port_ != MACH_PORT_NULL) { + mach_port_deallocate(mach_task_self(), port_); + } +} + +} // namespace mac +} // namespace base
diff --git a/src/base/mac/scoped_mach_port.h b/src/base/mac/scoped_mach_port.h new file mode 100644 index 0000000..d2aa2f7 --- /dev/null +++ b/src/base/mac/scoped_mach_port.h
@@ -0,0 +1,42 @@ +// 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_MAC_SCOPED_MACH_PORT_H_ +#define BASE_MAC_SCOPED_MACH_PORT_H_ + +#include <mach/mach.h> + +#include "base/basictypes.h" +#include "base/base_export.h" + +namespace base { +namespace mac { + +// A class for managing the life of a Mach port, releasing via +// mach_port_deallocate either its send and/or receive rights. +class BASE_EXPORT ScopedMachPort { + public: + // Creates a scoper by taking ownership of the port. + explicit ScopedMachPort(mach_port_t port); + + ~ScopedMachPort(); + + operator mach_port_t() const { + return port_; + } + + mach_port_t get() const { + return port_; + } + + private: + mach_port_t port_; + + DISALLOW_COPY_AND_ASSIGN(ScopedMachPort); +}; + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_SCOPED_MACH_PORT_H_
diff --git a/src/base/mac/scoped_nsautorelease_pool.h b/src/base/mac/scoped_nsautorelease_pool.h new file mode 100644 index 0000000..60af71a --- /dev/null +++ b/src/base/mac/scoped_nsautorelease_pool.h
@@ -0,0 +1,45 @@ +// 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. + +#ifndef BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_ +#define BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" + +#if defined(__OBJC__) +@class NSAutoreleasePool; +#else // __OBJC__ +class NSAutoreleasePool; +#endif // __OBJC__ + +namespace base { +namespace mac { + +// ScopedNSAutoreleasePool allocates an NSAutoreleasePool when instantiated and +// sends it a -drain message when destroyed. This allows an autorelease pool to +// be maintained in ordinary C++ code without bringing in any direct Objective-C +// dependency. + +class BASE_EXPORT ScopedNSAutoreleasePool { + public: + ScopedNSAutoreleasePool(); + ~ScopedNSAutoreleasePool(); + + // Clear out the pool in case its position on the stack causes it to be + // alive for long periods of time (such as the entire length of the app). + // Only use then when you're certain the items currently in the pool are + // no longer needed. + void Recycle(); + private: + NSAutoreleasePool* autorelease_pool_; + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedNSAutoreleasePool); +}; + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_
diff --git a/src/base/mac/scoped_nsautorelease_pool.mm b/src/base/mac/scoped_nsautorelease_pool.mm new file mode 100644 index 0000000..e542ca8 --- /dev/null +++ b/src/base/mac/scoped_nsautorelease_pool.mm
@@ -0,0 +1,32 @@ +// Copyright (c) 2010 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/mac/scoped_nsautorelease_pool.h" + +#import <Foundation/Foundation.h> + +#include "base/logging.h" + +namespace base { +namespace mac { + +ScopedNSAutoreleasePool::ScopedNSAutoreleasePool() + : autorelease_pool_([[NSAutoreleasePool alloc] init]) { + DCHECK(autorelease_pool_); +} + +ScopedNSAutoreleasePool::~ScopedNSAutoreleasePool() { + [autorelease_pool_ drain]; +} + +// Cycle the internal pool, allowing everything there to get cleaned up and +// start anew. +void ScopedNSAutoreleasePool::Recycle() { + [autorelease_pool_ drain]; + autorelease_pool_ = [[NSAutoreleasePool alloc] init]; + DCHECK(autorelease_pool_); +} + +} // namespace mac +} // namespace base
diff --git a/src/base/mac/scoped_nsexception_enabler.h b/src/base/mac/scoped_nsexception_enabler.h new file mode 100644 index 0000000..e1d0b3c --- /dev/null +++ b/src/base/mac/scoped_nsexception_enabler.h
@@ -0,0 +1,53 @@ +// 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. + +#ifndef BASE_MAC_SCOPED_NSEXCEPTION_ENABLER_H_ +#define BASE_MAC_SCOPED_NSEXCEPTION_ENABLER_H_ + +#import <Foundation/Foundation.h> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace mac { + +// BrowserCrApplication attempts to restrict throwing of NSExceptions +// because they interact badly with C++ scoping rules. Unfortunately, +// there are some cases where exceptions must be supported, such as +// when third-party printer drivers are used. These helpers can be +// used to enable exceptions for narrow windows. + +// Make it easy to safely allow NSException to be thrown in a limited +// scope. Note that if an exception is thrown, then this object will +// not be appropriately destructed! If the exception ends up in the +// top-level event loop, things are cleared in -reportException:. If +// the exception is caught at a lower level, a higher level scoper +// should eventually reset things. +class BASE_EXPORT ScopedNSExceptionEnabler { + public: + ScopedNSExceptionEnabler(); + ~ScopedNSExceptionEnabler(); + + private: + bool was_enabled_; + + DISALLOW_COPY_AND_ASSIGN(ScopedNSExceptionEnabler); +}; + +// Access the exception setting for the current thread. This is for +// the support code in BrowserCrApplication, other code should use +// the scoper. +BASE_EXPORT bool GetNSExceptionsAllowed(); +BASE_EXPORT void SetNSExceptionsAllowed(bool allowed); + +// Executes [target performSelector:sel] with fatal-exceptions turned +// off, and returns the result. If an exception is thrown during the +// perform, nil is returned. +BASE_EXPORT id PerformSelectorIgnoringExceptions(NSObject* target, SEL sel); + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_SCOPED_NSEXCEPTION_ENABLER_H_
diff --git a/src/base/mac/scoped_nsexception_enabler.mm b/src/base/mac/scoped_nsexception_enabler.mm new file mode 100644 index 0000000..9898789 --- /dev/null +++ b/src/base/mac/scoped_nsexception_enabler.mm
@@ -0,0 +1,63 @@ +// 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. + +#import "base/mac/scoped_nsexception_enabler.h" + +#import "base/lazy_instance.h" +#import "base/threading/thread_local.h" + +// To make the |g_exceptionsAllowed| declaration readable. +using base::LazyInstance; +using base::ThreadLocalBoolean; + +// When C++ exceptions are disabled, the C++ library defines |try| and +// |catch| so as to allow exception-expecting C++ code to build properly when +// language support for exceptions is not present. These macros interfere +// with the use of |@try| and |@catch| in Objective-C files such as this one. +// Undefine these macros here, after everything has been #included, since +// there will be no C++ uses and only Objective-C uses from this point on. +#undef try +#undef catch + +namespace { + +// Whether to allow NSExceptions to be raised on the current thread. +LazyInstance<ThreadLocalBoolean>::Leaky + g_exceptionsAllowed = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace base { +namespace mac { + +bool GetNSExceptionsAllowed() { + return g_exceptionsAllowed.Get().Get(); +} + +void SetNSExceptionsAllowed(bool allowed) { + return g_exceptionsAllowed.Get().Set(allowed); +} + +id PerformSelectorIgnoringExceptions(NSObject* target, SEL sel) { + id ret = nil; + @try { + base::mac::ScopedNSExceptionEnabler enable; + ret = [target performSelector:sel]; + } + @catch(id exception) { + } + return ret; +} + +ScopedNSExceptionEnabler::ScopedNSExceptionEnabler() { + was_enabled_ = GetNSExceptionsAllowed(); + SetNSExceptionsAllowed(true); +} + +ScopedNSExceptionEnabler::~ScopedNSExceptionEnabler() { + SetNSExceptionsAllowed(was_enabled_); +} + +} // namespace mac +} // namespace base
diff --git a/src/base/mac/scoped_sending_event.h b/src/base/mac/scoped_sending_event.h new file mode 100644 index 0000000..f7637bb --- /dev/null +++ b/src/base/mac/scoped_sending_event.h
@@ -0,0 +1,49 @@ +// 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_MAC_SCOPED_SENDING_EVENT_H_ +#define BASE_MAC_SCOPED_SENDING_EVENT_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/memory/scoped_nsobject.h" +#include "base/message_pump_mac.h" + +// Nested event loops can pump IPC messages, including +// script-initiated tab closes, which could release objects that the +// nested event loop might message. CrAppProtocol defines how to ask +// the embedding NSApplication subclass if an event is currently being +// handled, in which case such closes are deferred to the top-level +// event loop. +// +// ScopedSendingEvent allows script-initiated event loops to work like +// a nested event loop, as such events do not arrive via -sendEvent:. +// CrAppControlProtocol lets ScopedSendingEvent tell the embedding +// NSApplication what to return from -handlingSendEvent. + +@protocol CrAppControlProtocol<CrAppProtocol> +- (void)setHandlingSendEvent:(BOOL)handlingSendEvent; +@end + +namespace base { +namespace mac { + +class BASE_EXPORT ScopedSendingEvent { + public: + ScopedSendingEvent(); + ~ScopedSendingEvent(); + + private: + // The NSApp in control at the time the constructor was run, to be + // sure the |handling_| setting is restored appropriately. + NSObject<CrAppControlProtocol>* app_; + BOOL handling_; // Value of -[app_ handlingSendEvent] at construction. + + DISALLOW_COPY_AND_ASSIGN(ScopedSendingEvent); +}; + +} // namespace mac +} // namespace base + +#endif // BASE_MAC_SCOPED_SENDING_EVENT_H_
diff --git a/src/base/mac/scoped_sending_event.mm b/src/base/mac/scoped_sending_event.mm new file mode 100644 index 0000000..c3813d8 --- /dev/null +++ b/src/base/mac/scoped_sending_event.mm
@@ -0,0 +1,24 @@ +// 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. + +#import "base/mac/scoped_sending_event.h" + +#include "base/logging.h" + +namespace base { +namespace mac { + +ScopedSendingEvent::ScopedSendingEvent() + : app_(static_cast<NSObject<CrAppControlProtocol>*>(NSApp)) { + DCHECK([app_ conformsToProtocol:@protocol(CrAppControlProtocol)]); + handling_ = [app_ isHandlingSendEvent]; + [app_ setHandlingSendEvent:YES]; +} + +ScopedSendingEvent::~ScopedSendingEvent() { + [app_ setHandlingSendEvent:handling_]; +} + +} // namespace mac +} // namespace base
diff --git a/src/base/mac/scoped_sending_event_unittest.mm b/src/base/mac/scoped_sending_event_unittest.mm new file mode 100644 index 0000000..9ae9985 --- /dev/null +++ b/src/base/mac/scoped_sending_event_unittest.mm
@@ -0,0 +1,38 @@ +// 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. + +#import "base/mac/scoped_sending_event.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Sets the flag within scope, resets when leaving scope. +TEST(ScopedSendingEventTest, SetHandlingSendEvent) { + id<CrAppProtocol> app = NSApp; + EXPECT_FALSE([app isHandlingSendEvent]); + { + base::mac::ScopedSendingEvent is_handling_send_event; + EXPECT_TRUE([app isHandlingSendEvent]); + } + EXPECT_FALSE([app isHandlingSendEvent]); +} + +// Nested call restores previous value rather than resetting flag. +TEST(ScopedSendingEventTest, NestedSetHandlingSendEvent) { + id<CrAppProtocol> app = NSApp; + EXPECT_FALSE([app isHandlingSendEvent]); + { + base::mac::ScopedSendingEvent is_handling_send_event; + EXPECT_TRUE([app isHandlingSendEvent]); + { + base::mac::ScopedSendingEvent nested_is_handling_send_event; + EXPECT_TRUE([app isHandlingSendEvent]); + } + EXPECT_TRUE([app isHandlingSendEvent]); + } + EXPECT_FALSE([app isHandlingSendEvent]); +} + +} // namespace
diff --git a/src/base/mach_ipc_mac.h b/src/base/mach_ipc_mac.h new file mode 100644 index 0000000..1730d37 --- /dev/null +++ b/src/base/mach_ipc_mac.h
@@ -0,0 +1,326 @@ +// 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_MACH_IPC_MAC_H_ +#define BASE_MACH_IPC_MAC_H_ + +#include <mach/mach.h> +#include <mach/message.h> +#include <servers/bootstrap.h> +#include <sys/types.h> + +#include <CoreServices/CoreServices.h> + +#include "base/base_export.h" +#include "base/basictypes.h" + +//============================================================================== +// DISCUSSION: +// +// The three main classes of interest are +// +// MachMessage: a wrapper for a Mach message of the following form +// mach_msg_header_t +// mach_msg_body_t +// optional descriptors +// optional extra message data +// +// MachReceiveMessage and MachSendMessage subclass MachMessage +// and are used instead of MachMessage which is an abstract base class +// +// ReceivePort: +// Represents a Mach port for which we have receive rights +// +// MachPortSender: +// Represents a Mach port for which we have send rights +// +// Here's an example to receive a message on a server port: +// +// // This creates our named server port +// ReceivePort receivePort("com.Google.MyService"); +// +// MachReceiveMessage message; +// kern_return_t result = receivePort.WaitForMessage(&message, 0); +// +// if (result == KERN_SUCCESS && message.GetMessageID() == 57) { +// mach_port_t task = message.GetTranslatedPort(0); +// mach_port_t thread = message.GetTranslatedPort(1); +// +// char *messageString = message.GetData(); +// +// printf("message string = %s\n", messageString); +// } +// +// Here is an example of using these classes to send a message to this port: +// +// // send to already named port +// MachPortSender sender("com.Google.MyService"); +// MachSendMessage message(57); // our message ID is 57 +// +// // add some ports to be translated for us +// message.AddDescriptor(mach_task_self()); // our task +// message.AddDescriptor(mach_thread_self()); // this thread +// +// char messageString[] = "Hello server!\n"; +// message.SetData(messageString, strlen(messageString)+1); +// // timeout 1000ms +// kern_return_t result = sender.SendMessage(message, 1000); +// + +#define PRINT_MACH_RESULT(result_, message_) \ + printf(message_" %s (%d)\n", mach_error_string(result_), result_ ); + +namespace base { + +//============================================================================== +// A wrapper class for mach_msg_port_descriptor_t (with same memory layout) +// with convenient constructors and accessors +class MachMsgPortDescriptor : public mach_msg_port_descriptor_t { + public: + // General-purpose constructor + MachMsgPortDescriptor(mach_port_t in_name, + mach_msg_type_name_t in_disposition) { + name = in_name; + pad1 = 0; + pad2 = 0; + disposition = in_disposition; + type = MACH_MSG_PORT_DESCRIPTOR; + } + + // For passing send rights to a port + MachMsgPortDescriptor(mach_port_t in_name) { + name = in_name; + pad1 = 0; + pad2 = 0; + disposition = MACH_MSG_TYPE_PORT_SEND; + type = MACH_MSG_PORT_DESCRIPTOR; + } + + // Copy constructor + MachMsgPortDescriptor(const MachMsgPortDescriptor& desc) { + name = desc.name; + pad1 = desc.pad1; + pad2 = desc.pad2; + disposition = desc.disposition; + type = desc.type; + } + + mach_port_t GetMachPort() const { + return name; + } + + mach_msg_type_name_t GetDisposition() const { + return disposition; + } + + // For convenience + operator mach_port_t() const { + return GetMachPort(); + } +}; + +//============================================================================== +// MachMessage: a wrapper for a Mach message +// (mach_msg_header_t, mach_msg_body_t, extra data) +// +// This considerably simplifies the construction of a message for sending +// and the getting at relevant data and descriptors for the receiver. +// +// This class can be initialized using external storage of an arbitrary size +// or it can manage storage internally. +// 1. If storage is allocated internally, the combined size of the descriptors +// plus data must be less than 1024. But as a benefit no memory allocation is +// necessary. +// 2. For external storage, a buffer of at least EmptyMessageSize() must be +// provided. +// +// A MachMessage object is used by ReceivePort::WaitForMessage +// and MachPortSender::SendMessage +// +class BASE_EXPORT MachMessage { + public: + static const size_t kEmptyMessageSize; + + virtual ~MachMessage(); + + // The receiver of the message can retrieve the raw data this way + u_int8_t *GetData() { + return GetDataLength() > 0 ? GetDataPacket()->data : NULL; + } + + u_int32_t GetDataLength() { + return EndianU32_LtoN(GetDataPacket()->data_length); + } + + // The message ID may be used as a code identifying the type of message + void SetMessageID(int32_t message_id) { + GetDataPacket()->id = EndianU32_NtoL(message_id); + } + + int32_t GetMessageID() { return EndianU32_LtoN(GetDataPacket()->id); } + + // Adds a descriptor (typically a Mach port) to be translated + // returns true if successful, otherwise not enough space + bool AddDescriptor(const MachMsgPortDescriptor &desc); + + int GetDescriptorCount() const { + return storage_->body.msgh_descriptor_count; + } + + MachMsgPortDescriptor *GetDescriptor(int n); + + // Convenience method which gets the Mach port described by the descriptor + mach_port_t GetTranslatedPort(int n); + + // A simple message is one with no descriptors + bool IsSimpleMessage() const { return GetDescriptorCount() == 0; } + + // Sets raw data for the message (returns false if not enough space) + bool SetData(const void* data, int32_t data_length); + + protected: + // Consider this an abstract base class - must create an actual instance + // of MachReceiveMessage or MachSendMessage + MachMessage(); + + // Constructor for use with preallocate storage. + // storage_length must be >= EmptyMessageSize() + MachMessage(void *storage, size_t storage_length); + + friend class ReceivePort; + friend class MachPortSender; + + // Represents raw data in our message + struct MessageDataPacket { + int32_t id; // little-endian + int32_t data_length; // little-endian + u_int8_t data[1]; // actual size limited by storage_length_bytes_ + }; + + MessageDataPacket* GetDataPacket(); + + void SetDescriptorCount(int n); + void SetDescriptor(int n, const MachMsgPortDescriptor &desc); + + // Returns total message size setting msgh_size in the header to this value + int CalculateSize(); + + // Returns total storage size that this object can grow to, this is inclusive + // of the Mach header. + size_t MaxSize() const { return storage_length_bytes_; } + + mach_msg_header_t *Head() { return &(storage_->head); } + + private: + struct MachMessageData { + mach_msg_header_t head; + mach_msg_body_t body; + // descriptors and data may be embedded here. + u_int8_t padding[1024]; + }; + + MachMessageData *storage_; + size_t storage_length_bytes_; + bool own_storage_; // Is storage owned by this object? +}; + +//============================================================================== +// MachReceiveMessage and MachSendMessage are useful to separate the idea +// of a Mach message being sent and being received, and adds increased type +// safety: +// ReceivePort::WaitForMessage() only accepts a MachReceiveMessage +// MachPortSender::SendMessage() only accepts a MachSendMessage + +//============================================================================== +class MachReceiveMessage : public MachMessage { + public: + MachReceiveMessage() : MachMessage() {} + MachReceiveMessage(void *storage, size_t storage_length) + : MachMessage(storage, storage_length) {} + + private: + DISALLOW_COPY_AND_ASSIGN(MachReceiveMessage); +}; + +//============================================================================== +class BASE_EXPORT MachSendMessage : public MachMessage { + public: + explicit MachSendMessage(int32_t message_id); + MachSendMessage(void *storage, size_t storage_length, int32_t message_id); + + private: + void Initialize(int32_t message_id); + + DISALLOW_COPY_AND_ASSIGN(MachSendMessage); +}; + +//============================================================================== +// Represents a Mach port for which we have receive rights +class BASE_EXPORT ReceivePort { + public: + // Creates a new Mach port for receiving messages and registers a name for it + explicit ReceivePort(const char *receive_port_name); + + // Given an already existing Mach port, use it. We take ownership of the + // port and deallocate it in our destructor. + explicit ReceivePort(mach_port_t receive_port); + + // Create a new Mach port for receiving messages + ReceivePort(); + + ~ReceivePort(); + + // Waits on the Mach port until message received or timeout. If |timeout| is + // MACH_MSG_TIMEOUT_NONE, this method waits forever. + kern_return_t WaitForMessage(MachReceiveMessage *out_message, + mach_msg_timeout_t timeout); + + // The underlying Mach port that we wrap + mach_port_t GetPort() const { return port_; } + + private: + mach_port_t port_; + kern_return_t init_result_; + + DISALLOW_COPY_AND_ASSIGN(ReceivePort); +}; + +//============================================================================== +// Represents a Mach port for which we have send rights +class BASE_EXPORT MachPortSender { + public: + // get a port with send rights corresponding to a named registered service + explicit MachPortSender(const char *receive_port_name); + + + // Given an already existing Mach port, use it. Does not take ownership of + // |send_port|. + explicit MachPortSender(mach_port_t send_port); + + kern_return_t SendMessage(MachSendMessage &message, + mach_msg_timeout_t timeout); + + private: + mach_port_t send_port_; + kern_return_t init_result_; + + DISALLOW_COPY_AND_ASSIGN(MachPortSender); +}; + +//============================================================================== +// Static utility functions. + +namespace mac { + +// Returns the number of Mach ports to which the given task has a right. +// Note that unless the calling task has send rights to the passed task port, +// this will fail unless the calling task is running as root. +kern_return_t BASE_EXPORT GetNumberOfMachPorts(mach_port_t task_port, + int* port_count); + +} // namespace mac + +} // namespace base + +#endif // BASE_MACH_IPC_MAC_H_
diff --git a/src/base/mach_ipc_mac.mm b/src/base/mach_ipc_mac.mm new file mode 100644 index 0000000..3d45dd7 --- /dev/null +++ b/src/base/mach_ipc_mac.mm
@@ -0,0 +1,358 @@ +// 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/mach_ipc_mac.h" + +#import <Foundation/Foundation.h> +#include <mach/vm_map.h> + +#include <stdio.h> +#include "base/logging.h" + +namespace base { + +// static +const size_t MachMessage::kEmptyMessageSize = sizeof(mach_msg_header_t) + + sizeof(mach_msg_body_t) + sizeof(MessageDataPacket); + +//============================================================================== +MachSendMessage::MachSendMessage(int32_t message_id) : MachMessage() { + Initialize(message_id); +} + +MachSendMessage::MachSendMessage(void *storage, size_t storage_length, + int32_t message_id) + : MachMessage(storage, storage_length) { + Initialize(message_id); +} + +void MachSendMessage::Initialize(int32_t message_id) { + Head()->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); + + // head.msgh_remote_port = ...; // filled out in MachPortSender::SendMessage() + Head()->msgh_local_port = MACH_PORT_NULL; + Head()->msgh_reserved = 0; + Head()->msgh_id = 0; + + SetDescriptorCount(0); // start out with no descriptors + + SetMessageID(message_id); + SetData(NULL, 0); // client may add data later +} + +//============================================================================== +MachMessage::MachMessage() + : storage_(new MachMessageData), // Allocate storage_ ourselves + storage_length_bytes_(sizeof(MachMessageData)), + own_storage_(true) { + memset(storage_, 0, storage_length_bytes_); +} + +//============================================================================== +MachMessage::MachMessage(void *storage, size_t storage_length) + : storage_(static_cast<MachMessageData*>(storage)), + storage_length_bytes_(storage_length), + own_storage_(false) { + DCHECK(storage); + DCHECK_GE(storage_length, kEmptyMessageSize); +} + +//============================================================================== +MachMessage::~MachMessage() { + if (own_storage_) { + delete storage_; + storage_ = NULL; + } +} + +//============================================================================== +// returns true if successful +bool MachMessage::SetData(const void* data, + int32_t data_length) { + // Enforce the fact that it's only safe to call this method once on a + // message. + DCHECK(GetDataPacket()->data_length == 0); + + // first check to make sure we have enough space + int size = CalculateSize(); + int new_size = size + data_length; + + if ((unsigned)new_size > storage_length_bytes_) { + return false; // not enough space + } + + GetDataPacket()->data_length = EndianU32_NtoL(data_length); + if (data) memcpy(GetDataPacket()->data, data, data_length); + + // Update the Mach header with the new aligned size of the message. + CalculateSize(); + + return true; +} + +//============================================================================== +// calculates and returns the total size of the message +// Currently, the entire message MUST fit inside of the MachMessage +// messsage size <= EmptyMessageSize() +int MachMessage::CalculateSize() { + int size = sizeof(mach_msg_header_t) + sizeof(mach_msg_body_t); + + // add space for MessageDataPacket + int32_t alignedDataLength = (GetDataLength() + 3) & ~0x3; + size += 2*sizeof(int32_t) + alignedDataLength; + + // add space for descriptors + size += GetDescriptorCount() * sizeof(MachMsgPortDescriptor); + + Head()->msgh_size = size; + + return size; +} + +//============================================================================== +MachMessage::MessageDataPacket *MachMessage::GetDataPacket() { + int desc_size = sizeof(MachMsgPortDescriptor)*GetDescriptorCount(); + MessageDataPacket *packet = + reinterpret_cast<MessageDataPacket*>(storage_->padding + desc_size); + + return packet; +} + +//============================================================================== +void MachMessage::SetDescriptor(int n, + const MachMsgPortDescriptor &desc) { + MachMsgPortDescriptor *desc_array = + reinterpret_cast<MachMsgPortDescriptor*>(storage_->padding); + desc_array[n] = desc; +} + +//============================================================================== +// returns true if successful otherwise there was not enough space +bool MachMessage::AddDescriptor(const MachMsgPortDescriptor &desc) { + // first check to make sure we have enough space + int size = CalculateSize(); + int new_size = size + sizeof(MachMsgPortDescriptor); + + if ((unsigned)new_size > storage_length_bytes_) { + return false; // not enough space + } + + // unfortunately, we need to move the data to allow space for the + // new descriptor + u_int8_t *p = reinterpret_cast<u_int8_t*>(GetDataPacket()); + bcopy(p, p+sizeof(MachMsgPortDescriptor), GetDataLength()+2*sizeof(int32_t)); + + SetDescriptor(GetDescriptorCount(), desc); + SetDescriptorCount(GetDescriptorCount() + 1); + + CalculateSize(); + + return true; +} + +//============================================================================== +void MachMessage::SetDescriptorCount(int n) { + storage_->body.msgh_descriptor_count = n; + + if (n > 0) { + Head()->msgh_bits |= MACH_MSGH_BITS_COMPLEX; + } else { + Head()->msgh_bits &= ~MACH_MSGH_BITS_COMPLEX; + } +} + +//============================================================================== +MachMsgPortDescriptor *MachMessage::GetDescriptor(int n) { + if (n < GetDescriptorCount()) { + MachMsgPortDescriptor *desc = + reinterpret_cast<MachMsgPortDescriptor*>(storage_->padding); + return desc + n; + } + + return nil; +} + +//============================================================================== +mach_port_t MachMessage::GetTranslatedPort(int n) { + if (n < GetDescriptorCount()) { + return GetDescriptor(n)->GetMachPort(); + } + return MACH_PORT_NULL; +} + +#pragma mark - + +//============================================================================== +// create a new mach port for receiving messages and register a name for it +ReceivePort::ReceivePort(const char *receive_port_name) { + mach_port_t current_task = mach_task_self(); + + init_result_ = mach_port_allocate(current_task, + MACH_PORT_RIGHT_RECEIVE, + &port_); + + if (init_result_ != KERN_SUCCESS) + return; + + init_result_ = mach_port_insert_right(current_task, + port_, + port_, + MACH_MSG_TYPE_MAKE_SEND); + + if (init_result_ != KERN_SUCCESS) + return; + + // Without |NSMachPortDeallocateNone|, the NSMachPort seems to deallocate + // receive rights on port when it is eventually released. It is not necessary + // to deallocate any rights here as |port_| is fully deallocated in the + // ReceivePort destructor. + NSPort *ns_port = [NSMachPort portWithMachPort:port_ + options:NSMachPortDeallocateNone]; + NSString *port_name = [NSString stringWithUTF8String:receive_port_name]; + [[NSMachBootstrapServer sharedInstance] registerPort:ns_port name:port_name]; +} + +//============================================================================== +// create a new mach port for receiving messages +ReceivePort::ReceivePort() { + mach_port_t current_task = mach_task_self(); + + init_result_ = mach_port_allocate(current_task, + MACH_PORT_RIGHT_RECEIVE, + &port_); + + if (init_result_ != KERN_SUCCESS) + return; + + init_result_ = mach_port_insert_right(current_task, + port_, + port_, + MACH_MSG_TYPE_MAKE_SEND); +} + +//============================================================================== +// Given an already existing mach port, use it. We take ownership of the +// port and deallocate it in our destructor. +ReceivePort::ReceivePort(mach_port_t receive_port) + : port_(receive_port), + init_result_(KERN_SUCCESS) { +} + +//============================================================================== +ReceivePort::~ReceivePort() { + if (init_result_ == KERN_SUCCESS) + mach_port_deallocate(mach_task_self(), port_); +} + +//============================================================================== +kern_return_t ReceivePort::WaitForMessage(MachReceiveMessage *out_message, + mach_msg_timeout_t timeout) { + if (!out_message) { + return KERN_INVALID_ARGUMENT; + } + + // return any error condition encountered in constructor + if (init_result_ != KERN_SUCCESS) + return init_result_; + + out_message->Head()->msgh_bits = 0; + out_message->Head()->msgh_local_port = port_; + out_message->Head()->msgh_remote_port = MACH_PORT_NULL; + out_message->Head()->msgh_reserved = 0; + out_message->Head()->msgh_id = 0; + + mach_msg_option_t rcv_options = MACH_RCV_MSG; + if (timeout != MACH_MSG_TIMEOUT_NONE) + rcv_options |= MACH_RCV_TIMEOUT; + + kern_return_t result = mach_msg(out_message->Head(), + rcv_options, + 0, + out_message->MaxSize(), + port_, + timeout, // timeout in ms + MACH_PORT_NULL); + + return result; +} + +#pragma mark - + +//============================================================================== +// get a port with send rights corresponding to a named registered service +MachPortSender::MachPortSender(const char *receive_port_name) { + mach_port_t bootstrap_port = 0; + init_result_ = task_get_bootstrap_port(mach_task_self(), &bootstrap_port); + + if (init_result_ != KERN_SUCCESS) + return; + + init_result_ = bootstrap_look_up(bootstrap_port, + const_cast<char*>(receive_port_name), + &send_port_); +} + +//============================================================================== +MachPortSender::MachPortSender(mach_port_t send_port) + : send_port_(send_port), + init_result_(KERN_SUCCESS) { +} + +//============================================================================== +kern_return_t MachPortSender::SendMessage(MachSendMessage &message, + mach_msg_timeout_t timeout) { + if (message.Head()->msgh_size == 0) { + NOTREACHED(); + return KERN_INVALID_VALUE; // just for safety -- never should occur + }; + + if (init_result_ != KERN_SUCCESS) + return init_result_; + + message.Head()->msgh_remote_port = send_port_; + + kern_return_t result = mach_msg(message.Head(), + MACH_SEND_MSG | MACH_SEND_TIMEOUT, + message.Head()->msgh_size, + 0, + MACH_PORT_NULL, + timeout, // timeout in ms + MACH_PORT_NULL); + + return result; +} + +//============================================================================== + +namespace mac { + +kern_return_t GetNumberOfMachPorts(mach_port_t task_port, int* num_ports) { + mach_port_name_array_t names; + mach_msg_type_number_t names_count; + mach_port_type_array_t types; + mach_msg_type_number_t types_count; + + // A friendlier interface would allow NULL buffers to only get the counts. + kern_return_t kr = mach_port_names(task_port, &names, &names_count, + &types, &types_count); + if (kr != KERN_SUCCESS) + return kr; + + // The documentation states this is an invariant. + DCHECK_EQ(names_count, types_count); + *num_ports = names_count; + + kr = vm_deallocate(mach_task_self(), + reinterpret_cast<vm_address_t>(names), + names_count * sizeof(mach_port_name_array_t)); + kr = vm_deallocate(mach_task_self(), + reinterpret_cast<vm_address_t>(types), + types_count * sizeof(mach_port_type_array_t)); + + return kr; +} + +} // namespace mac + +} // namespace base
diff --git a/src/base/md5.cc b/src/base/md5.cc new file mode 100644 index 0000000..754994c --- /dev/null +++ b/src/base/md5.cc
@@ -0,0 +1,292 @@ +// 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. + +// The original file was copied from sqlite, and was in the public domain. + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#include "base/md5.h" + +#include "base/basictypes.h" + +namespace { + +struct Context { + uint32 buf[4]; + uint32 bits[2]; + unsigned char in[64]; +}; + +/* + * Note: this code is harmless on little-endian machines. + */ +void byteReverse(unsigned char *buf, unsigned longs) { + uint32 t; + do { + t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 | + ((unsigned)buf[1]<<8 | buf[0]); + *(uint32 *)buf = t; + buf += 4; + } while (--longs); +} + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void MD5Transform(uint32 buf[4], const uint32 in[16]) { + register uint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +} // namespace + +namespace base { + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void MD5Init(MD5Context* context) { + struct Context *ctx = (struct Context *)context; + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void MD5Update(MD5Context* context, const StringPiece& data) { + const unsigned char* inbuf = (const unsigned char*)data.data(); + size_t len = data.size(); + struct Context *ctx = (struct Context *)context; + const unsigned char* buf = (const unsigned char*)inbuf; + uint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32)len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += static_cast<uint32>(len >> 29); + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *)ctx->in + t; + + t = 64-t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += t; + len -= t; + } + + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void MD5Final(MD5Digest* digest, MD5Context* context) { + struct Context *ctx = (struct Context *)context; + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count-8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32 *)ctx->in)[ 14 ] = ctx->bits[0]; + ((uint32 *)ctx->in)[ 15 ] = ctx->bits[1]; + + MD5Transform(ctx->buf, (uint32 *)ctx->in); + byteReverse((unsigned char *)ctx->buf, 4); + memcpy(digest->a, ctx->buf, 16); + memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ +} + +std::string MD5DigestToBase16(const MD5Digest& digest) { + static char const zEncode[] = "0123456789abcdef"; + + std::string ret; + ret.resize(32); + + int j = 0; + for (int i = 0; i < 16; i ++) { + int a = digest.a[i]; + ret[j++] = zEncode[(a>>4)&0xf]; + ret[j++] = zEncode[a & 0xf]; + } + return ret; +} + +void MD5Sum(const void* data, size_t length, MD5Digest* digest) { + MD5Context ctx; + MD5Init(&ctx); + MD5Update(&ctx, + StringPiece(reinterpret_cast<const char*>(data), length)); + MD5Final(digest, &ctx); +} + +std::string MD5String(const StringPiece& str) { + MD5Digest digest; + MD5Sum(str.data(), str.length(), &digest); + return MD5DigestToBase16(digest); +} + +} // namespace base
diff --git a/src/base/md5.h b/src/base/md5.h new file mode 100644 index 0000000..4a29a2d --- /dev/null +++ b/src/base/md5.h
@@ -0,0 +1,69 @@ +// 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. + +#ifndef BASE_MD5_H_ +#define BASE_MD5_H_ + +#include "base/base_export.h" +#include "base/string_piece.h" + +namespace base { + +// MD5 stands for Message Digest algorithm 5. +// MD5 is a robust hash function, designed for cyptography, but often used +// for file checksums. The code is complex and slow, but has few +// collisions. +// See Also: +// http://en.wikipedia.org/wiki/MD5 + +// These functions perform MD5 operations. The simplest call is MD5Sum() to +// generate the MD5 sum of the given data. +// +// You can also compute the MD5 sum of data incrementally by making multiple +// calls to MD5Update(): +// MD5Context ctx; // intermediate MD5 data: do not use +// MD5Init(&ctx); +// MD5Update(&ctx, data1, length1); +// MD5Update(&ctx, data2, length2); +// ... +// +// MD5Digest digest; // the result of the computation +// MD5Final(&digest, &ctx); +// +// You can call MD5DigestToBase16() to generate a string of the digest. + +// The output of an MD5 operation. +struct MD5Digest { + unsigned char a[16]; +}; + +// Used for storing intermediate data during an MD5 computation. Callers +// should not access the data. +typedef char MD5Context[88]; + +// Computes the MD5 sum of the given data buffer with the given length. +// The given 'digest' structure will be filled with the result data. +BASE_EXPORT void MD5Sum(const void* data, size_t length, MD5Digest* digest); + +// Initializes the given MD5 context structure for subsequent calls to +// MD5Update(). +BASE_EXPORT void MD5Init(MD5Context* context); + +// For the given buffer of |data| as a StringPiece, updates the given MD5 +// context with the sum of the data. You can call this any number of times +// during the computation, except that MD5Init() must have been called first. +BASE_EXPORT void MD5Update(MD5Context* context, const StringPiece& data); + +// Finalizes the MD5 operation and fills the buffer with the digest. +BASE_EXPORT void MD5Final(MD5Digest* digest, MD5Context* context); + +// Converts a digest into human-readable hexadecimal. +BASE_EXPORT std::string MD5DigestToBase16(const MD5Digest& digest); + +// Returns the MD5 (in hexadecimal) of a string. +BASE_EXPORT std::string MD5String(const StringPiece& str); + +} // namespace base + +#endif // BASE_MD5_H_
diff --git a/src/base/md5_unittest.cc b/src/base/md5_unittest.cc new file mode 100644 index 0000000..a20d819 --- /dev/null +++ b/src/base/md5_unittest.cc
@@ -0,0 +1,207 @@ +// 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 <string.h> +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/md5.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(MD5, DigestToBase16) { + MD5Digest digest; + + int data[] = { + 0xd4, 0x1d, 0x8c, 0xd9, + 0x8f, 0x00, 0xb2, 0x04, + 0xe9, 0x80, 0x09, 0x98, + 0xec, 0xf8, 0x42, 0x7e + }; + + for (int i = 0; i < 16; ++i) + digest.a[i] = data[i] & 0xff; + + std::string actual = MD5DigestToBase16(digest); + std::string expected = "d41d8cd98f00b204e9800998ecf8427e"; + + EXPECT_EQ(expected, actual); +} + +TEST(MD5, MD5SumEmtpyData) { + MD5Digest digest; + const char* data = ""; + + MD5Sum(data, strlen(data), &digest); + + int expected[] = { + 0xd4, 0x1d, 0x8c, 0xd9, + 0x8f, 0x00, 0xb2, 0x04, + 0xe9, 0x80, 0x09, 0x98, + 0xec, 0xf8, 0x42, 0x7e + }; + + for (int i = 0; i < 16; ++i) + EXPECT_EQ(expected[i], digest.a[i] & 0xFF); +} + +TEST(MD5, MD5SumOneByteData) { + MD5Digest digest; + const char* data = "a"; + + MD5Sum(data, strlen(data), &digest); + + int expected[] = { + 0x0c, 0xc1, 0x75, 0xb9, + 0xc0, 0xf1, 0xb6, 0xa8, + 0x31, 0xc3, 0x99, 0xe2, + 0x69, 0x77, 0x26, 0x61 + }; + + for (int i = 0; i < 16; ++i) + EXPECT_EQ(expected[i], digest.a[i] & 0xFF); +} + +TEST(MD5, MD5SumLongData) { + const int length = 10 * 1024 * 1024 + 1; + scoped_array<char> data(new char[length]); + + for (int i = 0; i < length; ++i) + data[i] = i & 0xFF; + + MD5Digest digest; + MD5Sum(data.get(), length, &digest); + + int expected[] = { + 0x90, 0xbd, 0x6a, 0xd9, + 0x0a, 0xce, 0xf5, 0xad, + 0xaa, 0x92, 0x20, 0x3e, + 0x21, 0xc7, 0xa1, 0x3e + }; + + for (int i = 0; i < 16; ++i) + EXPECT_EQ(expected[i], digest.a[i] & 0xFF); +} + +TEST(MD5, ContextWithEmptyData) { + MD5Context ctx; + MD5Init(&ctx); + + MD5Digest digest; + MD5Final(&digest, &ctx); + + int expected[] = { + 0xd4, 0x1d, 0x8c, 0xd9, + 0x8f, 0x00, 0xb2, 0x04, + 0xe9, 0x80, 0x09, 0x98, + 0xec, 0xf8, 0x42, 0x7e + }; + + for (int i = 0; i < 16; ++i) + EXPECT_EQ(expected[i], digest.a[i] & 0xFF); +} + +TEST(MD5, ContextWithLongData) { + MD5Context ctx; + MD5Init(&ctx); + + const int length = 10 * 1024 * 1024 + 1; + scoped_array<char> data(new char[length]); + + for (int i = 0; i < length; ++i) + data[i] = i & 0xFF; + + int total = 0; + while (total < length) { + int len = 4097; // intentionally not 2^k. + if (len > length - total) + len = length - total; + + MD5Update(&ctx, + StringPiece(reinterpret_cast<char*>(data.get() + total), len)); + total += len; + } + + EXPECT_EQ(length, total); + + MD5Digest digest; + MD5Final(&digest, &ctx); + + int expected[] = { + 0x90, 0xbd, 0x6a, 0xd9, + 0x0a, 0xce, 0xf5, 0xad, + 0xaa, 0x92, 0x20, 0x3e, + 0x21, 0xc7, 0xa1, 0x3e + }; + + for (int i = 0; i < 16; ++i) + EXPECT_EQ(expected[i], digest.a[i] & 0xFF); +} + +// Example data from http://www.ietf.org/rfc/rfc1321.txt A.5 Test Suite +TEST(MD5, MD5StringTestSuite1) { + std::string actual = MD5String(""); + std::string expected = "d41d8cd98f00b204e9800998ecf8427e"; + EXPECT_EQ(expected, actual); +} + +TEST(MD5, MD5StringTestSuite2) { + std::string actual = MD5String("a"); + std::string expected = "0cc175b9c0f1b6a831c399e269772661"; + EXPECT_EQ(expected, actual); +} + +TEST(MD5, MD5StringTestSuite3) { + std::string actual = MD5String("abc"); + std::string expected = "900150983cd24fb0d6963f7d28e17f72"; + EXPECT_EQ(expected, actual); +} + +TEST(MD5, MD5StringTestSuite4) { + std::string actual = MD5String("message digest"); + std::string expected = "f96b697d7cb7938d525a2f31aaf161d0"; + EXPECT_EQ(expected, actual); +} + +TEST(MD5, MD5StringTestSuite5) { + std::string actual = MD5String("abcdefghijklmnopqrstuvwxyz"); + std::string expected = "c3fcd3d76192e4007dfb496cca67e13b"; + EXPECT_EQ(expected, actual); +} + +TEST(MD5, MD5StringTestSuite6) { + std::string actual = MD5String("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789"); + std::string expected = "d174ab98d277d9f5a5611c2c9f419d9f"; + EXPECT_EQ(expected, actual); +} + +TEST(MD5, MD5StringTestSuite7) { + std::string actual = MD5String("12345678901234567890" + "12345678901234567890" + "12345678901234567890" + "12345678901234567890"); + std::string expected = "57edf4a22be3c955ac49da2e2107b67a"; + EXPECT_EQ(expected, actual); +} + +TEST(MD5, ContextWithStringData) { + MD5Context ctx; + MD5Init(&ctx); + + MD5Update(&ctx, "abc"); + + MD5Digest digest; + MD5Final(&digest, &ctx); + + std::string actual = MD5DigestToBase16(digest); + std::string expected = "900150983cd24fb0d6963f7d28e17f72"; + + EXPECT_EQ(expected, actual); +} + +} // namespace base
diff --git a/src/base/memory/aligned_memory.cc b/src/base/memory/aligned_memory.cc new file mode 100644 index 0000000..6b0ed98 --- /dev/null +++ b/src/base/memory/aligned_memory.cc
@@ -0,0 +1,49 @@ +// 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/memory/aligned_memory.h" + +#include "base/logging.h" + +#if defined(OS_ANDROID) || defined(OS_NACL) || defined(__LB_SHELL__) +#include <malloc.h> +#endif + +namespace base { + +void* AlignedAlloc(size_t size, size_t alignment) { + DCHECK_GT(size, 0U); + DCHECK_EQ(alignment & (alignment - 1), 0U); + DCHECK_EQ(alignment % sizeof(void*), 0U); + void* ptr = NULL; +#if defined(OS_STARBOARD) + ptr = SbMemoryAllocateAligned(alignment, size); +#elif 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: +// 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) || defined(__LB_SHELL__) + ptr = memalign(alignment, size); +#else + if (posix_memalign(&ptr, alignment, size)) + ptr = NULL; +#endif + // Since aligned allocations may fail for non-memory related reasons, force a + // crash if we encounter a failed allocation; maintaining consistent behavior + // with a normal allocation failure in Chrome. + if (!ptr) { + DLOG(ERROR) << "If you crashed here, your aligned allocation is incorrect: " + << "size=" << size << ", alignment=" << alignment; + CHECK(false); + } + // Sanity check alignment just to be safe. + DCHECK_EQ(reinterpret_cast<uintptr_t>(ptr) & (alignment - 1), 0U); + return ptr; +} + +} // namespace base
diff --git a/src/base/memory/aligned_memory.h b/src/base/memory/aligned_memory.h new file mode 100644 index 0000000..040b56d --- /dev/null +++ b/src/base/memory/aligned_memory.h
@@ -0,0 +1,118 @@ +// 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. + +// AlignedMemory is a POD type that gives you a portable way to specify static +// or local stack data of a given alignment and size. For example, if you need +// static storage for a class, but you want manual control over when the object +// is constructed and destructed (you don't want static initialization and +// destruction), use AlignedMemory: +// +// static AlignedMemory<sizeof(MyClass), ALIGNOF(MyClass)> my_class; +// +// // ... at runtime: +// new(my_class.void_data()) MyClass(); +// +// // ... use it: +// MyClass* mc = my_class.data_as<MyClass>(); +// +// // ... later, to destruct my_class: +// my_class.data_as<MyClass>()->MyClass::~MyClass(); +// +// Alternatively, a runtime sized aligned allocation can be created: +// +// float* my_array = static_cast<float*>(AlignedAlloc(size, alignment)); +// +// // ... later, to release the memory: +// AlignedFree(my_array); +// +// Or using scoped_ptr_malloc: +// +// scoped_ptr_malloc<float, ScopedPtrAlignedFree> my_array( +// static_cast<float*>(AlignedAlloc(size, alignment))); + +#ifndef BASE_MEMORY_ALIGNED_MEMORY_H_ +#define BASE_MEMORY_ALIGNED_MEMORY_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" + +#if defined(COMPILER_MSVC) +#include <malloc.h> +#elif defined(OS_STARBOARD) +#include "starboard/memory.h" +#else +#include <stdlib.h> +#endif + +namespace base { + +// AlignedMemory is specialized for all supported alignments. +// Make sure we get a compiler error if someone uses an unsupported alignment. +template <size_t Size, size_t ByteAlignment> +struct AlignedMemory {}; + +#define BASE_DECL_ALIGNED_MEMORY(byte_alignment) \ + template <size_t Size> \ + class AlignedMemory<Size, byte_alignment> { \ + public: \ + ALIGNAS(byte_alignment) uint8 data_[Size]; \ + void* void_data() { return static_cast<void*>(data_); } \ + const void* void_data() const { \ + return static_cast<const void*>(data_); \ + } \ + template<typename Type> \ + Type* data_as() { return static_cast<Type*>(void_data()); } \ + template<typename Type> \ + const Type* data_as() const { \ + return static_cast<const Type*>(void_data()); \ + } \ + private: \ + void* operator new(size_t); \ + void operator delete(void*); \ + } + +// Specialization for all alignments is required because MSVC (as of VS 2008) +// does not understand ALIGNAS(ALIGNOF(Type)) or ALIGNAS(template_param). +// Greater than 4096 alignment is not supported by some compilers, so 4096 is +// the maximum specified here. +BASE_DECL_ALIGNED_MEMORY(1); +BASE_DECL_ALIGNED_MEMORY(2); +BASE_DECL_ALIGNED_MEMORY(4); +BASE_DECL_ALIGNED_MEMORY(8); +BASE_DECL_ALIGNED_MEMORY(16); +BASE_DECL_ALIGNED_MEMORY(32); +BASE_DECL_ALIGNED_MEMORY(64); +BASE_DECL_ALIGNED_MEMORY(128); +BASE_DECL_ALIGNED_MEMORY(256); +BASE_DECL_ALIGNED_MEMORY(512); +BASE_DECL_ALIGNED_MEMORY(1024); +BASE_DECL_ALIGNED_MEMORY(2048); +BASE_DECL_ALIGNED_MEMORY(4096); + +#undef BASE_DECL_ALIGNED_MEMORY + +BASE_EXPORT void* AlignedAlloc(size_t size, size_t alignment); + +inline void AlignedFree(void* ptr) { +#if defined(COMPILER_MSVC) + _aligned_free(ptr); +#elif defined(OS_STARBOARD) + SbMemoryFreeAligned(ptr); +#else + free(ptr); +#endif +} + +// Helper class for use with scoped_ptr_malloc. +class BASE_EXPORT ScopedPtrAlignedFree { + public: + inline void operator()(void* ptr) const { + AlignedFree(ptr); + } +}; + +} // namespace base + +#endif // BASE_MEMORY_ALIGNED_MEMORY_H_
diff --git a/src/base/memory/aligned_memory_unittest.cc b/src/base/memory/aligned_memory_unittest.cc new file mode 100644 index 0000000..ba82618 --- /dev/null +++ b/src/base/memory/aligned_memory_unittest.cc
@@ -0,0 +1,83 @@ +// 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/memory/aligned_memory.h" +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define EXPECT_ALIGNED(ptr, align) \ + EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(ptr) & (align - 1)) + +namespace { + +using base::AlignedMemory; + +TEST(AlignedMemoryTest, StaticAlignment) { + static AlignedMemory<8, 8> raw8; + static AlignedMemory<8, 16> raw16; + static AlignedMemory<8, 256> raw256; + static AlignedMemory<8, 4096> raw4096; + + EXPECT_EQ(8u, ALIGNOF(raw8)); + EXPECT_EQ(16u, ALIGNOF(raw16)); + EXPECT_EQ(256u, ALIGNOF(raw256)); + EXPECT_EQ(4096u, ALIGNOF(raw4096)); + + EXPECT_ALIGNED(raw8.void_data(), 8); + EXPECT_ALIGNED(raw16.void_data(), 16); + EXPECT_ALIGNED(raw256.void_data(), 256); + EXPECT_ALIGNED(raw4096.void_data(), 4096); +} + +TEST(AlignedMemoryTest, StackAlignment) { + AlignedMemory<8, 8> raw8; + AlignedMemory<8, 16> raw16; + AlignedMemory<8, 256> raw256; + + EXPECT_EQ(8u, ALIGNOF(raw8)); + EXPECT_EQ(16u, ALIGNOF(raw16)); + EXPECT_EQ(256u, ALIGNOF(raw256)); + + EXPECT_ALIGNED(raw8.void_data(), 8); + EXPECT_ALIGNED(raw16.void_data(), 16); + EXPECT_ALIGNED(raw256.void_data(), 256); + + // TODO: This test hits an armv7 bug in clang. crbug.com/138066 +#if !defined(ARCH_CPU_ARM_FAMILY) + AlignedMemory<8, 4096> raw4096; + EXPECT_EQ(4096u, ALIGNOF(raw4096)); + EXPECT_ALIGNED(raw4096.void_data(), 4096); +#endif // !(defined(OS_IOS) && defined(ARCH_CPU_ARM_FAMILY)) +} + +TEST(AlignedMemoryTest, DynamicAllocation) { + void* p = base::AlignedAlloc(8, 8); + EXPECT_TRUE(p); + EXPECT_ALIGNED(p, 8); + base::AlignedFree(p); + + p = base::AlignedAlloc(8, 16); + EXPECT_TRUE(p); + EXPECT_ALIGNED(p, 16); + base::AlignedFree(p); + + p = base::AlignedAlloc(8, 256); + EXPECT_TRUE(p); + EXPECT_ALIGNED(p, 256); + base::AlignedFree(p); + + p = base::AlignedAlloc(8, 4096); + EXPECT_TRUE(p); + EXPECT_ALIGNED(p, 4096); + base::AlignedFree(p); +} + +TEST(AlignedMemoryTest, ScopedDynamicAllocation) { + scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> p( + static_cast<float*>(base::AlignedAlloc(8, 8))); + EXPECT_TRUE(p.get()); + EXPECT_ALIGNED(p.get(), 8); +} + +} // namespace
diff --git a/src/base/memory/linked_ptr.h b/src/base/memory/linked_ptr.h new file mode 100644 index 0000000..80044ad --- /dev/null +++ b/src/base/memory/linked_ptr.h
@@ -0,0 +1,181 @@ +// 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. +// +// A "smart" pointer type with reference tracking. Every pointer to a +// particular object is kept on a circular linked list. When the last pointer +// to an object is destroyed or reassigned, the object is deleted. +// +// Used properly, this deletes the object when the last reference goes away. +// There are several caveats: +// - Like all reference counting schemes, cycles lead to leaks. +// - Each smart pointer is actually two pointers (8 bytes instead of 4). +// - Every time a pointer is released, the entire list of pointers to that +// object is traversed. This class is therefore NOT SUITABLE when there +// will often be more than two or three pointers to a particular object. +// - References are only tracked as long as linked_ptr<> objects are copied. +// If a linked_ptr<> is converted to a raw pointer and back, BAD THINGS +// will happen (double deletion). +// +// A good use of this class is storing object references in STL containers. +// You can safely put linked_ptr<> in a vector<>. +// Other uses may not be as good. +// +// Note: If you use an incomplete type with linked_ptr<>, the class +// *containing* linked_ptr<> must have a constructor and destructor (even +// if they do nothing!). +// +// Thread Safety: +// A linked_ptr is NOT thread safe. Copying a linked_ptr object is +// effectively a read-write operation. +// +// Alternative: to linked_ptr is shared_ptr, which +// - is also two pointers in size (8 bytes for 32 bit addresses) +// - is thread safe for copying and deletion +// - supports weak_ptrs + +#ifndef BASE_MEMORY_LINKED_PTR_H_ +#define BASE_MEMORY_LINKED_PTR_H_ + +#include "base/logging.h" // for CHECK macros + +// This is used internally by all instances of linked_ptr<>. It needs to be +// a non-template class because different types of linked_ptr<> can refer to +// the same object (linked_ptr<Superclass>(obj) vs linked_ptr<Subclass>(obj)). +// So, it needs to be possible for different types of linked_ptr to participate +// in the same circular linked list, so we need a single class type here. +// +// DO NOT USE THIS CLASS DIRECTLY YOURSELF. Use linked_ptr<T>. +class linked_ptr_internal { + public: + // Create a new circle that includes only this instance. + void join_new() { + next_ = this; + } + + // Join an existing circle. + void join(linked_ptr_internal const* ptr) { + next_ = ptr->next_; + ptr->next_ = this; + } + + // Leave whatever circle we're part of. Returns true iff we were the + // last member of the circle. Once this is done, you can join() another. + bool depart() { + if (next_ == this) return true; + linked_ptr_internal const* p = next_; + while (p->next_ != this) p = p->next_; + p->next_ = next_; + return false; + } + + private: + mutable linked_ptr_internal const* next_; +}; + +template <typename T> +class linked_ptr { + public: + typedef T element_type; + + // Take over ownership of a raw pointer. This should happen as soon as + // possible after the object is created. + explicit linked_ptr(T* ptr = NULL) { capture(ptr); } + ~linked_ptr() { depart(); } + + // Copy an existing linked_ptr<>, adding ourselves to the list of references. + template <typename U> linked_ptr(linked_ptr<U> const& ptr) { copy(&ptr); } + + linked_ptr(linked_ptr const& ptr) { + DCHECK_NE(&ptr, this); + copy(&ptr); + } + + // Assignment releases the old value and acquires the new. + template <typename U> linked_ptr& operator=(linked_ptr<U> const& ptr) { + depart(); + copy(&ptr); + return *this; + } + + linked_ptr& operator=(linked_ptr const& ptr) { + if (&ptr != this) { + depart(); + copy(&ptr); + } + return *this; + } + + // Smart pointer members. + void reset(T* ptr = NULL) { + depart(); + capture(ptr); + } + T* get() const { return value_; } + T* operator->() const { return value_; } + T& operator*() const { return *value_; } + // Release ownership of the pointed object and returns it. + // Sole ownership by this linked_ptr object is required. + T* release() { + bool last = link_.depart(); + CHECK(last); + T* v = value_; + value_ = NULL; + return v; + } + + bool operator==(const T* p) const { return value_ == p; } + bool operator!=(const T* p) const { return value_ != p; } + template <typename U> + bool operator==(linked_ptr<U> const& ptr) const { + return value_ == ptr.get(); + } + template <typename U> + bool operator!=(linked_ptr<U> const& ptr) const { + return value_ != ptr.get(); + } + + private: + template <typename U> + friend class linked_ptr; + + T* value_; + linked_ptr_internal link_; + + void depart() { + if (link_.depart()) delete value_; + } + + void capture(T* ptr) { + value_ = ptr; + link_.join_new(); + } + + template <typename U> void copy(linked_ptr<U> const* ptr) { + value_ = ptr->get(); + if (value_) + link_.join(&ptr->link_); + else + link_.join_new(); + } +}; + +template<typename T> inline +bool operator==(T* ptr, const linked_ptr<T>& x) { + return ptr == x.get(); +} + +template<typename T> inline +bool operator!=(T* ptr, const linked_ptr<T>& x) { + return ptr != x.get(); +} + +// A function to convert T* into linked_ptr<T> +// Doing e.g. make_linked_ptr(new FooBarBaz<type>(arg)) is a shorter notation +// for linked_ptr<FooBarBaz<type> >(new FooBarBaz<type>(arg)) +template <typename T> +linked_ptr<T> make_linked_ptr(T* ptr) { + return linked_ptr<T>(ptr); +} + +#endif // BASE_MEMORY_LINKED_PTR_H_
diff --git a/src/base/memory/linked_ptr_unittest.cc b/src/base/memory/linked_ptr_unittest.cc new file mode 100644 index 0000000..4550350 --- /dev/null +++ b/src/base/memory/linked_ptr_unittest.cc
@@ -0,0 +1,110 @@ +// 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 <string> + +#include "base/memory/linked_ptr.h" +#include "base/stringprintf.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +int num = 0; + +std::string history; + +// Class which tracks allocation/deallocation +struct A { + A(): mynum(num++) { history += base::StringPrintf("A%d ctor\n", mynum); } + virtual ~A() { history += base::StringPrintf("A%d dtor\n", mynum); } + virtual void Use() { history += base::StringPrintf("A%d use\n", mynum); } + int mynum; +}; + +// Subclass +struct B: public A { + B() { history += base::StringPrintf("B%d ctor\n", mynum); } + virtual ~B() { history += base::StringPrintf("B%d dtor\n", mynum); } + virtual void Use() OVERRIDE { + history += base::StringPrintf("B%d use\n", mynum); + } +}; + +} // namespace + +TEST(LinkedPtrTest, Test) { + { + linked_ptr<A> a0, a1, a2; + a0 = a0; + a1 = a2; + ASSERT_EQ(a0.get(), static_cast<A*>(NULL)); + ASSERT_EQ(a1.get(), static_cast<A*>(NULL)); + ASSERT_EQ(a2.get(), static_cast<A*>(NULL)); + ASSERT_TRUE(a0 == NULL); + ASSERT_TRUE(a1 == NULL); + ASSERT_TRUE(a2 == NULL); + + { + linked_ptr<A> a3(new A); + a0 = a3; + ASSERT_TRUE(a0 == a3); + ASSERT_TRUE(a0 != NULL); + ASSERT_TRUE(a0.get() == a3); + ASSERT_TRUE(a0 == a3.get()); + linked_ptr<A> a4(a0); + a1 = a4; + linked_ptr<A> a5(new A); + ASSERT_TRUE(a5.get() != a3); + ASSERT_TRUE(a5 != a3.get()); + a2 = a5; + linked_ptr<B> b0(new B); + linked_ptr<A> a6(b0); + ASSERT_TRUE(b0 == a6); + ASSERT_TRUE(a6 == b0); + ASSERT_TRUE(b0 != NULL); + a5 = b0; + a5 = b0; + a3->Use(); + a4->Use(); + a5->Use(); + a6->Use(); + b0->Use(); + (*b0).Use(); + b0.get()->Use(); + } + + a0->Use(); + a1->Use(); + a2->Use(); + + a1 = a2; + a2.reset(new A); + a0.reset(); + + linked_ptr<A> a7; + } + + ASSERT_EQ(history, + "A0 ctor\n" + "A1 ctor\n" + "A2 ctor\n" + "B2 ctor\n" + "A0 use\n" + "A0 use\n" + "B2 use\n" + "B2 use\n" + "B2 use\n" + "B2 use\n" + "B2 use\n" + "B2 dtor\n" + "A2 dtor\n" + "A0 use\n" + "A0 use\n" + "A1 use\n" + "A3 ctor\n" + "A0 dtor\n" + "A3 dtor\n" + "A1 dtor\n" + ); +}
diff --git a/src/base/memory/manual_constructor.h b/src/base/memory/manual_constructor.h new file mode 100644 index 0000000..9275f73 --- /dev/null +++ b/src/base/memory/manual_constructor.h
@@ -0,0 +1,125 @@ +// 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. + +// ManualConstructor statically-allocates space in which to store some +// object, but does not initialize it. You can then call the constructor +// and destructor for the object yourself as you see fit. This is useful +// for memory management optimizations, where you want to initialize and +// destroy an object multiple times but only allocate it once. +// +// (When I say ManualConstructor statically allocates space, I mean that +// the ManualConstructor object itself is forced to be the right size.) +// +// For example usage, check out base/containers/small_map.h. + +#ifndef BASE_MEMORY_MANUAL_CONSTRUCTOR_H_ +#define BASE_MEMORY_MANUAL_CONSTRUCTOR_H_ + +#include <stddef.h> + +#include "base/memory/aligned_memory.h" + +namespace base { + +template <typename Type> +class ManualConstructor { + public: + // No constructor or destructor because one of the most useful uses of + // this class is as part of a union, and members of a union cannot have + // constructors or destructors. And, anyway, the whole point of this + // class is to bypass these. + + // Support users creating arrays of ManualConstructor<>s. This ensures that + // the array itself has the correct alignment. + static void* operator new[](size_t size) { +#if defined(COMPILER_MSVC) + return AlignedAlloc(size, __alignof(Type)); +#else + return AlignedAlloc(size, __alignof__(Type)); +#endif + } + static void operator delete[](void* mem) { + AlignedFree(mem); + } + + inline Type* get() { + return space_.template data_as<Type>(); + } + inline const Type* get() const { + return space_.template data_as<Type>(); + } + + inline Type* operator->() { return get(); } + inline const Type* operator->() const { return get(); } + + inline Type& operator*() { return *get(); } + inline const Type& operator*() const { return *get(); } + + // You can pass up to eight constructor arguments as arguments of Init(). + inline void Init() { + new(space_.void_data()) Type; + } + + template <typename T1> + inline void Init(const T1& p1) { + new(space_.void_data()) Type(p1); + } + + template <typename T1, typename T2> + inline void Init(const T1& p1, const T2& p2) { + new(space_.void_data()) Type(p1, p2); + } + + template <typename T1, typename T2, typename T3> + inline void Init(const T1& p1, const T2& p2, const T3& p3) { + new(space_.void_data()) Type(p1, p2, p3); + } + + template <typename T1, typename T2, typename T3, typename T4> + inline void Init(const T1& p1, const T2& p2, const T3& p3, const T4& p4) { + new(space_.void_data()) Type(p1, p2, p3, p4); + } + + template <typename T1, typename T2, typename T3, typename T4, typename T5> + inline void Init(const T1& p1, const T2& p2, const T3& p3, const T4& p4, + const T5& p5) { + new(space_.void_data()) Type(p1, p2, p3, p4, p5); + } + + template <typename T1, typename T2, typename T3, typename T4, typename T5, + typename T6> + inline void Init(const T1& p1, const T2& p2, const T3& p3, const T4& p4, + const T5& p5, const T6& p6) { + new(space_.void_data()) Type(p1, p2, p3, p4, p5, p6); + } + + template <typename T1, typename T2, typename T3, typename T4, typename T5, + typename T6, typename T7> + inline void Init(const T1& p1, const T2& p2, const T3& p3, const T4& p4, + const T5& p5, const T6& p6, const T7& p7) { + new(space_.void_data()) Type(p1, p2, p3, p4, p5, p6, p7); + } + + template <typename T1, typename T2, typename T3, typename T4, typename T5, + typename T6, typename T7, typename T8> + inline void Init(const T1& p1, const T2& p2, const T3& p3, const T4& p4, + const T5& p5, const T6& p6, const T7& p7, const T8& p8) { + new(space_.void_data()) Type(p1, p2, p3, p4, p5, p6, p7, p8); + } + + inline void Destroy() { + get()->~Type(); + } + + private: +#if defined(COMPILER_MSVC) + AlignedMemory<sizeof(Type), __alignof(Type)> space_; +#else + AlignedMemory<sizeof(Type), __alignof__(Type)> space_; +#endif +}; + +} // namespace base + +#endif // BASE_MEMORY_MANUAL_CONSTRUCTOR_H_
diff --git a/src/base/memory/raw_scoped_refptr_mismatch_checker.h b/src/base/memory/raw_scoped_refptr_mismatch_checker.h new file mode 100644 index 0000000..7974f30 --- /dev/null +++ b/src/base/memory/raw_scoped_refptr_mismatch_checker.h
@@ -0,0 +1,129 @@ +// 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. + +#ifndef BASE_MEMORY_RAW_SCOPED_REFPTR_MISMATCH_CHECKER_H_ +#define BASE_MEMORY_RAW_SCOPED_REFPTR_MISMATCH_CHECKER_H_ + +#include "base/memory/ref_counted.h" +#include "base/template_util.h" +#include "base/tuple.h" +#include "build/build_config.h" + +// It is dangerous to post a task with a T* argument where T is a subtype of +// RefCounted(Base|ThreadSafeBase), since by the time the parameter is used, the +// object may already have been deleted since it was not held with a +// scoped_refptr. Example: http://crbug.com/27191 +// The following set of traits are designed to generate a compile error +// whenever this antipattern is attempted. + +namespace base { + +// This is a base internal implementation file used by task.h and callback.h. +// Not for public consumption, so we wrap it in namespace internal. +namespace internal { + +template <typename T> +struct NeedsScopedRefptrButGetsRawPtr { +#if defined(OS_WIN) + enum { + value = base::false_type::value + }; +#else + enum { + // Human readable translation: you needed to be a scoped_refptr if you are a + // raw pointer type and are convertible to a RefCounted(Base|ThreadSafeBase) + // type. + value = (is_pointer<T>::value && + (is_convertible<T, subtle::RefCountedBase*>::value || + is_convertible<T, subtle::RefCountedThreadSafeBase*>::value)) + }; +#endif +}; + +template <typename Params> +struct ParamsUseScopedRefptrCorrectly { + enum { value = 0 }; +}; + +template <> +struct ParamsUseScopedRefptrCorrectly<Tuple0> { + enum { value = 1 }; +}; + +template <typename A> +struct ParamsUseScopedRefptrCorrectly<Tuple1<A> > { + enum { value = !NeedsScopedRefptrButGetsRawPtr<A>::value }; +}; + +template <typename A, typename B> +struct ParamsUseScopedRefptrCorrectly<Tuple2<A, B> > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr<A>::value || + NeedsScopedRefptrButGetsRawPtr<B>::value) }; +}; + +template <typename A, typename B, typename C> +struct ParamsUseScopedRefptrCorrectly<Tuple3<A, B, C> > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr<A>::value || + NeedsScopedRefptrButGetsRawPtr<B>::value || + NeedsScopedRefptrButGetsRawPtr<C>::value) }; +}; + +template <typename A, typename B, typename C, typename D> +struct ParamsUseScopedRefptrCorrectly<Tuple4<A, B, C, D> > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr<A>::value || + NeedsScopedRefptrButGetsRawPtr<B>::value || + NeedsScopedRefptrButGetsRawPtr<C>::value || + NeedsScopedRefptrButGetsRawPtr<D>::value) }; +}; + +template <typename A, typename B, typename C, typename D, typename E> +struct ParamsUseScopedRefptrCorrectly<Tuple5<A, B, C, D, E> > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr<A>::value || + NeedsScopedRefptrButGetsRawPtr<B>::value || + NeedsScopedRefptrButGetsRawPtr<C>::value || + NeedsScopedRefptrButGetsRawPtr<D>::value || + NeedsScopedRefptrButGetsRawPtr<E>::value) }; +}; + +template <typename A, typename B, typename C, typename D, typename E, + typename F> +struct ParamsUseScopedRefptrCorrectly<Tuple6<A, B, C, D, E, F> > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr<A>::value || + NeedsScopedRefptrButGetsRawPtr<B>::value || + NeedsScopedRefptrButGetsRawPtr<C>::value || + NeedsScopedRefptrButGetsRawPtr<D>::value || + NeedsScopedRefptrButGetsRawPtr<E>::value || + NeedsScopedRefptrButGetsRawPtr<F>::value) }; +}; + +template <typename A, typename B, typename C, typename D, typename E, + typename F, typename G> +struct ParamsUseScopedRefptrCorrectly<Tuple7<A, B, C, D, E, F, G> > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr<A>::value || + NeedsScopedRefptrButGetsRawPtr<B>::value || + NeedsScopedRefptrButGetsRawPtr<C>::value || + NeedsScopedRefptrButGetsRawPtr<D>::value || + NeedsScopedRefptrButGetsRawPtr<E>::value || + NeedsScopedRefptrButGetsRawPtr<F>::value || + NeedsScopedRefptrButGetsRawPtr<G>::value) }; +}; + +template <typename A, typename B, typename C, typename D, typename E, + typename F, typename G, typename H> +struct ParamsUseScopedRefptrCorrectly<Tuple8<A, B, C, D, E, F, G, H> > { + enum { value = !(NeedsScopedRefptrButGetsRawPtr<A>::value || + NeedsScopedRefptrButGetsRawPtr<B>::value || + NeedsScopedRefptrButGetsRawPtr<C>::value || + NeedsScopedRefptrButGetsRawPtr<D>::value || + NeedsScopedRefptrButGetsRawPtr<E>::value || + NeedsScopedRefptrButGetsRawPtr<F>::value || + NeedsScopedRefptrButGetsRawPtr<G>::value || + NeedsScopedRefptrButGetsRawPtr<H>::value) }; +}; + +} // namespace internal + +} // namespace base + +#endif // BASE_MEMORY_RAW_SCOPED_REFPTR_MISMATCH_CHECKER_H_
diff --git a/src/base/memory/ref_counted.cc b/src/base/memory/ref_counted.cc new file mode 100644 index 0000000..31ad509 --- /dev/null +++ b/src/base/memory/ref_counted.cc
@@ -0,0 +1,95 @@ +// 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/memory/ref_counted.h" + +#include "base/logging.h" +#include "base/threading/thread_collision_warner.h" + +namespace base { + +namespace subtle { + +RefCountedBase::RefCountedBase() + : ref_count_(0) +#ifndef NDEBUG + , in_dtor_(false) +#endif + { +} + +RefCountedBase::~RefCountedBase() { +#ifndef NDEBUG + DCHECK(in_dtor_) << "RefCounted object deleted without calling Release()"; +#endif +} + +void RefCountedBase::AddRef() const { + // TODO(maruel): Add back once it doesn't assert 500 times/sec. + // Current thread books the critical section "AddRelease" without release it. + // DFAKE_SCOPED_LOCK_THREAD_LOCKED(add_release_); +#ifndef NDEBUG + DCHECK(!in_dtor_); +#endif + ++ref_count_; +} + +bool RefCountedBase::Release() const { + // TODO(maruel): Add back once it doesn't assert 500 times/sec. + // Current thread books the critical section "AddRelease" without release it. + // DFAKE_SCOPED_LOCK_THREAD_LOCKED(add_release_); +#ifndef NDEBUG + DCHECK(!in_dtor_); +#endif + if (--ref_count_ == 0) { +#ifndef NDEBUG + in_dtor_ = true; +#endif + return true; + } + return false; +} + +bool RefCountedThreadSafeBase::HasOneRef() const { + return AtomicRefCountIsOne( + &const_cast<RefCountedThreadSafeBase*>(this)->ref_count_); +} + +RefCountedThreadSafeBase::RefCountedThreadSafeBase() : ref_count_(0) { +#ifndef NDEBUG + in_dtor_ = false; +#endif +} + +RefCountedThreadSafeBase::~RefCountedThreadSafeBase() { +#ifndef NDEBUG + DCHECK(in_dtor_) << "RefCountedThreadSafe object deleted without " + "calling Release()"; +#endif +} + +void RefCountedThreadSafeBase::AddRef() const { +#ifndef NDEBUG + DCHECK(!in_dtor_); +#endif + AtomicRefCountInc(&ref_count_); +} + +bool RefCountedThreadSafeBase::Release() const { +#ifndef NDEBUG + DCHECK(!in_dtor_); + DCHECK(!AtomicRefCountIsZero(&ref_count_)); +#endif + if (!AtomicRefCountDec(&ref_count_)) { +#ifndef NDEBUG + in_dtor_ = true; +#endif + return true; + } + return false; +} + +} // namespace subtle + +} // namespace base
diff --git a/src/base/memory/ref_counted.h b/src/base/memory/ref_counted.h new file mode 100644 index 0000000..f5941e5 --- /dev/null +++ b/src/base/memory/ref_counted.h
@@ -0,0 +1,378 @@ +// 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_MEMORY_REF_COUNTED_H_ +#define BASE_MEMORY_REF_COUNTED_H_ + +#include "base/atomic_ref_count.h" +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/threading/thread_collision_warner.h" + +namespace base { + +namespace subtle { + +class BASE_EXPORT RefCountedBase { + public: + bool HasOneRef() const { return ref_count_ == 1; } + + protected: + RefCountedBase(); + ~RefCountedBase(); + + void AddRef() const; + + // Returns true if the object should self-delete. + bool Release() const; + + private: + mutable int ref_count_; +#ifndef NDEBUG + mutable bool in_dtor_; +#endif + + DFAKE_MUTEX(add_release_); + + DISALLOW_COPY_AND_ASSIGN(RefCountedBase); +}; + +class BASE_EXPORT RefCountedThreadSafeBase { + public: + bool HasOneRef() const; + + protected: + RefCountedThreadSafeBase(); + ~RefCountedThreadSafeBase(); + + void AddRef() const; + + // Returns true if the object should self-delete. + bool Release() const; + + private: + mutable AtomicRefCount ref_count_; +#ifndef NDEBUG + mutable bool in_dtor_; +#endif + + DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafeBase); +}; + +} // namespace subtle + +// +// A base class for reference counted classes. Otherwise, known as a cheap +// knock-off of WebKit's RefCounted<T> class. To use this guy just extend your +// class from it like so: +// +// class MyFoo : public base::RefCounted<MyFoo> { +// ... +// private: +// friend class base::RefCounted<MyFoo>; +// ~MyFoo(); +// }; +// +// You should always make your destructor private, to avoid any code deleting +// the object accidently while there are references to it. +template <class T> +class RefCounted : public subtle::RefCountedBase { + public: + RefCounted() {} + + void AddRef() const { + subtle::RefCountedBase::AddRef(); + } + + void Release() const { + if (subtle::RefCountedBase::Release()) { + delete static_cast<const T*>(this); + } + } + + protected: + ~RefCounted() {} + + private: + DISALLOW_COPY_AND_ASSIGN(RefCounted<T>); +}; + +// Forward declaration. +template <class T, typename Traits> class RefCountedThreadSafe; + +// Default traits for RefCountedThreadSafe<T>. Deletes the object when its ref +// count reaches 0. Overload to delete it on a different thread etc. +template<typename T> +struct DefaultRefCountedThreadSafeTraits { + static void Destruct(const T* x) { + // Delete through RefCountedThreadSafe to make child classes only need to be + // friend with RefCountedThreadSafe instead of this struct, which is an + // implementation detail. + RefCountedThreadSafe<T, + DefaultRefCountedThreadSafeTraits>::DeleteInternal(x); + } +}; + +// +// A thread-safe variant of RefCounted<T> +// +// class MyFoo : public base::RefCountedThreadSafe<MyFoo> { +// ... +// }; +// +// If you're using the default trait, then you should add compile time +// asserts that no one else is deleting your object. i.e. +// private: +// friend class base::RefCountedThreadSafe<MyFoo>; +// ~MyFoo(); +template <class T, typename Traits = DefaultRefCountedThreadSafeTraits<T> > +class RefCountedThreadSafe : public subtle::RefCountedThreadSafeBase { + public: + RefCountedThreadSafe() {} + + void AddRef() const { + subtle::RefCountedThreadSafeBase::AddRef(); + } + + void Release() const { + if (subtle::RefCountedThreadSafeBase::Release()) { + Traits::Destruct(static_cast<const T*>(this)); + } + } + + protected: + ~RefCountedThreadSafe() {} + + private: + friend struct DefaultRefCountedThreadSafeTraits<T>; + static void DeleteInternal(const T* x) { delete x; } + + DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafe); +}; + +// +// A thread-safe wrapper for some piece of data so we can place other +// things in scoped_refptrs<>. +// +template<typename T> +class RefCountedData + : public base::RefCountedThreadSafe< base::RefCountedData<T> > { + public: + RefCountedData() : data() {} + RefCountedData(const T& in_value) : data(in_value) {} + + T data; + + private: + friend class base::RefCountedThreadSafe<base::RefCountedData<T> >; + ~RefCountedData() {} +}; + +} // namespace base + +// +// A smart pointer class for reference counted objects. Use this class instead +// of calling AddRef and Release manually on a reference counted object to +// avoid common memory leaks caused by forgetting to Release an object +// reference. Sample usage: +// +// class MyFoo : public RefCounted<MyFoo> { +// ... +// }; +// +// void some_function() { +// scoped_refptr<MyFoo> foo = new MyFoo(); +// foo->Method(param); +// // |foo| is released when this function returns +// } +// +// void some_other_function() { +// scoped_refptr<MyFoo> foo = new MyFoo(); +// ... +// foo = NULL; // explicitly releases |foo| +// ... +// if (foo) +// foo->Method(param); +// } +// +// The above examples show how scoped_refptr<T> acts like a pointer to T. +// Given two scoped_refptr<T> classes, it is also possible to exchange +// references between the two objects, like so: +// +// { +// scoped_refptr<MyFoo> a = new MyFoo(); +// scoped_refptr<MyFoo> b; +// +// b.swap(a); +// // now, |b| references the MyFoo object, and |a| references NULL. +// } +// +// To make both |a| and |b| in the above example reference the same MyFoo +// object, simply use the assignment operator: +// +// { +// scoped_refptr<MyFoo> a = new MyFoo(); +// scoped_refptr<MyFoo> b; +// +// b = a; +// // now, |a| and |b| each own a reference to the same MyFoo object. +// } +// +template <class T> +class scoped_refptr { + public: + typedef T element_type; + + scoped_refptr() : ptr_(NULL) { + } + + scoped_refptr(T* p) : ptr_(p) { + if (ptr_) + ptr_->AddRef(); + } + + scoped_refptr(const scoped_refptr<T>& r) : ptr_(r.ptr_) { + if (ptr_) + ptr_->AddRef(); + } + + template <typename U> + scoped_refptr(const scoped_refptr<U>& r) : ptr_(r.get()) { + if (ptr_) + ptr_->AddRef(); + } + + ~scoped_refptr() { + if (ptr_) + ptr_->Release(); + } + + T* get() const { return ptr_; } + operator T*() const { return ptr_; } + T* operator->() const { + DCHECK(ptr_); + return ptr_; + } + // The compiler requires an explicit * operator here. +#if defined(__LB_PS3__) + T& operator*() const { + DCHECK(ptr_); + return *ptr_; + } +#endif + + scoped_refptr<T>& operator=(T* p) { + // AddRef first so that self assignment should work + if (p) + p->AddRef(); + T* old_ptr = ptr_; + ptr_ = p; + if (old_ptr) + old_ptr->Release(); + return *this; + } + + scoped_refptr<T>& operator=(const scoped_refptr<T>& r) { + return *this = r.ptr_; + } + + template <typename U> + scoped_refptr<T>& operator=(const scoped_refptr<U>& r) { + return *this = r.get(); + } + + void swap(T** pp) { + T* p = ptr_; + ptr_ = *pp; + *pp = p; + } + + void swap(scoped_refptr<T>& r) { + swap(&r.ptr_); + } + + protected: + T* ptr_; +}; + +// Handy utility for creating a scoped_refptr<T> out of a T* explicitly without +// having to retype all the template arguments +template <typename T> +scoped_refptr<T> make_scoped_refptr(T* t) { + return scoped_refptr<T>(t); +} + +// Make scoped_reftr usable as key in base::hash_map. + +// +// GCC-flavored hash functor. +// +#if defined(COMPILER_GCC) && defined(__LB_LINUX__) + +namespace __gnu_cxx { + +// Forward declaration in case <hash_fun.h> is not #include'd. +template <typename Key> +struct hash; + +template <typename T> +struct hash<scoped_refptr<T> > { + size_t operator()(const scoped_refptr<T>& key) const { + return base_hash(key.get()); + } + + hash<T*> base_hash; +}; + +} // namespace __gnu_cxx + +// +// Dinkumware-flavored hash functor. +// +#else + +#if defined(COMPILER_MSVC) +namespace stdext { +#else +namespace std { +#endif + +// Forward declaration in case <xhash> is not #include'd. +template <typename Key, typename Predicate> +class hash_compare; + +template <typename T, typename Predicate> +class hash_compare<scoped_refptr<T>, Predicate> { + public: + typedef hash_compare<T*, Predicate> BaseHashCompare; + + enum { + bucket_size = BaseHashCompare::bucket_size, +#if !defined(COMPILER_MSVC) + min_buckets = BaseHashCompare::min_buckets, +#endif + }; + + hash_compare() {} + hash_compare(Predicate predicate) : base_hash_compare_(predicate) {} + + size_t operator()(const scoped_refptr<T>& key) const { + return base_hash_compare_(key.get()); + } + + bool operator()(const scoped_refptr<T>& lhs, + const scoped_refptr<T>& rhs) const { + return base_hash_compare_(lhs.get(), rhs.get()); + } + + private: + BaseHashCompare base_hash_compare_; +}; + +} // namespace std[ext] + +#endif + +#endif // BASE_MEMORY_REF_COUNTED_H_
diff --git a/src/base/memory/ref_counted_memory.cc b/src/base/memory/ref_counted_memory.cc new file mode 100644 index 0000000..b048a6e --- /dev/null +++ b/src/base/memory/ref_counted_memory.cc
@@ -0,0 +1,77 @@ +// 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/memory/ref_counted_memory.h" + +#include "base/logging.h" + +namespace base { + +bool RefCountedMemory::Equals( + const scoped_refptr<RefCountedMemory>& other) const { + return other.get() && + size() == other->size() && + (memcmp(front(), other->front(), size()) == 0); +} + +RefCountedMemory::RefCountedMemory() {} + +RefCountedMemory::~RefCountedMemory() {} + +const unsigned char* RefCountedStaticMemory::front() const { + return data_; +} + +size_t RefCountedStaticMemory::size() const { + return length_; +} + +RefCountedStaticMemory::~RefCountedStaticMemory() {} + +RefCountedBytes::RefCountedBytes() {} + +RefCountedBytes::RefCountedBytes(const std::vector<unsigned char>& initializer) + : data_(initializer) { +} + +RefCountedBytes* RefCountedBytes::TakeVector( + std::vector<unsigned char>* to_destroy) { + RefCountedBytes* bytes = new RefCountedBytes; + bytes->data_.swap(*to_destroy); + return bytes; +} + +const unsigned char* RefCountedBytes::front() const { + // STL will assert if we do front() on an empty vector, but calling code + // expects a NULL. + return size() ? &data_.front() : NULL; +} + +size_t RefCountedBytes::size() const { + return data_.size(); +} + +RefCountedBytes::~RefCountedBytes() {} + +RefCountedString::RefCountedString() {} + +RefCountedString::~RefCountedString() {} + +// static +RefCountedString* RefCountedString::TakeString(std::string* to_destroy) { + RefCountedString* self = new RefCountedString; + to_destroy->swap(self->data_); + return self; +} + +const unsigned char* RefCountedString::front() const { + return data_.empty() ? NULL : + reinterpret_cast<const unsigned char*>(data_.data()); +} + +size_t RefCountedString::size() const { + return data_.size(); +} + +} // namespace base
diff --git a/src/base/memory/ref_counted_memory.h b/src/base/memory/ref_counted_memory.h new file mode 100644 index 0000000..b99871b --- /dev/null +++ b/src/base/memory/ref_counted_memory.h
@@ -0,0 +1,118 @@ +// 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_MEMORY_REF_COUNTED_MEMORY_H_ +#define BASE_MEMORY_REF_COUNTED_MEMORY_H_ + +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" + +namespace base { + +// A generic interface to memory. This object is reference counted because one +// of its two subclasses own the data they carry, and we need to have +// heterogeneous containers of these two types of memory. +class BASE_EXPORT RefCountedMemory + : public base::RefCountedThreadSafe<RefCountedMemory> { + public: + // Retrieves a pointer to the beginning of the data we point to. If the data + // is empty, this will return NULL. + virtual const unsigned char* front() const = 0; + + // Size of the memory pointed to. + virtual size_t size() const = 0; + + // Returns true if |other| is byte for byte equal. + bool Equals(const scoped_refptr<RefCountedMemory>& other) const; + + protected: + friend class base::RefCountedThreadSafe<RefCountedMemory>; + RefCountedMemory(); + virtual ~RefCountedMemory(); +}; + +// An implementation of RefCountedMemory, where the ref counting does not +// matter. +class BASE_EXPORT RefCountedStaticMemory : public RefCountedMemory { + public: + RefCountedStaticMemory() + : data_(NULL), length_(0) {} + RefCountedStaticMemory(const unsigned char* data, size_t length) + : data_(length ? data : NULL), length_(length) {} + + // Overridden from RefCountedMemory: + virtual const unsigned char* front() const OVERRIDE; + virtual size_t size() const OVERRIDE; + + private: + virtual ~RefCountedStaticMemory(); + + const unsigned char* data_; + size_t length_; + + DISALLOW_COPY_AND_ASSIGN(RefCountedStaticMemory); +}; + +// An implementation of RefCountedMemory, where we own our the data in a +// vector. +class BASE_EXPORT RefCountedBytes : public RefCountedMemory { + public: + RefCountedBytes(); + + // Constructs a RefCountedBytes object by _copying_ from |initializer|. + RefCountedBytes(const std::vector<unsigned char>& initializer); + + // Constructs a RefCountedBytes object by performing a swap. (To non + // destructively build a RefCountedBytes, use the constructor that takes a + // vector.) + static RefCountedBytes* TakeVector(std::vector<unsigned char>* to_destroy); + + // Overridden from RefCountedMemory: + virtual const unsigned char* front() const OVERRIDE; + virtual size_t size() const OVERRIDE; + + const std::vector<unsigned char>& data() const { return data_; } + std::vector<unsigned char>& data() { return data_; } + + private: + virtual ~RefCountedBytes(); + + std::vector<unsigned char> data_; + + DISALLOW_COPY_AND_ASSIGN(RefCountedBytes); +}; + +// An implementation of RefCountedMemory, where the bytes are stored in an STL +// string. Use this if your data naturally arrives in that format. +class BASE_EXPORT RefCountedString : public RefCountedMemory { + public: + RefCountedString(); + + // Constructs a RefCountedString object by performing a swap. (To non + // destructively build a RefCountedString, use the default constructor and + // copy into object->data()). + static RefCountedString* TakeString(std::string* to_destroy); + + // Overridden from RefCountedMemory: + virtual const unsigned char* front() const OVERRIDE; + virtual size_t size() const OVERRIDE; + + const std::string& data() const { return data_; } + std::string& data() { return data_; } + + private: + virtual ~RefCountedString(); + + std::string data_; + + DISALLOW_COPY_AND_ASSIGN(RefCountedString); +}; + +} // namespace base + +#endif // BASE_MEMORY_REF_COUNTED_MEMORY_H_
diff --git a/src/base/memory/ref_counted_memory_unittest.cc b/src/base/memory/ref_counted_memory_unittest.cc new file mode 100644 index 0000000..c6f2b9c --- /dev/null +++ b/src/base/memory/ref_counted_memory_unittest.cc
@@ -0,0 +1,71 @@ +// 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/memory/ref_counted_memory.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(RefCountedMemoryUnitTest, RefCountedStaticMemory) { + scoped_refptr<RefCountedMemory> mem = new RefCountedStaticMemory( + reinterpret_cast<const uint8*>("static mem00"), 10); + + EXPECT_EQ(10U, mem->size()); + EXPECT_EQ("static mem", + std::string(reinterpret_cast<const char*>(mem->front()), + mem->size())); +} + +TEST(RefCountedMemoryUnitTest, RefCountedBytes) { + std::vector<uint8> data; + data.push_back(45); + data.push_back(99); + scoped_refptr<RefCountedMemory> mem = RefCountedBytes::TakeVector(&data); + + EXPECT_EQ(0U, data.size()); + + EXPECT_EQ(2U, mem->size()); + EXPECT_EQ(45U, mem->front()[0]); + EXPECT_EQ(99U, mem->front()[1]); +} + +TEST(RefCountedMemoryUnitTest, RefCountedString) { + std::string s("destroy me"); + scoped_refptr<RefCountedMemory> mem = RefCountedString::TakeString(&s); + + EXPECT_EQ(0U, s.size()); + + EXPECT_EQ(10U, mem->size()); + EXPECT_EQ('d', mem->front()[0]); + EXPECT_EQ('e', mem->front()[1]); +} + +TEST(RefCountedMemoryUnitTest, Equals) { + std::string s1("same"); + scoped_refptr<RefCountedMemory> mem1 = RefCountedString::TakeString(&s1); + + std::vector<unsigned char> d2; + d2.push_back('s'); + d2.push_back('a'); + d2.push_back('m'); + d2.push_back('e'); + scoped_refptr<RefCountedMemory> mem2 = RefCountedBytes::TakeVector(&d2); + + EXPECT_TRUE(mem1->Equals(mem2)); + + std::string s3("diff"); + scoped_refptr<RefCountedMemory> mem3 = RefCountedString::TakeString(&s3); + + EXPECT_FALSE(mem1->Equals(mem3)); + EXPECT_FALSE(mem2->Equals(mem3)); +} + +TEST(RefCountedMemoryUnitTest, EqualsNull) { + std::string s("str"); + scoped_refptr<RefCountedMemory> mem = RefCountedString::TakeString(&s); + EXPECT_FALSE(mem->Equals(NULL)); +} + +} // namespace base
diff --git a/src/base/memory/ref_counted_unittest.cc b/src/base/memory/ref_counted_unittest.cc new file mode 100644 index 0000000..8ddd5be --- /dev/null +++ b/src/base/memory/ref_counted_unittest.cc
@@ -0,0 +1,64 @@ +// 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/memory/ref_counted.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class SelfAssign : public base::RefCounted<SelfAssign> { + friend class base::RefCounted<SelfAssign>; + + ~SelfAssign() {} +}; + +class CheckDerivedMemberAccess : public scoped_refptr<SelfAssign> { + public: + CheckDerivedMemberAccess() { + // This shouldn't compile if we don't have access to the member variable. + SelfAssign** pptr = &ptr_; + EXPECT_EQ(*pptr, ptr_); + } +}; + +class ScopedRefPtrToSelf : public base::RefCounted<ScopedRefPtrToSelf> { + public: + ScopedRefPtrToSelf() + : ALLOW_THIS_IN_INITIALIZER_LIST(self_ptr_(this)) { + } + + static bool was_destroyed() { return was_destroyed_; } + + void SelfDestruct() { self_ptr_ = NULL; } + + private: + friend class base::RefCounted<ScopedRefPtrToSelf>; + ~ScopedRefPtrToSelf() { was_destroyed_ = true; } + + static bool was_destroyed_; + + scoped_refptr<ScopedRefPtrToSelf> self_ptr_; +}; + +bool ScopedRefPtrToSelf::was_destroyed_ = false; + +} // end namespace + +TEST(RefCountedUnitTest, TestSelfAssignment) { + SelfAssign* p = new SelfAssign; + scoped_refptr<SelfAssign> var(p); + var = var; + EXPECT_EQ(var.get(), p); +} + +TEST(RefCountedUnitTest, ScopedRefPtrMemberAccess) { + CheckDerivedMemberAccess check; +} + +TEST(RefCountedUnitTest, ScopedRefPtrToSelf) { + ScopedRefPtrToSelf* check = new ScopedRefPtrToSelf(); + EXPECT_FALSE(ScopedRefPtrToSelf::was_destroyed()); + check->SelfDestruct(); + EXPECT_TRUE(ScopedRefPtrToSelf::was_destroyed()); +}
diff --git a/src/base/memory/scoped_generic_obj.h b/src/base/memory/scoped_generic_obj.h new file mode 100644 index 0000000..4b3cb86 --- /dev/null +++ b/src/base/memory/scoped_generic_obj.h
@@ -0,0 +1,129 @@ +// 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_MEMORY_SCOPED_GENERIC_OBJ_H_ +#define BASE_MEMORY_SCOPED_GENERIC_OBJ_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" + +// ScopedGenericObj<> is patterned after scoped_ptr_malloc<>, except +// that it assumes the template argument is typedef'ed to a pointer +// type. It does not support retain/release semantics. It takes as its +// second template argument a functor which frees the object. +// +// Example (Mac-specific): +// +// class ScopedDestroyRendererInfo { +// public: +// void operator()(CGLRendererInfoObj x) const { +// CGLDestroyRendererInfo(x); +// } +// }; +// +// ... +// +// CGLRendererInfoObj renderer_info = NULL; +// ... +// ScopedGenericObj<CGLRendererInfoObj, ScopedDestroyRendererInfo> +// scoper(renderer_info); + +template<class C, class FreeProc> +class ScopedGenericObj { + public: + + // The element type + typedef C element_type; + + // Constructor. Defaults to initializing with NULL. + // There is no way to create an uninitialized ScopedGenericObj. + // The input parameter must be allocated with an allocator that matches the + // Free functor. + explicit ScopedGenericObj(C p = C()): obj_(p) {} + + // Destructor. If there is a C object, call the Free functor. + ~ScopedGenericObj() { + reset(); + } + + // Reset. Calls the Free functor on the current owned object, if any. + // Then takes ownership of a new object, if given. + // this->reset(this->get()) works. + void reset(C p = C()) { + if (obj_ != p) { + FreeProc free_proc; + free_proc(obj_); + obj_ = p; + } + } + + operator C() const { + return obj_; + } + + C get() const { + return obj_; + } + + // Comparison operators. + // These return whether a ScopedGenericObj and a plain pointer refer + // to the same object, not just to two different but equal objects. + // For compatibility with the boost-derived implementation, these + // take non-const arguments. + bool operator==(C p) const { + return obj_ == p; + } + + bool operator!=(C p) const { + return obj_ != p; + } + + // Swap two ScopedGenericObjs. + void swap(ScopedGenericObj& b) { + C tmp = b.obj_; + b.obj_ = obj_; + obj_ = tmp; + } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + C release() WARN_UNUSED_RESULT { + C tmp = obj_; + obj_ = NULL; + return tmp; + } + + private: + C obj_; + + // no reason to use these: each ScopedGenericObj should have its own object. + template <class C2, class GP> + bool operator==(ScopedGenericObj<C2, GP> const& p) const; + template <class C2, class GP> + bool operator!=(ScopedGenericObj<C2, GP> const& p) const; + + // Disallow evil constructors. + ScopedGenericObj(const ScopedGenericObj&); + void operator=(const ScopedGenericObj&); +}; + +template<class C, class FP> inline +void swap(ScopedGenericObj<C, FP>& a, ScopedGenericObj<C, FP>& b) { + a.swap(b); +} + +template<class C, class FP> inline +bool operator==(C* p, const ScopedGenericObj<C, FP>& b) { + return p == b.get(); +} + +template<class C, class FP> inline +bool operator!=(C* p, const ScopedGenericObj<C, FP>& b) { + return p != b.get(); +} + +#endif // BASE_MEMORY_SCOPED_GENERIC_OBJ_H_
diff --git a/src/base/memory/scoped_handle.h b/src/base/memory/scoped_handle.h new file mode 100644 index 0000000..b95559d --- /dev/null +++ b/src/base/memory/scoped_handle.h
@@ -0,0 +1,50 @@ +// 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. + +#ifndef BASE_MEMORY_SCOPED_HANDLE_H_ +#define BASE_MEMORY_SCOPED_HANDLE_H_ + +#include <stdio.h> + +#include "base/basictypes.h" + +class ScopedStdioHandle { + public: + ScopedStdioHandle() + : handle_(NULL) { } + + explicit ScopedStdioHandle(FILE* handle) + : handle_(handle) { } + + ~ScopedStdioHandle() { + Close(); + } + + void Close() { + if (handle_) { + fclose(handle_); + handle_ = NULL; + } + } + + FILE* get() const { return handle_; } + + FILE* Take() { + FILE* temp = handle_; + handle_ = NULL; + return temp; + } + + void Set(FILE* newhandle) { + Close(); + handle_ = newhandle; + } + + private: + FILE* handle_; + + DISALLOW_COPY_AND_ASSIGN(ScopedStdioHandle); +}; + +#endif // BASE_MEMORY_SCOPED_HANDLE_H_
diff --git a/src/base/memory/scoped_nsobject.h b/src/base/memory/scoped_nsobject.h new file mode 100644 index 0000000..5d98e3f --- /dev/null +++ b/src/base/memory/scoped_nsobject.h
@@ -0,0 +1,174 @@ +// 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_MEMORY_SCOPED_NSOBJECT_H_ +#define BASE_MEMORY_SCOPED_NSOBJECT_H_ + +#import <Foundation/Foundation.h> +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_policy.h" + +// scoped_nsobject<> is patterned after scoped_ptr<>, but maintains ownership +// of an NSObject subclass object. Style deviations here are solely for +// compatibility with scoped_ptr<>'s interface, with which everyone is already +// familiar. +// +// By default, scoped_nsobject<> takes ownership of an object (in the +// constructor or in reset()) by taking over the caller's existing ownership +// claim. The caller must own the object it gives to scoped_nsobject<>, and +// relinquishes an ownership claim to that object. scoped_nsobject<> does not +// call -retain. This behavior is parametrized by the |OwnershipPolicy| enum. +// If the value |RETAIN| is passed (in the constructor or in reset()), then +// scoped_nsobject<> will call -retain on the object, and the initial +// ownership is not changed. +// +// scoped_nsprotocol<> has the same behavior as scoped_nsobject, but can be used +// with protocols. +// +// scoped_nsobject<> is not to be used for NSAutoreleasePools. For +// NSAutoreleasePools use ScopedNSAutoreleasePool from +// scoped_nsautorelease_pool.h instead. +// We check for bad uses of scoped_nsobject and NSAutoreleasePool at compile +// time with a template specialization (see below). + +template<typename NST> +class scoped_nsprotocol { + public: + explicit scoped_nsprotocol( + NST object = nil, + base::scoped_policy::OwnershipPolicy policy = base::scoped_policy::ASSUME) + : object_(object) { + if (policy == base::scoped_policy::RETAIN) + [object retain]; + } + + scoped_nsprotocol(const scoped_nsprotocol<NST>& that) + : object_([that.object_ retain]) { + } + + ~scoped_nsprotocol() { + [object_ release]; + } + + scoped_nsprotocol& operator=(const scoped_nsprotocol<NST>& that) { + reset(that.get(), base::scoped_policy::RETAIN); + return *this; + } + + void reset(NST object = nil, + base::scoped_policy::OwnershipPolicy policy = + base::scoped_policy::ASSUME) { + if (policy == base::scoped_policy::RETAIN) + [object retain]; + // We intentionally do not check that object != object_ as the caller must + // either already have an ownership claim over whatever it passes to this + // method, or call it with the |RETAIN| policy which will have ensured that + // the object is retained once more when reaching this point. + [object_ release]; + object_ = object; + } + + bool operator==(NST that) const { return object_ == that; } + bool operator!=(NST that) const { return object_ != that; } + + operator NST() const { + return object_; + } + + NST get() const { + return object_; + } + + void swap(scoped_nsprotocol& that) { + NST temp = that.object_; + that.object_ = object_; + object_ = temp; + } + + // scoped_nsprotocol<>::release() is like scoped_ptr<>::release. It is NOT a + // wrapper for [object_ release]. To force a scoped_nsprotocol<> to call + // [object_ release], use scoped_nsprotocol<>::reset(). + NST release() WARN_UNUSED_RESULT { + NST temp = object_; + object_ = nil; + return temp; + } + + // Shift reference to the autorelease pool to be released later. + NST autorelease() { + return [release() autorelease]; + } + + private: + NST object_; +}; + +// Free functions +template <class C> +void swap(scoped_nsprotocol<C>& p1, scoped_nsprotocol<C>& p2) { + p1.swap(p2); +} + +template <class C> +bool operator==(C p1, const scoped_nsprotocol<C>& p2) { + return p1 == p2.get(); +} + +template <class C> +bool operator!=(C p1, const scoped_nsprotocol<C>& p2) { + return p1 != p2.get(); +} + +template<typename NST> +class scoped_nsobject : public scoped_nsprotocol<NST*> { + public: + explicit scoped_nsobject( + NST* object = nil, + base::scoped_policy::OwnershipPolicy policy = base::scoped_policy::ASSUME) + : scoped_nsprotocol<NST*>(object, policy) { + } + + scoped_nsobject(const scoped_nsobject<NST>& that) + : scoped_nsprotocol<NST*>(that) { + } + + scoped_nsobject& operator=(const scoped_nsobject<NST>& that) { + scoped_nsprotocol<NST*>::operator=(that); + return *this; + } +}; + +// Specialization to make scoped_nsobject<id> work. +template<> +class scoped_nsobject<id> : public scoped_nsprotocol<id> { + public: + explicit scoped_nsobject( + id object = nil, + base::scoped_policy::OwnershipPolicy policy = base::scoped_policy::ASSUME) + : scoped_nsprotocol<id>(object, policy) { + } + + scoped_nsobject(const scoped_nsobject<id>& that) + : scoped_nsprotocol<id>(that) { + } + + scoped_nsobject& operator=(const scoped_nsobject<id>& that) { + scoped_nsprotocol<id>::operator=(that); + return *this; + } +}; + +// Do not use scoped_nsobject for NSAutoreleasePools, use +// ScopedNSAutoreleasePool instead. This is a compile time check. See details +// at top of header. +template<> +class scoped_nsobject<NSAutoreleasePool> { + private: + explicit scoped_nsobject(NSAutoreleasePool* object = nil, + base::scoped_policy::OwnershipPolicy policy = + base::scoped_policy::ASSUME); + DISALLOW_COPY_AND_ASSIGN(scoped_nsobject); +}; +#endif // BASE_MEMORY_SCOPED_NSOBJECT_H_
diff --git a/src/base/memory/scoped_nsobject_unittest.mm b/src/base/memory/scoped_nsobject_unittest.mm new file mode 100644 index 0000000..377a3de --- /dev/null +++ b/src/base/memory/scoped_nsobject_unittest.mm
@@ -0,0 +1,86 @@ +// 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 <vector> + +#include "base/basictypes.h" +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/memory/scoped_nsobject.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +TEST(ScopedNSObjectTest, ScopedNSObject) { + scoped_nsobject<NSObject> p1([[NSObject alloc] init]); + ASSERT_TRUE(p1.get()); + ASSERT_EQ(1u, [p1 retainCount]); + scoped_nsobject<NSObject> p2(p1); + ASSERT_EQ(p1.get(), p2.get()); + ASSERT_EQ(2u, [p1 retainCount]); + p2.reset(); + ASSERT_EQ(nil, p2.get()); + ASSERT_EQ(1u, [p1 retainCount]); + { + scoped_nsobject<NSObject> p3 = p1; + ASSERT_EQ(p1.get(), p3.get()); + ASSERT_EQ(2u, [p1 retainCount]); + p3 = p1; + ASSERT_EQ(p1.get(), p3.get()); + ASSERT_EQ(2u, [p1 retainCount]); + } + ASSERT_EQ(1u, [p1 retainCount]); + scoped_nsobject<NSObject> p4(p1.get(), base::scoped_policy::RETAIN); + ASSERT_EQ(2u, [p1 retainCount]); + ASSERT_TRUE(p1 == p1.get()); + ASSERT_TRUE(p1 == p1); + ASSERT_FALSE(p1 != p1); + ASSERT_FALSE(p1 != p1.get()); + scoped_nsobject<NSObject> p5([[NSObject alloc] init]); + ASSERT_TRUE(p1 != p5); + ASSERT_TRUE(p1 != p5.get()); + ASSERT_FALSE(p1 == p5); + ASSERT_FALSE(p1 == p5.get()); + + scoped_nsobject<NSObject> p6 = p1; + ASSERT_EQ(3u, [p6 retainCount]); + { + base::mac::ScopedNSAutoreleasePool pool; + p6.autorelease(); + ASSERT_EQ(nil, p6.get()); + ASSERT_EQ(3u, [p1 retainCount]); + } + ASSERT_EQ(2u, [p1 retainCount]); +} + +TEST(ScopedNSObjectTest, ScopedNSObjectInContainer) { + scoped_nsobject<id> p([[NSObject alloc] init]); + ASSERT_TRUE(p.get()); + ASSERT_EQ(1u, [p retainCount]); + { + std::vector<scoped_nsobject<id> > objects; + objects.push_back(p); + ASSERT_EQ(2u, [p retainCount]); + ASSERT_EQ(p.get(), objects[0].get()); + objects.push_back(scoped_nsobject<id>([[NSObject alloc] init])); + ASSERT_TRUE(objects[1].get()); + ASSERT_EQ(1u, [objects[1] retainCount]); + } + ASSERT_EQ(1u, [p retainCount]); +} + +TEST(ScopedNSObjectTest, ScopedNSObjectFreeFunctions) { + scoped_nsobject<id> p1([[NSObject alloc] init]); + id o1 = p1.get(); + ASSERT_TRUE(o1 == p1); + ASSERT_FALSE(o1 != p1); + scoped_nsobject<id> p2([[NSObject alloc] init]); + ASSERT_TRUE(o1 != p2); + ASSERT_FALSE(o1 == p2); + id o2 = p2.get(); + swap(p1, p2); + ASSERT_EQ(o2, p1.get()); + ASSERT_EQ(o1, p2.get()); +} + +} // namespace
diff --git a/src/base/memory/scoped_open_process.h b/src/base/memory/scoped_open_process.h new file mode 100644 index 0000000..93ba387 --- /dev/null +++ b/src/base/memory/scoped_open_process.h
@@ -0,0 +1,49 @@ +// 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. + +#ifndef BASE_MEMORY_SCOPED_OPEN_PROCESS_H_ +#define BASE_MEMORY_SCOPED_OPEN_PROCESS_H_ + +#include "base/process.h" +#include "base/process_util.h" + +namespace base { + +// A class that opens a process from its process id and closes it when the +// instance goes out of scope. +class ScopedOpenProcess { + public: + ScopedOpenProcess() : handle_(kNullProcessHandle) { + } + + // Automatically close the process. + ~ScopedOpenProcess() { + Close(); + } + + // Open a new process by pid. Closes any previously opened process (even if + // opening the new one fails). + bool Open(ProcessId pid) { + Close(); + return OpenProcessHandle(pid, &handle_); + } + + // Close the previously opened process. + void Close() { + if (handle_ == kNullProcessHandle) + return; + + CloseProcessHandle(handle_); + handle_ = kNullProcessHandle; + } + + ProcessHandle handle() const { return handle_; } + + private: + ProcessHandle handle_; + DISALLOW_COPY_AND_ASSIGN(ScopedOpenProcess); +}; +} // namespace base + +#endif // BASE_MEMORY_SCOPED_OPEN_PROCESS_H_
diff --git a/src/base/memory/scoped_policy.h b/src/base/memory/scoped_policy.h new file mode 100644 index 0000000..5dbf204 --- /dev/null +++ b/src/base/memory/scoped_policy.h
@@ -0,0 +1,25 @@ +// 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_MEMORY_SCOPED_POLICY_H_ +#define BASE_MEMORY_SCOPED_POLICY_H_ + +namespace base { +namespace scoped_policy { + +// Defines the ownership policy for a scoped object. +enum OwnershipPolicy { + // The scoped object takes ownership of an object by taking over an existing + // ownership claim. + ASSUME, + + // The scoped object will retain the the object and any initial ownership is + // not changed. + RETAIN +}; + +} // namespace scoped_policy +} // namespace base + +#endif // BASE_MEMORY_SCOPED_POLICY_H_
diff --git a/src/base/memory/scoped_ptr.h b/src/base/memory/scoped_ptr.h new file mode 100644 index 0000000..9ee780e --- /dev/null +++ b/src/base/memory/scoped_ptr.h
@@ -0,0 +1,537 @@ +// 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. + +// Scopers help you manage ownership of a pointer, helping you easily manage the +// a pointer within a scope, and automatically destroying the pointer at the +// end of a scope. There are two main classes you will use, which correspond +// to the operators new/delete and new[]/delete[]. +// +// Example usage (scoped_ptr): +// { +// scoped_ptr<Foo> foo(new Foo("wee")); +// } // foo goes out of scope, releasing the pointer with it. +// +// { +// scoped_ptr<Foo> foo; // No pointer managed. +// foo.reset(new Foo("wee")); // Now a pointer is managed. +// foo.reset(new Foo("wee2")); // Foo("wee") was destroyed. +// foo.reset(new Foo("wee3")); // Foo("wee2") was destroyed. +// foo->Method(); // Foo::Method() called. +// foo.get()->Method(); // Foo::Method() called. +// SomeFunc(foo.release()); // SomeFunc takes ownership, foo no longer +// // manages a pointer. +// foo.reset(new Foo("wee4")); // foo manages a pointer again. +// foo.reset(); // Foo("wee4") destroyed, foo no longer +// // manages a pointer. +// } // foo wasn't managing a pointer, so nothing was destroyed. +// +// Example usage (scoped_array): +// { +// scoped_array<Foo> foo(new Foo[100]); +// foo.get()->Method(); // Foo::Method on the 0th element. +// foo[10].Method(); // Foo::Method on the 10th element. +// } +// +// These scopers also implement part of the functionality of C++11 unique_ptr +// in that they are "movable but not copyable." You can use the scopers in +// the parameter and return types of functions to signify ownership transfer +// in to and out of a function. When calling a function that has a scoper +// as the argument type, it must be called with the result of an analogous +// scoper's Pass() function or another function that generates a temporary; +// passing by copy will NOT work. Here is an example using scoped_ptr: +// +// void TakesOwnership(scoped_ptr<Foo> arg) { +// // Do something with arg +// } +// scoped_ptr<Foo> CreateFoo() { +// // No need for calling Pass() because we are constructing a temporary +// // for the return value. +// return scoped_ptr<Foo>(new Foo("new")); +// } +// scoped_ptr<Foo> PassThru(scoped_ptr<Foo> arg) { +// return arg.Pass(); +// } +// +// { +// scoped_ptr<Foo> ptr(new Foo("yay")); // ptr manages Foo("yay"). +// TakesOwnership(ptr.Pass()); // ptr no longer owns Foo("yay"). +// scoped_ptr<Foo> ptr2 = CreateFoo(); // ptr2 owns the return Foo. +// scoped_ptr<Foo> ptr3 = // ptr3 now owns what was in ptr2. +// PassThru(ptr2.Pass()); // ptr2 is correspondingly NULL. +// } +// +// Notice that if you do not call Pass() when returning from PassThru(), or +// when invoking TakesOwnership(), the code will not compile because scopers +// are not copyable; they only implement move semantics which require calling +// the Pass() function to signify a destructive transfer of state. CreateFoo() +// 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>: +// +// scoped_ptr<Foo> foo(new Foo()); +// scoped_ptr<FooParent> parent = foo.Pass(); +// +// PassAs<>() should be used to upcast return value in return statement: +// +// scoped_ptr<Foo> CreateFoo() { +// scoped_ptr<FooChild> result(new FooChild()); +// return result.PassAs<Foo>(); +// } +// +// Note that PassAs<>() is implemented only for scoped_ptr, but not for +// scoped_array. This is because casting array pointers may not be safe. + +#ifndef BASE_MEMORY_SCOPED_PTR_H_ +#define BASE_MEMORY_SCOPED_PTR_H_ + +// This is an implementation designed to match the anticipated future TR2 +// implementation of the scoped_ptr class, and its closely-related brethren, +// scoped_array, scoped_ptr_malloc. + +#include <assert.h> +#include <stddef.h> +#include <stdlib.h> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/move.h" +#include "base/template_util.h" +#include "build/build_config.h" + +#if defined(OS_STARBOARD) +#include "starboard/memory.h" +#endif + +namespace base { + +namespace subtle { +class RefCountedBase; +class RefCountedThreadSafeBase; +} // namespace subtle + +namespace internal { + +template <typename T> struct IsNotRefCounted { + enum { + value = !base::is_convertible<T*, base::subtle::RefCountedBase*>::value && + !base::is_convertible<T*, base::subtle::RefCountedThreadSafeBase*>:: + value + }; +}; + +} // namespace internal +} // namespace base + +// A scoped_ptr<T> is like a T*, except that the destructor of scoped_ptr<T> +// automatically deletes the pointer it holds (if any). +// That is, scoped_ptr<T> owns the T object that it points to. +// Like a T*, a scoped_ptr<T> may hold either NULL or a pointer to a T object. +// Also like T*, scoped_ptr<T> is thread-compatible, and once you +// dereference it, you get the thread safety guarantees of T. +// +// The size of a scoped_ptr is small: +// sizeof(scoped_ptr<C>) == sizeof(C*) +template <class C> +class scoped_ptr { + MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) + + COMPILE_ASSERT(base::internal::IsNotRefCounted<C>::value, + C_is_refcounted_type_and_needs_scoped_refptr); + + public: + + // The element type + typedef C element_type; + + // Constructor. Defaults to initializing with NULL. + // There is no way to create an uninitialized scoped_ptr. + // The input parameter must be allocated with new. + explicit scoped_ptr(C* p = NULL) : ptr_(p) { } + + // The GHS compiler always chooses this copy constructor over the next one, + // so disable this to promote the more important and frequently used constr. +#if !defined(COMPILER_GHS) + // Constructor. Allows construction from a scoped_ptr rvalue for a + // convertible type. + template <typename U> + scoped_ptr(scoped_ptr<U> other) : ptr_(other.release()) { } +#endif + + // Constructor. Move constructor for C++03 move emulation of this type. + scoped_ptr(RValue rvalue) + : ptr_(rvalue.object->release()) { + } + + // Destructor. If there is a C object, delete it. + // We don't need to test ptr_ == NULL because C++ does that for us. + ~scoped_ptr() { + enum { type_must_be_complete = sizeof(C) }; + delete ptr_; + } + + // operator=. Allows assignment from a scoped_ptr rvalue for a convertible + // type. + template <typename U> + scoped_ptr& operator=(scoped_ptr<U> rhs) { + reset(rhs.release()); + return *this; + } + + // operator=. Move operator= for C++03 move emulation of this type. + scoped_ptr& operator=(RValue rhs) { + swap(*rhs->object); + return *this; + } + + // Reset. Deletes the current owned object, if any. + // Then takes ownership of a new object, if given. + // this->reset(this->get()) works. + void reset(C* p = NULL) { + if (p != ptr_) { + enum { type_must_be_complete = sizeof(C) }; + delete ptr_; + ptr_ = p; + } + } + + // Accessors to get the owned object. + // operator* and operator-> will assert() if there is no current object. + C& operator*() const { + assert(ptr_ != NULL); + return *ptr_; + } + C* operator->() const { + assert(ptr_ != NULL); + return ptr_; + } + C* get() const { return ptr_; } + + // Allow scoped_ptr<C> to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + typedef C* scoped_ptr::*Testable; + operator Testable() const { return ptr_ ? &scoped_ptr::ptr_ : NULL; } + + // Comparison operators. + // These return whether two scoped_ptr refer to the same object, not just to + // two different but equal objects. + bool operator==(C* p) const { return ptr_ == p; } + bool operator!=(C* p) const { return ptr_ != p; } + + // Swap two scoped pointers. + void swap(scoped_ptr& p2) { + C* tmp = ptr_; + ptr_ = p2.ptr_; + p2.ptr_ = tmp; + } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + C* release() WARN_UNUSED_RESULT { + C* retVal = ptr_; + ptr_ = NULL; + return retVal; + } + + template <typename PassAsType> + scoped_ptr<PassAsType> PassAs() { + return scoped_ptr<PassAsType>(release()); + } + + private: + C* ptr_; + + // Forbid comparison of scoped_ptr types. If C2 != C, it totally doesn't + // make sense, and if C2 == C, it still doesn't make sense because you should + // never have the same object owned by two different scoped_ptrs. + template <class C2> bool operator==(scoped_ptr<C2> const& p2) const; + template <class C2> bool operator!=(scoped_ptr<C2> const& p2) const; + +}; + +// Free functions +template <class C> +void swap(scoped_ptr<C>& p1, scoped_ptr<C>& p2) { + p1.swap(p2); +} + +template <class C> +bool operator==(C* p1, const scoped_ptr<C>& p2) { + return p1 == p2.get(); +} + +template <class C> +bool operator!=(C* p1, const scoped_ptr<C>& p2) { + return p1 != p2.get(); +} + +// scoped_array<C> is like scoped_ptr<C>, except that the caller must allocate +// with new [] and the destructor deletes objects with delete []. +// +// As with scoped_ptr<C>, a scoped_array<C> either points to an object +// or is NULL. A scoped_array<C> owns the object that it points to. +// scoped_array<T> is thread-compatible, and once you index into it, +// the returned objects have only the thread safety guarantees of T. +// +// Size: sizeof(scoped_array<C>) == sizeof(C*) +template <class C> +class scoped_array { + MOVE_ONLY_TYPE_FOR_CPP_03(scoped_array, RValue) + + public: + + // The element type + typedef C element_type; + + // Constructor. Defaults to initializing with NULL. + // There is no way to create an uninitialized scoped_array. + // The input parameter must be allocated with new []. + explicit scoped_array(C* p = NULL) : array_(p) { } + + // Constructor. Move constructor for C++03 move emulation of this type. + scoped_array(RValue rvalue) + : array_(rvalue.object->release()) { + } + + // Destructor. If there is a C object, delete it. + // We don't need to test ptr_ == NULL because C++ does that for us. + ~scoped_array() { + enum { type_must_be_complete = sizeof(C) }; + delete[] array_; + } + + // operator=. Move operator= for C++03 move emulation of this type. + scoped_array& operator=(RValue rhs) { + swap(*rhs.object); + return *this; + } + + // Reset. Deletes the current owned object, if any. + // Then takes ownership of a new object, if given. + // this->reset(this->get()) works. + void reset(C* p = NULL) { + if (p != array_) { + enum { type_must_be_complete = sizeof(C) }; + delete[] array_; + array_ = p; + } + } + + // Get one element of the current object. + // Will assert() if there is no current object, or index i is negative. + C& operator[](ptrdiff_t i) const { + assert(i >= 0); + assert(array_ != NULL); + return array_[i]; + } + + // Get a pointer to the zeroth element of the current object. + // If there is no current object, return NULL. + C* get() const { + return array_; + } + + // Allow scoped_array<C> to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + typedef C* scoped_array::*Testable; + operator Testable() const { return array_ ? &scoped_array::array_ : NULL; } + + // Comparison operators. + // These return whether two scoped_array refer to the same object, not just to + // two different but equal objects. + bool operator==(C* p) const { return array_ == p; } + bool operator!=(C* p) const { return array_ != p; } + + // Swap two scoped arrays. + void swap(scoped_array& p2) { + C* tmp = array_; + array_ = p2.array_; + p2.array_ = tmp; + } + + // Release an array. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + C* release() WARN_UNUSED_RESULT { + C* retVal = array_; + array_ = NULL; + return retVal; + } + + private: + C* array_; + + // Forbid comparison of different scoped_array types. + template <class C2> bool operator==(scoped_array<C2> const& p2) const; + template <class C2> bool operator!=(scoped_array<C2> const& p2) const; +}; + +// Free functions +template <class C> +void swap(scoped_array<C>& p1, scoped_array<C>& p2) { + p1.swap(p2); +} + +template <class C> +bool operator==(C* p1, const scoped_array<C>& p2) { + return p1 == p2.get(); +} + +template <class C> +bool operator!=(C* p1, const scoped_array<C>& p2) { + return p1 != p2.get(); +} + +// This class wraps the c library function free() in a class that can be +// passed as a template argument to scoped_ptr_malloc below. +class ScopedPtrMallocFree { + public: + inline void operator()(void* x) const { +#if defined(OS_STARBOARD) + SbMemoryFree(x); +#else + free(x); +#endif + } +}; + +// scoped_ptr_malloc<> is similar to scoped_ptr<>, but it accepts a +// second template argument, the functor used to free the object. + +template<class C, class FreeProc = ScopedPtrMallocFree> +class scoped_ptr_malloc { + MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr_malloc, RValue) + + public: + + // The element type + typedef C element_type; + + // Constructor. Defaults to initializing with NULL. + // There is no way to create an uninitialized scoped_ptr. + // The input parameter must be allocated with an allocator that matches the + // Free functor. For the default Free functor, this is malloc, calloc, or + // realloc. + explicit scoped_ptr_malloc(C* p = NULL): ptr_(p) {} + + // Constructor. Move constructor for C++03 move emulation of this type. + scoped_ptr_malloc(RValue rvalue) + : ptr_(rvalue.object->release()) { + } + + // Destructor. If there is a C object, call the Free functor. + ~scoped_ptr_malloc() { + reset(); + } + + // operator=. Move operator= for C++03 move emulation of this type. + scoped_ptr_malloc& operator=(RValue rhs) { + swap(*rhs.object); + return *this; + } + + // Reset. Calls the Free functor on the current owned object, if any. + // Then takes ownership of a new object, if given. + // this->reset(this->get()) works. + void reset(C* p = NULL) { + if (ptr_ != p) { + FreeProc free_proc; + free_proc(ptr_); + ptr_ = p; + } + } + + // Get the current object. + // operator* and operator-> will cause an assert() failure if there is + // no current object. + C& operator*() const { + assert(ptr_ != NULL); + return *ptr_; + } + + C* operator->() const { + assert(ptr_ != NULL); + return ptr_; + } + + C* get() const { + return ptr_; + } + + // Allow scoped_ptr_malloc<C> to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + typedef C* scoped_ptr_malloc::*Testable; + operator Testable() const { return ptr_ ? &scoped_ptr_malloc::ptr_ : NULL; } + + // Comparison operators. + // These return whether a scoped_ptr_malloc and a plain pointer refer + // to the same object, not just to two different but equal objects. + // For compatibility with the boost-derived implementation, these + // take non-const arguments. + bool operator==(C* p) const { + return ptr_ == p; + } + + bool operator!=(C* p) const { + return ptr_ != p; + } + + // Swap two scoped pointers. + void swap(scoped_ptr_malloc & b) { + C* tmp = b.ptr_; + b.ptr_ = ptr_; + ptr_ = tmp; + } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + C* release() WARN_UNUSED_RESULT { + C* tmp = ptr_; + ptr_ = NULL; + return tmp; + } + + private: + C* ptr_; + + // no reason to use these: each scoped_ptr_malloc should have its own object + template <class C2, class GP> + bool operator==(scoped_ptr_malloc<C2, GP> const& p) const; + template <class C2, class GP> + bool operator!=(scoped_ptr_malloc<C2, GP> const& p) const; +}; + +template<class C, class FP> inline +void swap(scoped_ptr_malloc<C, FP>& a, scoped_ptr_malloc<C, FP>& b) { + a.swap(b); +} + +template<class C, class FP> inline +bool operator==(C* p, const scoped_ptr_malloc<C, FP>& b) { + return p == b.get(); +} + +template<class C, class FP> inline +bool operator!=(C* p, const scoped_ptr_malloc<C, FP>& b) { + return p != b.get(); +} + +// A function to convert T* into scoped_ptr<T> +// Doing e.g. make_scoped_ptr(new FooBarBaz<type>(arg)) is a shorter notation +// for scoped_ptr<FooBarBaz<type> >(new FooBarBaz<type>(arg)) +template <typename T> +scoped_ptr<T> make_scoped_ptr(T* ptr) { + return scoped_ptr<T>(ptr); +} + +#endif // BASE_MEMORY_SCOPED_PTR_H_
diff --git a/src/base/memory/scoped_ptr_unittest.cc b/src/base/memory/scoped_ptr_unittest.cc new file mode 100644 index 0000000..2a1b455 --- /dev/null +++ b/src/base/memory/scoped_ptr_unittest.cc
@@ -0,0 +1,366 @@ +// 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/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Used to test depth subtyping. +class ConDecLoggerParent { + public: + virtual ~ConDecLoggerParent() {} + + virtual void SetPtr(int* ptr) = 0; + + virtual int SomeMeth(int x) const = 0; +}; + +class ConDecLogger : public ConDecLoggerParent { + public: + ConDecLogger() : ptr_(NULL) { } + explicit ConDecLogger(int* ptr) { SetPtr(ptr); } + virtual ~ConDecLogger() { --*ptr_; } + + virtual void SetPtr(int* ptr) OVERRIDE { ptr_ = ptr; ++*ptr_; } + + virtual int SomeMeth(int x) const OVERRIDE { return x; } + + private: + int* ptr_; + + DISALLOW_COPY_AND_ASSIGN(ConDecLogger); +}; + +scoped_ptr<ConDecLogger> PassThru(scoped_ptr<ConDecLogger> logger) { + return logger.Pass(); +} + +void GrabAndDrop(scoped_ptr<ConDecLogger> logger) { +} + +// Do not delete this function! It's existence is to test that you can +// return a temporarily constructed version of the scoper. +scoped_ptr<ConDecLogger> TestReturnOfType(int* constructed) { + return scoped_ptr<ConDecLogger>(new ConDecLogger(constructed)); +} + +scoped_ptr<ConDecLoggerParent> UpcastUsingPassAs( + scoped_ptr<ConDecLogger> object) { + return object.PassAs<ConDecLoggerParent>(); +} + +} // namespace + +TEST(ScopedPtrTest, ScopedPtr) { + int constructed = 0; + + { + scoped_ptr<ConDecLogger> scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + EXPECT_EQ(10, scoper->SomeMeth(10)); + EXPECT_EQ(10, scoper.get()->SomeMeth(10)); + EXPECT_EQ(10, (*scoper).SomeMeth(10)); + } + EXPECT_EQ(0, constructed); + + // Test reset() and release() + { + scoped_ptr<ConDecLogger> scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoper.reset(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoper.reset(); + EXPECT_EQ(0, constructed); + EXPECT_FALSE(scoper.get()); + + scoper.reset(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + ConDecLogger* take = scoper.release(); + EXPECT_EQ(1, constructed); + EXPECT_FALSE(scoper.get()); + delete take; + EXPECT_EQ(0, constructed); + + scoper.reset(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Test swap(), == and != + { + scoped_ptr<ConDecLogger> scoper1; + scoped_ptr<ConDecLogger> scoper2; + EXPECT_TRUE(scoper1 == scoper2.get()); + EXPECT_FALSE(scoper1 != scoper2.get()); + + ConDecLogger* logger = new ConDecLogger(&constructed); + scoper1.reset(logger); + EXPECT_EQ(logger, scoper1.get()); + EXPECT_FALSE(scoper2.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + + scoper2.swap(scoper1); + EXPECT_EQ(logger, scoper2.get()); + EXPECT_FALSE(scoper1.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + } + EXPECT_EQ(0, constructed); +} + +TEST(ScopedPtrTest, ScopedPtrDepthSubtyping) { + int constructed = 0; + +#if !defined(COMPILER_GHS) + // Test construction from a scoped_ptr to a derived class. + { + scoped_ptr<ConDecLogger> scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoped_ptr<ConDecLoggerParent> scoper_parent(scoper.Pass()); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper_parent.get()); + EXPECT_FALSE(scoper.get()); + + EXPECT_EQ(10, scoper_parent->SomeMeth(10)); + EXPECT_EQ(10, scoper_parent.get()->SomeMeth(10)); + EXPECT_EQ(10, (*scoper_parent).SomeMeth(10)); + } + EXPECT_EQ(0, constructed); +#endif + + // Test assignment from a scoped_ptr to a derived class. + { + scoped_ptr<ConDecLogger> scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoped_ptr<ConDecLoggerParent> scoper_parent; + scoper_parent = scoper.Pass(); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper_parent.get()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); + +#if !defined(COMPILER_GHS) + // For the GHS compiler we have had to disable depth subtyping constructor, + // since that interferes with the move constructor. Luckily, we don't use + // the subtyping constructor anywhere in chromium, yet. + // Test construction of a scoped_ptr with an additional const annotation. + { + scoped_ptr<ConDecLogger> scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoped_ptr<const ConDecLogger> scoper_const(scoper.Pass()); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper_const.get()); + EXPECT_FALSE(scoper.get()); + + EXPECT_EQ(10, scoper_const->SomeMeth(10)); + EXPECT_EQ(10, scoper_const.get()->SomeMeth(10)); + EXPECT_EQ(10, (*scoper_const).SomeMeth(10)); + } + EXPECT_EQ(0, constructed); +#endif + + // Test assignment to a scoped_ptr with an additional const annotation. + { + scoped_ptr<ConDecLogger> scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoped_ptr<const ConDecLogger> scoper_const; + scoper_const = scoper.Pass(); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper_const.get()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); +} + +TEST(ScopedPtrTest, ScopedArray) { + static const int kNumLoggers = 12; + + int constructed = 0; + + { + scoped_array<ConDecLogger> scoper(new ConDecLogger[kNumLoggers]); + EXPECT_TRUE(scoper.get()); + EXPECT_EQ(&scoper[0], scoper.get()); + for (int i = 0; i < kNumLoggers; ++i) { + scoper[i].SetPtr(&constructed); + } + EXPECT_EQ(12, constructed); + + EXPECT_EQ(10, scoper.get()->SomeMeth(10)); + EXPECT_EQ(10, scoper[2].SomeMeth(10)); + } + EXPECT_EQ(0, constructed); + + // Test reset() and release() + { + scoped_array<ConDecLogger> scoper; + EXPECT_FALSE(scoper.get()); + EXPECT_FALSE(scoper.release()); + EXPECT_FALSE(scoper.get()); + scoper.reset(); + EXPECT_FALSE(scoper.get()); + + scoper.reset(new ConDecLogger[kNumLoggers]); + for (int i = 0; i < kNumLoggers; ++i) { + scoper[i].SetPtr(&constructed); + } + EXPECT_EQ(12, constructed); + scoper.reset(); + EXPECT_EQ(0, constructed); + + scoper.reset(new ConDecLogger[kNumLoggers]); + for (int i = 0; i < kNumLoggers; ++i) { + scoper[i].SetPtr(&constructed); + } + EXPECT_EQ(12, constructed); + ConDecLogger* ptr = scoper.release(); + EXPECT_EQ(12, constructed); + delete[] ptr; + EXPECT_EQ(0, constructed); + } + EXPECT_EQ(0, constructed); + + // Test swap(), == and != + { + scoped_array<ConDecLogger> scoper1; + scoped_array<ConDecLogger> scoper2; + EXPECT_TRUE(scoper1 == scoper2.get()); + EXPECT_FALSE(scoper1 != scoper2.get()); + + ConDecLogger* loggers = new ConDecLogger[kNumLoggers]; + for (int i = 0; i < kNumLoggers; ++i) { + loggers[i].SetPtr(&constructed); + } + scoper1.reset(loggers); + EXPECT_EQ(loggers, scoper1.get()); + EXPECT_FALSE(scoper2.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + + scoper2.swap(scoper1); + EXPECT_EQ(loggers, scoper2.get()); + EXPECT_FALSE(scoper1.get()); + EXPECT_FALSE(scoper1 == scoper2.get()); + EXPECT_TRUE(scoper1 != scoper2.get()); + } + EXPECT_EQ(0, constructed); +} + +TEST(ScopedPtrTest, PassBehavior) { + int constructed = 0; + { + ConDecLogger* logger = new ConDecLogger(&constructed); + scoped_ptr<ConDecLogger> scoper(logger); + EXPECT_EQ(1, constructed); + + // Test Pass() with constructor; + scoped_ptr<ConDecLogger> scoper2(scoper.Pass()); + EXPECT_EQ(1, constructed); + + // Test Pass() with assignment; + scoped_ptr<ConDecLogger> scoper3; + scoper3 = scoper2.Pass(); + EXPECT_EQ(1, constructed); + EXPECT_FALSE(scoper.get()); + EXPECT_FALSE(scoper2.get()); + EXPECT_TRUE(scoper3.get()); + } + + // Test uncaught Pass() does not leak. + { + ConDecLogger* logger = new ConDecLogger(&constructed); + scoped_ptr<ConDecLogger> scoper(logger); + EXPECT_EQ(1, constructed); + + // Should auto-destruct logger by end of scope. + scoper.Pass(); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Test that passing to function which does nothing does not leak. + { + ConDecLogger* logger = new ConDecLogger(&constructed); + scoped_ptr<ConDecLogger> scoper(logger); + EXPECT_EQ(1, constructed); + + // Should auto-destruct logger by end of scope. + GrabAndDrop(scoper.Pass()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); +} + +TEST(ScopedPtrTest, ReturnTypeBehavior) { + int constructed = 0; + + // Test that we can return a scoped_ptr. + { + ConDecLogger* logger = new ConDecLogger(&constructed); + scoped_ptr<ConDecLogger> scoper(logger); + EXPECT_EQ(1, constructed); + + PassThru(scoper.Pass()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Test uncaught return type not leak. + { + ConDecLogger* logger = new ConDecLogger(&constructed); + scoped_ptr<ConDecLogger> scoper(logger); + EXPECT_EQ(1, constructed); + + // Should auto-destruct logger by end of scope. + PassThru(scoper.Pass()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); + + // Call TestReturnOfType() so the compiler doesn't warn for an unused + // function. + { + TestReturnOfType(&constructed); + } + EXPECT_EQ(0, constructed); +} + +TEST(ScopedPtrTest, PassAs) { + int constructed = 0; + { + scoped_ptr<ConDecLogger> scoper(new ConDecLogger(&constructed)); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper.get()); + + scoped_ptr<ConDecLoggerParent> scoper_parent; + scoper_parent = UpcastUsingPassAs(scoper.Pass()); + EXPECT_EQ(1, constructed); + EXPECT_TRUE(scoper_parent.get()); + EXPECT_FALSE(scoper.get()); + } + EXPECT_EQ(0, constructed); +} + +// TODO scoped_ptr_malloc
diff --git a/src/base/memory/scoped_ptr_unittest.nc b/src/base/memory/scoped_ptr_unittest.nc new file mode 100644 index 0000000..30a332e --- /dev/null +++ b/src/base/memory/scoped_ptr_unittest.nc
@@ -0,0 +1,35 @@ +// 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/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/ref_counted.h" + +namespace { + +class Parent { +}; + +class Child : public Parent { +}; + +class RefCountedClass : public base::RefCountedThreadSafe<RefCountedClass> { +}; + +} // namespace + +#if defined(NCTEST_NO_PASSAS_DOWNCAST) // [r"invalid conversion from"] + +scoped_ptr<Child> DowncastUsingPassAs(scoped_ptr<Parent> object) { + return object.PassAs<Child>(); +} + +#elif defined(NCTEST_NO_REF_COUNTED_SCOPED_PTR) // [r"creating array with negative size"] + +// scoped_ptr<> should not work for ref-counted objects. +void WontCompile() { + scoped_ptr<RefCountedClass> x; +} + +#endif
diff --git a/src/base/memory/scoped_vector.h b/src/base/memory/scoped_vector.h new file mode 100644 index 0000000..90a1f7d --- /dev/null +++ b/src/base/memory/scoped_vector.h
@@ -0,0 +1,130 @@ +// 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_MEMORY_SCOPED_VECTOR_H_ +#define BASE_MEMORY_SCOPED_VECTOR_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/move.h" +#include "base/stl_util.h" + +// ScopedVector wraps a vector deleting the elements from its +// destructor. +template <class T> +class ScopedVector { + MOVE_ONLY_TYPE_FOR_CPP_03(ScopedVector, RValue) + + public: + typedef typename std::vector<T*>::allocator_type allocator_type; + typedef typename std::vector<T*>::size_type size_type; + typedef typename std::vector<T*>::difference_type difference_type; + typedef typename std::vector<T*>::pointer pointer; + typedef typename std::vector<T*>::const_pointer const_pointer; + typedef typename std::vector<T*>::reference reference; + typedef typename std::vector<T*>::const_reference const_reference; + typedef typename std::vector<T*>::value_type value_type; + typedef typename std::vector<T*>::iterator iterator; + typedef typename std::vector<T*>::const_iterator const_iterator; + typedef typename std::vector<T*>::reverse_iterator reverse_iterator; + typedef typename std::vector<T*>::const_reverse_iterator + const_reverse_iterator; + + ScopedVector() {} + ~ScopedVector() { clear(); } + ScopedVector(RValue other) { swap(*other.object); } + + ScopedVector& operator=(RValue rhs) { + swap(*rhs.object); + return *this; + } + + T*& operator[](size_t index) { return v_[index]; } + const T* operator[](size_t index) const { return v_[index]; } + + bool empty() const { return v_.empty(); } + size_t size() const { return v_.size(); } + + reverse_iterator rbegin() { return v_.rbegin(); } + const_reverse_iterator rbegin() const { return v_.rbegin(); } + reverse_iterator rend() { return v_.rend(); } + const_reverse_iterator rend() const { return v_.rend(); } + + iterator begin() { return v_.begin(); } + const_iterator begin() const { return v_.begin(); } + iterator end() { return v_.end(); } + const_iterator end() const { return v_.end(); } + + const_reference front() const { return v_.front(); } + reference front() { return v_.front(); } + const_reference back() const { return v_.back(); } + reference back() { return v_.back(); } + + void push_back(T* elem) { v_.push_back(elem); } + + std::vector<T*>& get() { return v_; } + const std::vector<T*>& get() const { return v_; } + void swap(std::vector<T*>& other) { v_.swap(other); } + void swap(ScopedVector<T>& other) { v_.swap(other.v_); } + void release(std::vector<T*>* out) { + out->swap(v_); + v_.clear(); + } + + void reserve(size_t capacity) { v_.reserve(capacity); } + + // Resize, deleting elements in the disappearing range if we are shrinking. + void resize(size_t new_size) { + if (v_.size() > new_size) + STLDeleteContainerPointers(v_.begin() + new_size, v_.end()); + v_.resize(new_size); + } + + template<typename InputIterator> + void assign(InputIterator begin, InputIterator end) { + v_.assign(begin, end); + } + + void clear() { STLDeleteElements(&v_); } + + // Like |clear()|, but doesn't delete any elements. + void weak_clear() { v_.clear(); } + + // Lets the ScopedVector take ownership of |x|. + iterator insert(iterator position, T* x) { + return v_.insert(position, x); + } + + // Lets the ScopedVector take ownership of elements in [first,last). + template<typename InputIterator> + void insert(iterator position, InputIterator first, InputIterator last) { + v_.insert(position, first, last); + } + + iterator erase(iterator position) { + delete *position; + return v_.erase(position); + } + + iterator erase(iterator first, iterator last) { + STLDeleteContainerPointers(first, last); + return v_.erase(first, last); + } + + // Like |erase()|, but doesn't delete the element at |position|. + iterator weak_erase(iterator position) { + return v_.erase(position); + } + + // Like |erase()|, but doesn't delete the elements in [first, last). + iterator weak_erase(iterator first, iterator last) { + return v_.erase(first, last); + } + + private: + std::vector<T*> v_; +}; + +#endif // BASE_MEMORY_SCOPED_VECTOR_H_
diff --git a/src/base/memory/scoped_vector_unittest.cc b/src/base/memory/scoped_vector_unittest.cc new file mode 100644 index 0000000..a91776a --- /dev/null +++ b/src/base/memory/scoped_vector_unittest.cc
@@ -0,0 +1,301 @@ +// 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/memory/scoped_vector.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// The LifeCycleObject notifies its Observer upon construction & destruction. +class LifeCycleObject { + public: + class Observer { + public: + virtual void OnLifeCycleConstruct(LifeCycleObject* o) = 0; + virtual void OnLifeCycleDestroy(LifeCycleObject* o) = 0; + + protected: + virtual ~Observer() {} + }; + + ~LifeCycleObject() { + observer_->OnLifeCycleDestroy(this); + } + + private: + friend class LifeCycleWatcher; + + explicit LifeCycleObject(Observer* observer) + : observer_(observer) { + observer_->OnLifeCycleConstruct(this); + } + + Observer* observer_; + + DISALLOW_COPY_AND_ASSIGN(LifeCycleObject); +}; + +// The life cycle states we care about for the purposes of testing ScopedVector +// against objects. +enum LifeCycleState { + LC_INITIAL, + LC_CONSTRUCTED, + LC_DESTROYED, +}; + +// Because we wish to watch the life cycle of an object being constructed and +// destroyed, and further wish to test expectations against the state of that +// object, we cannot save state in that object itself. Instead, we use this +// pairing of the watcher, which observes the object and notifies of +// construction & destruction. Since we also may be testing assumptions about +// things not getting freed, this class also acts like a scoping object and +// deletes the |constructed_life_cycle_object_|, if any when the +// LifeCycleWatcher is destroyed. To keep this simple, the only expected state +// changes are: +// INITIAL -> CONSTRUCTED -> DESTROYED. +// Anything more complicated than that should start another test. +class LifeCycleWatcher : public LifeCycleObject::Observer { + public: + LifeCycleWatcher() + : life_cycle_state_(LC_INITIAL), + constructed_life_cycle_object_(NULL) {} + virtual ~LifeCycleWatcher() {} + + // Assert INITIAL -> CONSTRUCTED and no LifeCycleObject associated with this + // LifeCycleWatcher. + virtual void OnLifeCycleConstruct(LifeCycleObject* object) OVERRIDE { + ASSERT_EQ(LC_INITIAL, life_cycle_state_); + ASSERT_EQ(NULL, constructed_life_cycle_object_.get()); + life_cycle_state_ = LC_CONSTRUCTED; + constructed_life_cycle_object_.reset(object); + } + + // Assert CONSTRUCTED -> DESTROYED and the |object| being destroyed is the + // same one we saw constructed. + virtual void OnLifeCycleDestroy(LifeCycleObject* object) OVERRIDE { + ASSERT_EQ(LC_CONSTRUCTED, life_cycle_state_); + LifeCycleObject* constructed_life_cycle_object = + constructed_life_cycle_object_.release(); + ASSERT_EQ(constructed_life_cycle_object, object); + life_cycle_state_ = LC_DESTROYED; + } + + LifeCycleState life_cycle_state() const { return life_cycle_state_; } + + // Factory method for creating a new LifeCycleObject tied to this + // LifeCycleWatcher. + LifeCycleObject* NewLifeCycleObject() { + return new LifeCycleObject(this); + } + + // Returns true iff |object| is the same object that this watcher is tracking. + bool IsWatching(LifeCycleObject* object) const { + return object == constructed_life_cycle_object_.get(); + } + + private: + LifeCycleState life_cycle_state_; + scoped_ptr<LifeCycleObject> constructed_life_cycle_object_; + + DISALLOW_COPY_AND_ASSIGN(LifeCycleWatcher); +}; + +TEST(ScopedVectorTest, LifeCycleWatcher) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + LifeCycleObject* object = watcher.NewLifeCycleObject(); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + delete object; + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +TEST(ScopedVectorTest, Clear) { + 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.clear(); + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); + EXPECT_TRUE(scoped_vector.empty()); +} + +TEST(ScopedVectorTest, WeakClear) { + 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.weak_clear(); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(scoped_vector.empty()); +} + +TEST(ScopedVectorTest, ResizeShrink) { + LifeCycleWatcher first_watcher; + EXPECT_EQ(LC_INITIAL, first_watcher.life_cycle_state()); + LifeCycleWatcher second_watcher; + EXPECT_EQ(LC_INITIAL, second_watcher.life_cycle_state()); + ScopedVector<LifeCycleObject> scoped_vector; + + scoped_vector.push_back(first_watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, first_watcher.life_cycle_state()); + EXPECT_EQ(LC_INITIAL, second_watcher.life_cycle_state()); + EXPECT_TRUE(first_watcher.IsWatching(scoped_vector[0])); + EXPECT_FALSE(second_watcher.IsWatching(scoped_vector[0])); + + scoped_vector.push_back(second_watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, first_watcher.life_cycle_state()); + EXPECT_EQ(LC_CONSTRUCTED, second_watcher.life_cycle_state()); + EXPECT_FALSE(first_watcher.IsWatching(scoped_vector[1])); + EXPECT_TRUE(second_watcher.IsWatching(scoped_vector[1])); + + // Test that shrinking a vector deletes elements in the disappearing range. + scoped_vector.resize(1); + EXPECT_EQ(LC_CONSTRUCTED, first_watcher.life_cycle_state()); + EXPECT_EQ(LC_DESTROYED, second_watcher.life_cycle_state()); + EXPECT_EQ(1u, scoped_vector.size()); + EXPECT_TRUE(first_watcher.IsWatching(scoped_vector[0])); +} + +TEST(ScopedVectorTest, ResizeGrow) { + 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.resize(5); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + ASSERT_EQ(5u, scoped_vector.size()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector[0])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[1])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[2])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[3])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[4])); +} + +TEST(ScopedVectorTest, Scope) { + 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())); + } + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +TEST(ScopedVectorTest, MoveConstruct) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + { + ScopedVector<LifeCycleObject> scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_FALSE(scoped_vector.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + + ScopedVector<LifeCycleObject> scoped_vector_copy(scoped_vector.Pass()); + EXPECT_TRUE(scoped_vector.empty()); + EXPECT_FALSE(scoped_vector_copy.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector_copy.back())); + + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + } + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +TEST(ScopedVectorTest, MoveAssign) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + { + ScopedVector<LifeCycleObject> scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + ScopedVector<LifeCycleObject> scoped_vector_assign; + EXPECT_FALSE(scoped_vector.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + + scoped_vector_assign = scoped_vector.Pass(); + EXPECT_TRUE(scoped_vector.empty()); + EXPECT_FALSE(scoped_vector_assign.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector_assign.back())); + + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + } + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +class DeleteCounter { + public: + explicit DeleteCounter(int* deletes) + : deletes_(deletes) { + } + + ~DeleteCounter() { + (*deletes_)++; + } + + void VoidMethod0() {} + + private: + int* const deletes_; + + DISALLOW_COPY_AND_ASSIGN(DeleteCounter); +}; + +template <typename T> +ScopedVector<T> PassThru(ScopedVector<T> scoper) { + return scoper.Pass(); +} + +TEST(ScopedVectorTest, Passed) { + int deletes = 0; + ScopedVector<DeleteCounter> deleter_vector; + deleter_vector.push_back(new DeleteCounter(&deletes)); + EXPECT_EQ(0, deletes); + base::Callback<ScopedVector<DeleteCounter>(void)> callback = + base::Bind(&PassThru<DeleteCounter>, base::Passed(&deleter_vector)); + EXPECT_EQ(0, deletes); + ScopedVector<DeleteCounter> result = callback.Run(); + EXPECT_EQ(0, deletes); + result.clear(); + EXPECT_EQ(1, deletes); +}; + +TEST(ScopedVectorTest, InsertRange) { + LifeCycleWatcher watchers[5]; + + std::vector<LifeCycleObject*> vec; + for(LifeCycleWatcher* it = watchers; it != watchers + arraysize(watchers); + ++it) { + EXPECT_EQ(LC_INITIAL, it->life_cycle_state()); + vec.push_back(it->NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); + } + // Start scope for ScopedVector. + { + ScopedVector<LifeCycleObject> scoped_vector; + scoped_vector.insert(scoped_vector.end(), vec.begin() + 1, vec.begin() + 3); + for(LifeCycleWatcher* it = watchers; it != watchers + arraysize(watchers); + ++it) + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); + } + for(LifeCycleWatcher* it = watchers; it != watchers + 1; ++it) + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); + for(LifeCycleWatcher* it = watchers + 1; it != watchers + 3; ++it) + EXPECT_EQ(LC_DESTROYED, it->life_cycle_state()); + for(LifeCycleWatcher* it = watchers + 3; it != watchers + arraysize(watchers); + ++it) + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); +} + +} // namespace
diff --git a/src/base/memory/singleton.cc b/src/base/memory/singleton.cc new file mode 100644 index 0000000..ee5e58d --- /dev/null +++ b/src/base/memory/singleton.cc
@@ -0,0 +1,31 @@ +// 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/memory/singleton.h" +#include "base/threading/platform_thread.h" + +namespace base { +namespace internal { + +subtle::AtomicWord WaitForInstance(subtle::AtomicWord* instance) { + // Handle the race. Another thread beat us and either: + // - Has the object in BeingCreated state + // - Already has the object created... + // We know value != NULL. It could be kBeingCreatedMarker, or a valid ptr. + // Unless your constructor can be very time consuming, it is very unlikely + // to hit this race. When it does, we just spin and yield the thread until + // the object has been created. + subtle::AtomicWord value; + while (true) { + value = subtle::NoBarrier_Load(instance); + if (value != kBeingCreatedMarker) + break; + PlatformThread::YieldCurrentThread(); + } + return value; +} + +} // namespace internal +} // namespace base +
diff --git a/src/base/memory/singleton.h b/src/base/memory/singleton.h new file mode 100644 index 0000000..0d4fc89 --- /dev/null +++ b/src/base/memory/singleton.h
@@ -0,0 +1,285 @@ +// 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. + +// PLEASE READ: Do you really need a singleton? +// +// Singletons make it hard to determine the lifetime of an object, which can +// lead to buggy code and spurious crashes. +// +// Instead of adding another singleton into the mix, try to identify either: +// a) An existing singleton that can manage your object's lifetime +// b) Locations where you can deterministically create the object and pass +// into other objects +// +// If you absolutely need a singleton, please keep them as trivial as possible +// and ideally a leaf dependency. Singletons get problematic when they attempt +// to do too much in their destructor or have circular dependencies. + +#ifndef BASE_MEMORY_SINGLETON_H_ +#define BASE_MEMORY_SINGLETON_H_ + +#include "base/at_exit.h" +#include "base/atomicops.h" +#include "base/base_export.h" +#include "base/memory/aligned_memory.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/thread_restrictions.h" + +namespace base { +namespace internal { + +// Our AtomicWord doubles as a spinlock, where a value of +// kBeingCreatedMarker means the spinlock is being held for creation. +static const subtle::AtomicWord kBeingCreatedMarker = 1; + +// We pull out some of the functionality into a non-templated function, so that +// we can implement the more complicated pieces out of line in the .cc file. +BASE_EXPORT subtle::AtomicWord WaitForInstance(subtle::AtomicWord* instance); + +} // namespace internal +} // namespace base + +// TODO(joth): Move more of this file into namespace base + +// Default traits for Singleton<Type>. Calls operator new and operator delete on +// the object. Registers automatic deletion at process exit. +// Overload if you need arguments or another memory allocation function. +template<typename Type> +struct DefaultSingletonTraits { + // Allocates the object. + static Type* New() { + // The parenthesis is very important here; it forces POD type + // initialization. + return new Type(); + } + + // Destroys the object. + static void Delete(Type* x) { + delete x; + } + + // Set to true to automatically register deletion of the object on process + // exit. See below for the required call that makes this happen. + static const bool kRegisterAtExit = true; + + // Set to false to disallow access on a non-joinable thread. This is + // different from kRegisterAtExit because StaticMemorySingletonTraits allows + // access on non-joinable threads, and gracefully handles this. + static const bool kAllowedToAccessOnNonjoinableThread = false; +}; + + +// Alternate traits for use with the Singleton<Type>. Identical to +// DefaultSingletonTraits except that the Singleton will not be cleaned up +// at exit. +template<typename Type> +struct LeakySingletonTraits : public DefaultSingletonTraits<Type> { + static const bool kRegisterAtExit = false; + static const bool kAllowedToAccessOnNonjoinableThread = true; +}; + + +// Alternate traits for use with the Singleton<Type>. Allocates memory +// for the singleton instance from a static buffer. The singleton will +// be cleaned up at exit, but can't be revived after destruction unless +// the Resurrect() method is called. +// +// This is useful for a certain category of things, notably logging and +// tracing, where the singleton instance is of a type carefully constructed to +// be safe to access post-destruction. +// In logging and tracing you'll typically get stray calls at odd times, like +// during static destruction, thread teardown and the like, and there's a +// termination race on the heap-based singleton - e.g. if one thread calls +// get(), but then another thread initiates AtExit processing, the first thread +// may call into an object residing in unallocated memory. If the instance is +// allocated from the data segment, then this is survivable. +// +// The destructor is to deallocate system resources, in this case to unregister +// a callback the system will invoke when logging levels change. Note that +// this is also used in e.g. Chrome Frame, where you have to allow for the +// possibility of loading briefly into someone else's process space, and +// so leaking is not an option, as that would sabotage the state of your host +// process once you've unloaded. +template <typename Type> +struct StaticMemorySingletonTraits { + // WARNING: User has to deal with get() in the singleton class + // this is traits for returning NULL. + static Type* New() { + // Only constructs once and returns pointer; otherwise returns NULL. + if (base::subtle::NoBarrier_AtomicExchange(&dead_, 1)) + return NULL; + + return new(buffer_.void_data()) Type(); + } + + static void Delete(Type* p) { + if (p != NULL) + p->Type::~Type(); + } + + static const bool kRegisterAtExit = true; + static const bool kAllowedToAccessOnNonjoinableThread = true; + + // Exposed for unittesting. + static void Resurrect() { + base::subtle::NoBarrier_Store(&dead_, 0); + } + + private: + static base::AlignedMemory<sizeof(Type), ALIGNOF(Type)> buffer_; + // Signal the object was already deleted, so it is not revived. + static base::subtle::Atomic32 dead_; +}; + +template <typename Type> base::AlignedMemory<sizeof(Type), ALIGNOF(Type)> + StaticMemorySingletonTraits<Type>::buffer_; +template <typename Type> base::subtle::Atomic32 + StaticMemorySingletonTraits<Type>::dead_ = 0; + +// The Singleton<Type, Traits, DifferentiatingType> class manages a single +// instance of Type which will be created on first use and will be destroyed at +// normal process exit). The Trait::Delete function will not be called on +// abnormal process exit. +// +// DifferentiatingType is used as a key to differentiate two different +// singletons having the same memory allocation functions but serving a +// different purpose. This is mainly used for Locks serving different purposes. +// +// Example usage: +// +// In your header: +// template <typename T> struct DefaultSingletonTraits; +// class FooClass { +// public: +// static FooClass* GetInstance(); <-- See comment below on this. +// void Bar() { ... } +// private: +// FooClass() { ... } +// friend struct DefaultSingletonTraits<FooClass>; +// +// DISALLOW_COPY_AND_ASSIGN(FooClass); +// }; +// +// In your source file: +// #include "base/memory/singleton.h" +// FooClass* FooClass::GetInstance() { +// return Singleton<FooClass>::get(); +// } +// +// And to call methods on FooClass: +// FooClass::GetInstance()->Bar(); +// +// NOTE: The method accessing Singleton<T>::get() has to be named as GetInstance +// and it is important that FooClass::GetInstance() is not inlined in the +// header. This makes sure that when source files from multiple targets include +// this header they don't end up with different copies of the inlined code +// creating multiple copies of the singleton. +// +// Singleton<> has no non-static members and doesn't need to actually be +// instantiated. +// +// This class is itself thread-safe. The underlying Type must of course be +// thread-safe if you want to use it concurrently. Two parameters may be tuned +// depending on the user's requirements. +// +// Glossary: +// RAE = kRegisterAtExit +// +// On every platform, if Traits::RAE is true, the singleton will be destroyed at +// process exit. More precisely it uses base::AtExitManager which requires an +// object of this type to be instantiated. AtExitManager mimics the semantics +// of atexit() such as LIFO order but under Windows is safer to call. For more +// information see at_exit.h. +// +// If Traits::RAE is false, the singleton will not be freed at process exit, +// thus the singleton will be leaked if it is ever accessed. Traits::RAE +// shouldn't be false unless absolutely necessary. Remember that the heap where +// the object is allocated may be destroyed by the CRT anyway. +// +// Caveats: +// (a) Every call to get(), operator->() and operator*() incurs some overhead +// (16ns on my P4/2.8GHz) to check whether the object has already been +// initialized. You may wish to cache the result of get(); it will not +// change. +// +// (b) Your factory function must never throw an exception. This class is not +// exception-safe. +// +template <typename Type, + typename Traits = DefaultSingletonTraits<Type>, + typename DifferentiatingType = Type> +class Singleton { + private: + // Classes using the Singleton<T> pattern should declare a GetInstance() + // method and call Singleton::get() from within that. + friend Type* Type::GetInstance(); + + // Allow TraceLog tests to test tracing after OnExit. + friend class DeleteTraceLogForTesting; + + // This class is safe to be constructed and copy-constructed since it has no + // member. + + // Return a pointer to the one true instance of the class. + static Type* get() { +#ifndef NDEBUG + // Avoid making TLS lookup on release builds. + if (!Traits::kAllowedToAccessOnNonjoinableThread) + base::ThreadRestrictions::AssertSingletonAllowed(); +#endif + + base::subtle::AtomicWord value = base::subtle::NoBarrier_Load(&instance_); + if (value != 0 && value != base::internal::kBeingCreatedMarker) { + // See the corresponding HAPPENS_BEFORE below. + ANNOTATE_HAPPENS_AFTER(&instance_); + return reinterpret_cast<Type*>(value); + } + + // Object isn't created yet, maybe we will get to create it, let's try... + if (base::subtle::Acquire_CompareAndSwap( + &instance_, 0, base::internal::kBeingCreatedMarker) == 0) { + // instance_ was NULL and is now kBeingCreatedMarker. Only one thread + // will ever get here. Threads might be spinning on us, and they will + // stop right after we do this store. + Type* newval = Traits::New(); + + // This annotation helps race detectors recognize correct lock-less + // synchronization between different threads calling get(). + // See the corresponding HAPPENS_AFTER below and above. + ANNOTATE_HAPPENS_BEFORE(&instance_); + base::subtle::Release_Store( + &instance_, reinterpret_cast<base::subtle::AtomicWord>(newval)); + + if (newval != NULL && Traits::kRegisterAtExit) + base::AtExitManager::RegisterCallback(OnExit, NULL); + + return newval; + } + + // We hit a race. Wait for the other thread to complete it. + value = base::internal::WaitForInstance(&instance_); + + // See the corresponding HAPPENS_BEFORE above. + ANNOTATE_HAPPENS_AFTER(&instance_); + return reinterpret_cast<Type*>(value); + } + + // Adapter function for use with AtExit(). This should be called single + // threaded, so don't use atomic operations. + // Calling OnExit while singleton is in use by other threads is a mistake. + static void OnExit(void* /*unused*/) { + // AtExit should only ever be register after the singleton instance was + // created. We should only ever get here with a valid instance_ pointer. + Traits::Delete( + reinterpret_cast<Type*>(base::subtle::NoBarrier_Load(&instance_))); + instance_ = 0; + } + static base::subtle::AtomicWord instance_; +}; + +template <typename Type, typename Traits, typename DifferentiatingType> +base::subtle::AtomicWord Singleton<Type, Traits, DifferentiatingType>:: + instance_ = 0; + +#endif // BASE_MEMORY_SINGLETON_H_
diff --git a/src/base/memory/singleton_objc.h b/src/base/memory/singleton_objc.h new file mode 100644 index 0000000..6df3f77 --- /dev/null +++ b/src/base/memory/singleton_objc.h
@@ -0,0 +1,60 @@ +// 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. + +// Support for using the Singleton<T> pattern with Objective-C objects. A +// SingletonObjC is the same as a Singleton, except the default traits are +// appropriate for Objective-C objects. A typical Objective-C object of type +// NSExampleType can be maintained as a singleton and accessed with: +// +// NSExampleType* exampleSingleton = SingletonObjC<NSExampleType>::get(); +// +// The first time this is used, it will create exampleSingleton as the result +// of [[NSExampleType alloc] init]. Subsequent calls will return the same +// NSExampleType* object. The object will be released by calling +// -[NSExampleType release] when Singleton's atexit routines run +// (see singleton.h). +// +// For Objective-C objects initialized through means other than the +// no-parameter -init selector, DefaultSingletonObjCTraits may be extended +// as needed: +// +// struct FooSingletonTraits : public DefaultSingletonObjCTraits<Foo> { +// static Foo* New() { +// return [[Foo alloc] initWithName:@"selecty"]; +// } +// }; +// ... +// Foo* widgetSingleton = SingletonObjC<Foo, FooSingletonTraits>::get(); + +#ifndef BASE_MEMORY_SINGLETON_OBJC_H_ +#define BASE_MEMORY_SINGLETON_OBJC_H_ + +#import <Foundation/Foundation.h> +#include "base/memory/singleton.h" + +// Singleton traits usable to manage traditional Objective-C objects, which +// are instantiated by sending |alloc| and |init| messages, and are deallocated +// in a memory-managed environment when their retain counts drop to 0 by +// sending |release| messages. +template<typename Type> +struct DefaultSingletonObjCTraits : public DefaultSingletonTraits<Type> { + static Type* New() { + return [[Type alloc] init]; + } + + static void Delete(Type* object) { + [object release]; + } +}; + +// Exactly like Singleton, but without the DefaultSingletonObjCTraits as the +// default trait class. This makes it straightforward for Objective-C++ code +// to hold Objective-C objects as singletons. +template<typename Type, + typename Traits = DefaultSingletonObjCTraits<Type>, + typename DifferentiatingType = Type> +class SingletonObjC : public Singleton<Type, Traits, DifferentiatingType> { +}; + +#endif // BASE_MEMORY_SINGLETON_OBJC_H_
diff --git a/src/base/memory/singleton_unittest.cc b/src/base/memory/singleton_unittest.cc new file mode 100644 index 0000000..d9892cb --- /dev/null +++ b/src/base/memory/singleton_unittest.cc
@@ -0,0 +1,289 @@ +// 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/at_exit.h" +#include "base/file_util.h" +#include "base/memory/singleton.h" +#include "base/path_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +COMPILE_ASSERT(DefaultSingletonTraits<int>::kRegisterAtExit == true, a); + +typedef void (*CallbackFunc)(); + +class IntSingleton { + public: + static IntSingleton* GetInstance() { + return Singleton<IntSingleton>::get(); + } + + int value_; +}; + +class Init5Singleton { + public: + struct Trait; + + static Init5Singleton* GetInstance() { + return Singleton<Init5Singleton, Trait>::get(); + } + + int value_; +}; + +struct Init5Singleton::Trait : public DefaultSingletonTraits<Init5Singleton> { + static Init5Singleton* New() { + Init5Singleton* instance = new Init5Singleton(); + instance->value_ = 5; + return instance; + } +}; + +int* SingletonInt() { + return &IntSingleton::GetInstance()->value_; +} + +int* SingletonInt5() { + return &Init5Singleton::GetInstance()->value_; +} + +template <typename Type> +struct CallbackTrait : public DefaultSingletonTraits<Type> { + static void Delete(Type* instance) { + if (instance->callback_) + (instance->callback_)(); + DefaultSingletonTraits<Type>::Delete(instance); + } +}; + +class CallbackSingleton { + public: + CallbackSingleton() : callback_(NULL) { } + CallbackFunc callback_; +}; + +class CallbackSingletonWithNoLeakTrait : public CallbackSingleton { + public: + struct Trait : public CallbackTrait<CallbackSingletonWithNoLeakTrait> { }; + + CallbackSingletonWithNoLeakTrait() : CallbackSingleton() { } + + static CallbackSingletonWithNoLeakTrait* GetInstance() { + return Singleton<CallbackSingletonWithNoLeakTrait, Trait>::get(); + } +}; + +class CallbackSingletonWithLeakTrait : public CallbackSingleton { + public: + struct Trait : public CallbackTrait<CallbackSingletonWithLeakTrait> { + static const bool kRegisterAtExit = false; + }; + + CallbackSingletonWithLeakTrait() : CallbackSingleton() { } + + static CallbackSingletonWithLeakTrait* GetInstance() { + return Singleton<CallbackSingletonWithLeakTrait, Trait>::get(); + } +}; + +class CallbackSingletonWithStaticTrait : public CallbackSingleton { + public: + struct Trait; + + CallbackSingletonWithStaticTrait() : CallbackSingleton() { } + + static CallbackSingletonWithStaticTrait* GetInstance() { + return Singleton<CallbackSingletonWithStaticTrait, Trait>::get(); + } +}; + +struct CallbackSingletonWithStaticTrait::Trait + : public StaticMemorySingletonTraits<CallbackSingletonWithStaticTrait> { + static void Delete(CallbackSingletonWithStaticTrait* instance) { + if (instance->callback_) + (instance->callback_)(); + StaticMemorySingletonTraits<CallbackSingletonWithStaticTrait>::Delete( + instance); + } +}; + +template <class Type> +class AlignedTestSingleton { + public: + AlignedTestSingleton() {} + ~AlignedTestSingleton() {} + static AlignedTestSingleton* GetInstance() { + return Singleton<AlignedTestSingleton, + StaticMemorySingletonTraits<AlignedTestSingleton> >::get(); + } + + Type type_; +}; + + +void SingletonNoLeak(CallbackFunc CallOnQuit) { + CallbackSingletonWithNoLeakTrait::GetInstance()->callback_ = CallOnQuit; +} + +void SingletonLeak(CallbackFunc CallOnQuit) { + CallbackSingletonWithLeakTrait::GetInstance()->callback_ = CallOnQuit; +} + +CallbackFunc* GetLeakySingleton() { + return &CallbackSingletonWithLeakTrait::GetInstance()->callback_; +} + +void DeleteLeakySingleton() { + DefaultSingletonTraits<CallbackSingletonWithLeakTrait>::Delete( + CallbackSingletonWithLeakTrait::GetInstance()); +} + +void SingletonStatic(CallbackFunc CallOnQuit) { + CallbackSingletonWithStaticTrait::GetInstance()->callback_ = CallOnQuit; +} + +CallbackFunc* GetStaticSingleton() { + return &CallbackSingletonWithStaticTrait::GetInstance()->callback_; +} + +} // namespace + +class SingletonTest : public testing::Test { + public: + SingletonTest() {} + + virtual void SetUp() OVERRIDE { + non_leak_called_ = false; + leaky_called_ = false; + static_called_ = false; + } + + protected: + void VerifiesCallbacks() { + EXPECT_TRUE(non_leak_called_); + EXPECT_FALSE(leaky_called_); + EXPECT_TRUE(static_called_); + non_leak_called_ = false; + leaky_called_ = false; + static_called_ = false; + } + + void VerifiesCallbacksNotCalled() { + EXPECT_FALSE(non_leak_called_); + EXPECT_FALSE(leaky_called_); + EXPECT_FALSE(static_called_); + non_leak_called_ = false; + leaky_called_ = false; + static_called_ = false; + } + + static void CallbackNoLeak() { + non_leak_called_ = true; + } + + static void CallbackLeak() { + leaky_called_ = true; + } + + static void CallbackStatic() { + static_called_ = true; + } + + private: + static bool non_leak_called_; + static bool leaky_called_; + static bool static_called_; +}; + +bool SingletonTest::non_leak_called_ = false; +bool SingletonTest::leaky_called_ = false; +bool SingletonTest::static_called_ = false; + +TEST_F(SingletonTest, Basic) { + int* singleton_int; + int* singleton_int_5; + CallbackFunc* leaky_singleton; + CallbackFunc* static_singleton; + + { + base::ShadowingAtExitManager sem; + { + singleton_int = SingletonInt(); + } + // Ensure POD type initialization. + EXPECT_EQ(*singleton_int, 0); + *singleton_int = 1; + + EXPECT_EQ(singleton_int, SingletonInt()); + EXPECT_EQ(*singleton_int, 1); + + { + singleton_int_5 = SingletonInt5(); + } + // Is default initialized to 5. + EXPECT_EQ(*singleton_int_5, 5); + + SingletonNoLeak(&CallbackNoLeak); + SingletonLeak(&CallbackLeak); + SingletonStatic(&CallbackStatic); + static_singleton = GetStaticSingleton(); + leaky_singleton = GetLeakySingleton(); + EXPECT_TRUE(leaky_singleton); + } + + // Verify that only the expected callback has been called. + VerifiesCallbacks(); + // Delete the leaky singleton. + DeleteLeakySingleton(); + + // The static singleton can't be acquired post-atexit. + EXPECT_EQ(NULL, GetStaticSingleton()); + + { + base::ShadowingAtExitManager sem; + // Verifiy that the variables were reset. + { + singleton_int = SingletonInt(); + EXPECT_EQ(*singleton_int, 0); + } + { + singleton_int_5 = SingletonInt5(); + EXPECT_EQ(*singleton_int_5, 5); + } + { + // Resurrect the static singleton, and assert that it + // still points to the same (static) memory. + CallbackSingletonWithStaticTrait::Trait::Resurrect(); + EXPECT_EQ(GetStaticSingleton(), static_singleton); + } + } + // The leaky singleton shouldn't leak since SingletonLeak has not been called. + VerifiesCallbacksNotCalled(); +} + +#define EXPECT_ALIGNED(ptr, align) \ + EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(ptr) & (align - 1)) + +TEST_F(SingletonTest, Alignment) { + using base::AlignedMemory; + + // Create some static singletons with increasing sizes and alignment + // requirements. By ordering this way, the linker will need to do some work to + // ensure proper alignment of the static data. + AlignedTestSingleton<int32>* align4 = + AlignedTestSingleton<int32>::GetInstance(); + AlignedTestSingleton<AlignedMemory<32, 32> >* align32 = + AlignedTestSingleton<AlignedMemory<32, 32> >::GetInstance(); + AlignedTestSingleton<AlignedMemory<128, 128> >* align128 = + AlignedTestSingleton<AlignedMemory<128, 128> >::GetInstance(); + AlignedTestSingleton<AlignedMemory<4096, 4096> >* align4096 = + AlignedTestSingleton<AlignedMemory<4096, 4096> >::GetInstance(); + + EXPECT_ALIGNED(align4, 4); + EXPECT_ALIGNED(align32, 32); + EXPECT_ALIGNED(align128, 128); + EXPECT_ALIGNED(align4096, 4096); +}
diff --git a/src/base/memory/weak_ptr.cc b/src/base/memory/weak_ptr.cc new file mode 100644 index 0000000..9dec8fd --- /dev/null +++ b/src/base/memory/weak_ptr.cc
@@ -0,0 +1,73 @@ +// 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/memory/weak_ptr.h" + +namespace base { +namespace internal { + +WeakReference::Flag::Flag() : is_valid_(true) { +} + +void WeakReference::Flag::Invalidate() { + // The flag being invalidated with a single ref implies that there are no + // weak pointers in existence. Allow deletion on other thread in this case. + DCHECK(thread_checker_.CalledOnValidThread() || HasOneRef()); + is_valid_ = false; +} + +bool WeakReference::Flag::IsValid() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return is_valid_; +} + +WeakReference::Flag::~Flag() { +} + +WeakReference::WeakReference() { +} + +WeakReference::WeakReference(const Flag* flag) : flag_(flag) { +} + +WeakReference::~WeakReference() { +} + +bool WeakReference::is_valid() const { + return flag_ && flag_->IsValid(); +} + +WeakReferenceOwner::WeakReferenceOwner() { +} + +WeakReferenceOwner::~WeakReferenceOwner() { + Invalidate(); +} + +WeakReference WeakReferenceOwner::GetRef() const { + // We also want to reattach to the current thread if all previous references + // have gone away. + if (!HasRefs()) + flag_ = new WeakReference::Flag(); + return WeakReference(flag_); +} + +void WeakReferenceOwner::Invalidate() { + if (flag_) { + flag_->Invalidate(); + flag_ = NULL; + } +} + +WeakPtrBase::WeakPtrBase() { +} + +WeakPtrBase::~WeakPtrBase() { +} + +WeakPtrBase::WeakPtrBase(const WeakReference& ref) : ref_(ref) { +} + +} // namespace internal +} // namespace base
diff --git a/src/base/memory/weak_ptr.h b/src/base/memory/weak_ptr.h new file mode 100644 index 0000000..6dc93b7 --- /dev/null +++ b/src/base/memory/weak_ptr.h
@@ -0,0 +1,350 @@ +// 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. + +// Weak pointers help in cases where you have many objects referring back to a +// shared object and you wish for the lifetime of the shared object to not be +// bound to the lifetime of the referrers. In other words, this is useful when +// reference counting is not a good fit. +// +// A common alternative to weak pointers is to have the shared object hold a +// list of all referrers, and then when the shared object is destroyed, it +// calls a method on the referrers to tell them to drop their references. This +// approach also requires the referrers to tell the shared object when they get +// destroyed so that the shared object can remove the referrer from its list of +// referrers. Such a solution works, but it is a bit complex. +// +// EXAMPLE: +// +// class Controller : public SupportsWeakPtr<Controller> { +// public: +// void SpawnWorker() { Worker::StartNew(AsWeakPtr()); } +// void WorkComplete(const Result& result) { ... } +// }; +// +// class Worker { +// public: +// static void StartNew(const WeakPtr<Controller>& controller) { +// Worker* worker = new Worker(controller); +// // Kick off asynchronous processing... +// } +// private: +// Worker(const WeakPtr<Controller>& controller) +// : controller_(controller) {} +// void DidCompleteAsynchronousProcessing(const Result& result) { +// if (controller_) +// controller_->WorkComplete(result); +// } +// WeakPtr<Controller> controller_; +// }; +// +// Given the above classes, a consumer may allocate a Controller object, call +// SpawnWorker several times, and then destroy the Controller object before all +// of the workers have completed. Because the Worker class only holds a weak +// pointer to the Controller, we don't have to worry about the Worker +// dereferencing the Controller back pointer after the Controller has been +// destroyed. +// +// ------------------------ Thread-safety notes ------------------------ +// When you get a WeakPtr (from a WeakPtrFactory or SupportsWeakPtr), if it's +// the only one pointing to the object, the object become bound to the +// current thread, as well as this WeakPtr and all later ones get created. +// +// You may only dereference the WeakPtr on the thread it binds to. However, it +// is safe to destroy the WeakPtr object on another thread. Because of this, +// querying WeakPtrFactory's HasWeakPtrs() method can be racy. +// +// On the other hand, the object that supports WeakPtr (extends SupportsWeakPtr) +// can only be deleted from the thread it binds to, until all WeakPtrs are +// deleted. +// +// Calling SupportsWeakPtr::DetachFromThread() can work around the limitations +// above and cancel the thread binding of the object and all WeakPtrs pointing +// to it, but it's not recommended and unsafe. +// +// WeakPtrs may be copy-constructed or assigned on threads other than the thread +// they are bound to. This does not change the thread binding. So these WeakPtrs +// may only be dereferenced on the thread that the original WeakPtr was bound +// to. + +#ifndef BASE_MEMORY_WEAK_PTR_H_ +#define BASE_MEMORY_WEAK_PTR_H_ + +#include "base/basictypes.h" +#include "base/base_export.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/template_util.h" +#include "base/threading/thread_checker.h" + +namespace base { + +template <typename T> class SupportsWeakPtr; +template <typename T> class WeakPtr; + +namespace internal { +// These classes are part of the WeakPtr implementation. +// DO NOT USE THESE CLASSES DIRECTLY YOURSELF. + +class BASE_EXPORT WeakReference { + public: + // While Flag is bound to a specific thread, it may be deleted from another + // via base::WeakPtr::~WeakPtr(). + class Flag : public RefCountedThreadSafe<Flag> { + public: + Flag(); + + void Invalidate(); + bool IsValid() const; + + void DetachFromThread() { thread_checker_.DetachFromThread(); } + + private: + friend class base::RefCountedThreadSafe<Flag>; + + ~Flag(); + + ThreadChecker thread_checker_; + bool is_valid_; + }; + + WeakReference(); + explicit WeakReference(const Flag* flag); + ~WeakReference(); + + bool is_valid() const; + + private: + scoped_refptr<const Flag> flag_; +}; + +class BASE_EXPORT WeakReferenceOwner { + public: + WeakReferenceOwner(); + ~WeakReferenceOwner(); + + WeakReference GetRef() const; + + bool HasRefs() const { + return flag_.get() && !flag_->HasOneRef(); + } + + void Invalidate(); + + // Indicates that this object will be used on another thread from now on. + void DetachFromThread() { + if (flag_) flag_->DetachFromThread(); + } + + private: + mutable scoped_refptr<WeakReference::Flag> flag_; +}; + +// This class simplifies the implementation of WeakPtr's type conversion +// constructor by avoiding the need for a public accessor for ref_. A +// WeakPtr<T> cannot access the private members of WeakPtr<U>, so this +// base class gives us a way to access ref_ in a protected fashion. +class BASE_EXPORT WeakPtrBase { + public: + WeakPtrBase(); + ~WeakPtrBase(); + + protected: + explicit WeakPtrBase(const WeakReference& ref); + + WeakReference ref_; +}; + +// This class provides a common implementation of common functions that would +// otherwise get instantiated separately for each distinct instantiation of +// SupportsWeakPtr<>. +class SupportsWeakPtrBase { + public: + // A safe static downcast of a WeakPtr<Base> to WeakPtr<Derived>. This + // conversion will only compile if there is exists a Base which inherits + // from SupportsWeakPtr<Base>. See base::AsWeakPtr() below for a helper + // function that makes calling this easier. + template<typename Derived> + static WeakPtr<Derived> StaticAsWeakPtr(Derived* t) { + typedef + is_convertible<Derived, internal::SupportsWeakPtrBase&> convertible; + COMPILE_ASSERT(convertible::value, + AsWeakPtr_argument_inherits_from_SupportsWeakPtr); + return AsWeakPtrImpl<Derived>(t, *t); + } + + private: + // This template function uses type inference to find a Base of Derived + // which is an instance of SupportsWeakPtr<Base>. We can then safely + // static_cast the Base* to a Derived*. + template <typename Derived, typename Base> + static WeakPtr<Derived> AsWeakPtrImpl( + Derived* t, const SupportsWeakPtr<Base>&) { + WeakPtr<Base> ptr = t->Base::AsWeakPtr(); + return WeakPtr<Derived>(ptr.ref_, static_cast<Derived*>(ptr.ptr_)); + } +}; + +} // namespace internal + +template <typename T> class WeakPtrFactory; + +// The WeakPtr class holds a weak reference to |T*|. +// +// This class is designed to be used like a normal pointer. You should always +// null-test an object of this class before using it or invoking a method that +// may result in the underlying object being destroyed. +// +// EXAMPLE: +// +// class Foo { ... }; +// WeakPtr<Foo> foo; +// if (foo) +// foo->method(); +// +template <typename T> +class WeakPtr : public internal::WeakPtrBase { + public: + WeakPtr() : ptr_(NULL) { + } + + // Allow conversion from U to T provided U "is a" T. + template <typename U> + WeakPtr(const WeakPtr<U>& other) : WeakPtrBase(other), ptr_(other.get()) { + } + + T* get() const { return ref_.is_valid() ? ptr_ : NULL; } + operator T*() const { return get(); } + + T& operator*() const { + DCHECK(get() != NULL); + return *get(); + } + T* operator->() const { + DCHECK(get() != NULL); + return get(); + } + + void reset() { + ref_ = internal::WeakReference(); + ptr_ = NULL; + } + + private: + friend class internal::SupportsWeakPtrBase; + friend class SupportsWeakPtr<T>; + friend class WeakPtrFactory<T>; + + WeakPtr(const internal::WeakReference& ref, T* ptr) + : WeakPtrBase(ref), + ptr_(ptr) { + } + + // This pointer is only valid when ref_.is_valid() is true. Otherwise, its + // value is undefined (as opposed to NULL). + T* ptr_; +}; + +// A class may extend from SupportsWeakPtr to expose weak pointers to itself. +// This is useful in cases where you want others to be able to get a weak +// pointer to your class. It also has the property that you don't need to +// initialize it from your constructor. +template <class T> +class SupportsWeakPtr : public internal::SupportsWeakPtrBase { + public: + SupportsWeakPtr() {} + + WeakPtr<T> AsWeakPtr() { + return WeakPtr<T>(weak_reference_owner_.GetRef(), static_cast<T*>(this)); + } + + // Indicates that this object will be used on another thread from now on. + void DetachFromThread() { + weak_reference_owner_.DetachFromThread(); + } + + protected: + ~SupportsWeakPtr() {} + + // Call this method to invalidate all existing weak pointers. + // This may be useful to call explicitly in a destructor of a derived class, + // as the SupportsWeakPtr destructor won't run until late in destruction. + void InvalidateWeakPtrs() { + weak_reference_owner_.Invalidate(); + } + + private: + internal::WeakReferenceOwner weak_reference_owner_; + DISALLOW_COPY_AND_ASSIGN(SupportsWeakPtr); +}; + +// Helper function that uses type deduction to safely return a WeakPtr<Derived> +// when Derived doesn't directly extend SupportsWeakPtr<Derived>, instead it +// extends a Base that extends SupportsWeakPtr<Base>. +// +// EXAMPLE: +// class Base : public base::SupportsWeakPtr<Producer> {}; +// class Derived : public Base {}; +// +// Derived derived; +// base::WeakPtr<Derived> ptr = base::AsWeakPtr(&derived); +// +// Note that the following doesn't work (invalid type conversion) since +// Derived::AsWeakPtr() is WeakPtr<Base> SupportsWeakPtr<Base>::AsWeakPtr(), +// and there's no way to safely cast WeakPtr<Base> to WeakPtr<Derived> at +// the caller. +// +// base::WeakPtr<Derived> ptr = derived.AsWeakPtr(); // Fails. + +template <typename Derived> +WeakPtr<Derived> AsWeakPtr(Derived* t) { + return internal::SupportsWeakPtrBase::StaticAsWeakPtr<Derived>(t); +} + +// A class may alternatively be composed of a WeakPtrFactory and thereby +// control how it exposes weak pointers to itself. This is helpful if you only +// need weak pointers within the implementation of a class. This class is also +// useful when working with primitive types. For example, you could have a +// WeakPtrFactory<bool> that is used to pass around a weak reference to a bool. +template <class T> +class WeakPtrFactory { + public: + explicit WeakPtrFactory(T* ptr) : ptr_(ptr) { + } + + ~WeakPtrFactory() { + ptr_ = NULL; + } + + WeakPtr<T> GetWeakPtr() { + DCHECK(ptr_); + return WeakPtr<T>(weak_reference_owner_.GetRef(), ptr_); + } + + // Call this method to invalidate all existing weak pointers. + void InvalidateWeakPtrs() { + DCHECK(ptr_); + weak_reference_owner_.Invalidate(); + } + + // Call this method to determine if any weak pointers exist. + bool HasWeakPtrs() const { + DCHECK(ptr_); + return weak_reference_owner_.HasRefs(); + } + + // Indicates that this object will be used on another thread from now on. + void DetachFromThread() { + DCHECK(ptr_); + weak_reference_owner_.DetachFromThread(); + } + + private: + internal::WeakReferenceOwner weak_reference_owner_; + T* ptr_; + DISALLOW_IMPLICIT_CONSTRUCTORS(WeakPtrFactory); +}; + +} // namespace base + +#endif // BASE_MEMORY_WEAK_PTR_H_
diff --git a/src/base/memory/weak_ptr_unittest.cc b/src/base/memory/weak_ptr_unittest.cc new file mode 100644 index 0000000..d5f8057 --- /dev/null +++ b/src/base/memory/weak_ptr_unittest.cc
@@ -0,0 +1,477 @@ +// 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/memory/weak_ptr.h" + +#include <string> + +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +template <class T> +class OffThreadObjectCreator { + public: + static T* NewObject() { + T* result; + { + Thread creator_thread("creator_thread"); + creator_thread.Start(); + creator_thread.message_loop()->PostTask( + FROM_HERE, + base::Bind(OffThreadObjectCreator::CreateObject, &result)); + } + DCHECK(result); // We synchronized on thread destruction above. + return result; + } + private: + static void CreateObject(T** result) { + *result = new T; + } +}; + +struct Base { + std::string member; +}; +struct Derived : Base {}; + +struct Target : SupportsWeakPtr<Target> {}; +struct DerivedTarget : Target {}; +struct Arrow { + WeakPtr<Target> target; +}; + +// Helper class to create and destroy weak pointer copies +// and delete objects on a background thread. +class BackgroundThread : public Thread { + public: + BackgroundThread() : Thread("owner_thread") {} + + virtual ~BackgroundThread() { + Stop(); + } + + void CreateArrowFromTarget(Arrow** arrow, Target* target) { + WaitableEvent completion(true, false); + message_loop()->PostTask( + FROM_HERE, + base::Bind(&BackgroundThread::DoCreateArrowFromTarget, + arrow, target, &completion)); + completion.Wait(); + } + + void CreateArrowFromArrow(Arrow** arrow, const Arrow* other) { + WaitableEvent completion(true, false); + message_loop()->PostTask( + FROM_HERE, + base::Bind(&BackgroundThread::DoCreateArrowFromArrow, + arrow, other, &completion)); + completion.Wait(); + } + + void DeleteTarget(Target* object) { + WaitableEvent completion(true, false); + message_loop()->PostTask( + FROM_HERE, + base::Bind(&BackgroundThread::DoDeleteTarget, object, &completion)); + completion.Wait(); + } + + void DeleteArrow(Arrow* object) { + WaitableEvent completion(true, false); + message_loop()->PostTask( + FROM_HERE, + base::Bind(&BackgroundThread::DoDeleteArrow, object, &completion)); + completion.Wait(); + } + + Target* DeRef(const Arrow* arrow) { + WaitableEvent completion(true, false); + Target* result = NULL; + message_loop()->PostTask( + FROM_HERE, + base::Bind(&BackgroundThread::DoDeRef, arrow, &result, &completion)); + completion.Wait(); + return result; + } + + protected: + static void DoCreateArrowFromArrow(Arrow** arrow, + const Arrow* other, + WaitableEvent* completion) { + *arrow = new Arrow; + **arrow = *other; + completion->Signal(); + } + + static void DoCreateArrowFromTarget(Arrow** arrow, + Target* target, + WaitableEvent* completion) { + *arrow = new Arrow; + (*arrow)->target = target->AsWeakPtr(); + completion->Signal(); + } + + static void DoDeRef(const Arrow* arrow, + Target** result, + WaitableEvent* completion) { + *result = arrow->target.get(); + completion->Signal(); + } + + static void DoDeleteTarget(Target* object, WaitableEvent* completion) { + delete object; + completion->Signal(); + } + + static void DoDeleteArrow(Arrow* object, WaitableEvent* completion) { + delete object; + completion->Signal(); + } +}; + +} // namespace + +TEST(WeakPtrFactoryTest, Basic) { + int data; + WeakPtrFactory<int> factory(&data); + WeakPtr<int> ptr = factory.GetWeakPtr(); + EXPECT_EQ(&data, ptr.get()); +} + +TEST(WeakPtrFactoryTest, Comparison) { + int data; + WeakPtrFactory<int> factory(&data); + WeakPtr<int> ptr = factory.GetWeakPtr(); + WeakPtr<int> ptr2 = ptr; + EXPECT_EQ(ptr, ptr2); +} + +TEST(WeakPtrFactoryTest, OutOfScope) { + WeakPtr<int> ptr; + EXPECT_EQ(NULL, ptr.get()); + { + int data; + WeakPtrFactory<int> factory(&data); + ptr = factory.GetWeakPtr(); + } + EXPECT_EQ(NULL, ptr.get()); +} + +TEST(WeakPtrFactoryTest, Multiple) { + WeakPtr<int> a, b; + { + int data; + WeakPtrFactory<int> factory(&data); + a = factory.GetWeakPtr(); + b = factory.GetWeakPtr(); + EXPECT_EQ(&data, a.get()); + EXPECT_EQ(&data, b.get()); + } + EXPECT_EQ(NULL, a.get()); + EXPECT_EQ(NULL, b.get()); +} + +TEST(WeakPtrFactoryTest, MultipleStaged) { + WeakPtr<int> a; + { + int data; + WeakPtrFactory<int> factory(&data); + a = factory.GetWeakPtr(); + { + WeakPtr<int> b = factory.GetWeakPtr(); + } + EXPECT_TRUE(NULL != a.get()); + } + EXPECT_EQ(NULL, a.get()); +} + +TEST(WeakPtrFactoryTest, Dereference) { + Base data; + data.member = "123456"; + WeakPtrFactory<Base> factory(&data); + WeakPtr<Base> ptr = factory.GetWeakPtr(); + EXPECT_EQ(&data, ptr.get()); + EXPECT_EQ(data.member, (*ptr).member); + EXPECT_EQ(data.member, ptr->member); +} + +TEST(WeakPtrFactoryTest, UpCast) { + Derived data; + WeakPtrFactory<Derived> factory(&data); + WeakPtr<Base> ptr = factory.GetWeakPtr(); + ptr = factory.GetWeakPtr(); + EXPECT_EQ(ptr.get(), &data); +} + +TEST(WeakPtrTest, SupportsWeakPtr) { + Target target; + WeakPtr<Target> ptr = target.AsWeakPtr(); + EXPECT_EQ(&target, ptr.get()); +} + +TEST(WeakPtrTest, DerivedTarget) { + DerivedTarget target; + WeakPtr<DerivedTarget> ptr = AsWeakPtr(&target); + EXPECT_EQ(&target, ptr.get()); +} + +TEST(WeakPtrTest, InvalidateWeakPtrs) { + int data; + WeakPtrFactory<int> factory(&data); + WeakPtr<int> ptr = factory.GetWeakPtr(); + EXPECT_EQ(&data, ptr.get()); + EXPECT_TRUE(factory.HasWeakPtrs()); + factory.InvalidateWeakPtrs(); + EXPECT_EQ(NULL, ptr.get()); + EXPECT_FALSE(factory.HasWeakPtrs()); +} + +TEST(WeakPtrTest, HasWeakPtrs) { + int data; + WeakPtrFactory<int> factory(&data); + { + WeakPtr<int> ptr = factory.GetWeakPtr(); + EXPECT_TRUE(factory.HasWeakPtrs()); + } + EXPECT_FALSE(factory.HasWeakPtrs()); +} + +TEST(WeakPtrTest, ObjectAndWeakPtrOnDifferentThreads) { + // Test that it is OK to create an object that supports WeakPtr on one thread, + // but use it on another. This tests that we do not trip runtime checks that + // ensure that a WeakPtr is not used by multiple threads. + scoped_ptr<Target> target(OffThreadObjectCreator<Target>::NewObject()); + WeakPtr<Target> weak_ptr = target->AsWeakPtr(); + EXPECT_EQ(target.get(), weak_ptr.get()); +} + +TEST(WeakPtrTest, WeakPtrInitiateAndUseOnDifferentThreads) { + // Test that it is OK to create an object that has a WeakPtr member on one + // thread, but use it on another. This tests that we do not trip runtime + // checks that ensure that a WeakPtr is not used by multiple threads. + scoped_ptr<Arrow> arrow(OffThreadObjectCreator<Arrow>::NewObject()); + Target target; + arrow->target = target.AsWeakPtr(); + EXPECT_EQ(&target, arrow->target.get()); +} + +TEST(WeakPtrTest, MoveOwnershipImplicitly) { + // Move object ownership to another thread by releasing all weak pointers + // on the original thread first, and then establish WeakPtr on a different + // thread. + BackgroundThread background; + background.Start(); + + Target* target = new Target(); + { + WeakPtr<Target> weak_ptr = target->AsWeakPtr(); + // Main thread deletes the WeakPtr, then the thread ownership of the + // object can be implicitly moved. + } + Arrow* arrow; + + // Background thread creates WeakPtr(and implicitly owns the object). + background.CreateArrowFromTarget(&arrow, target); + EXPECT_EQ(background.DeRef(arrow), target); + + { + // Main thread creates another WeakPtr, but this does not trigger implicitly + // thread ownership move. + Arrow arrow; + arrow.target = target->AsWeakPtr(); + + // The new WeakPtr is owned by background thread. + EXPECT_EQ(target, background.DeRef(&arrow)); + } + + // Target can only be deleted on background thread. + background.DeleteTarget(target); + background.DeleteArrow(arrow); +} + +TEST(WeakPtrTest, MoveOwnershipExplicitlyObjectNotReferenced) { + // Case 1: The target is not bound to any thread yet. So calling + // DetachFromThread() is a no-op. + Target target; + target.DetachFromThread(); + + // Case 2: The target is bound to main thread but no WeakPtr is pointing to + // it. In this case, it will be re-bound to any thread trying to get a + // WeakPtr pointing to it. So detach function call is again no-op. + { + WeakPtr<Target> weak_ptr = target.AsWeakPtr(); + } + target.DetachFromThread(); +} + +TEST(WeakPtrTest, MoveOwnershipExplicitly) { + BackgroundThread background; + background.Start(); + + Arrow* arrow; + { + Target target; + // Background thread creates WeakPtr(and implicitly owns the object). + background.CreateArrowFromTarget(&arrow, &target); + EXPECT_EQ(&target, background.DeRef(arrow)); + + // Detach from background thread. + target.DetachFromThread(); + + // Re-bind to main thread. + EXPECT_EQ(&target, arrow->target.get()); + + // Main thread can now delete the target. + } + + // WeakPtr can be deleted on non-owner thread. + background.DeleteArrow(arrow); +} + +TEST(WeakPtrTest, MainThreadRefOutlivesBackgroundThreadRef) { + // Originating thread has a WeakPtr that outlives others. + // - Main thread creates a WeakPtr + // - Background thread creates a WeakPtr copy from the one in main thread + // - Destruct the WeakPtr on background thread + // - Destruct the WeakPtr on main thread + BackgroundThread background; + background.Start(); + + Target target; + Arrow arrow; + arrow.target = target.AsWeakPtr(); + + Arrow* arrow_copy; + background.CreateArrowFromArrow(&arrow_copy, &arrow); + EXPECT_EQ(arrow_copy->target, &target); + background.DeleteArrow(arrow_copy); +} + +TEST(WeakPtrTest, BackgroundThreadRefOutlivesMainThreadRef) { + // Originating thread drops all references before another thread. + // - Main thread creates a WeakPtr and passes copy to background thread + // - Destruct the pointer on main thread + // - Destruct the pointer on background thread + BackgroundThread background; + background.Start(); + + Target target; + Arrow* arrow_copy; + { + Arrow arrow; + arrow.target = target.AsWeakPtr(); + background.CreateArrowFromArrow(&arrow_copy, &arrow); + } + EXPECT_EQ(arrow_copy->target, &target); + background.DeleteArrow(arrow_copy); +} + +TEST(WeakPtrTest, OwnerThreadDeletesObject) { + // Originating thread invalidates WeakPtrs while its held by other thread. + // - Main thread creates WeakPtr and passes Copy to background thread + // - Object gets destroyed on main thread + // (invalidates WeakPtr on background thread) + // - WeakPtr gets destroyed on Thread B + BackgroundThread background; + background.Start(); + Arrow* arrow_copy; + { + Target target; + Arrow arrow; + arrow.target = target.AsWeakPtr(); + background.CreateArrowFromArrow(&arrow_copy, &arrow); + } + EXPECT_EQ(NULL, arrow_copy->target.get()); + background.DeleteArrow(arrow_copy); +} + +TEST(WeakPtrTest, NonOwnerThreadCanDeleteWeakPtr) { + // Main thread creates a Target object. + Target target; + // Main thread creates an arrow referencing the Target. + Arrow* arrow = new Arrow(); + arrow->target = target.AsWeakPtr(); + + // Background can delete arrow (as well as the WeakPtr inside). + BackgroundThread background; + background.Start(); + background.DeleteArrow(arrow); +} + +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST + +TEST(WeakPtrDeathTest, WeakPtrCopyDoesNotChangeThreadBinding) { + // The default style "fast" does not support multi-threaded tests + // (introduces deadlock on Linux). + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + + BackgroundThread background; + background.Start(); + + // Main thread creates a Target object. + Target target; + // Main thread creates an arrow referencing the Target. + Arrow arrow; + arrow.target = target.AsWeakPtr(); + + // Background copies the WeakPtr. + Arrow* arrow_copy; + background.CreateArrowFromArrow(&arrow_copy, &arrow); + + // The copy is still bound to main thread so I can deref. + EXPECT_EQ(arrow.target.get(), arrow_copy->target.get()); + + // Although background thread created the copy, it can not deref the copied + // WeakPtr. + ASSERT_DEATH(background.DeRef(arrow_copy), ""); + + background.DeleteArrow(arrow_copy); +} + +TEST(WeakPtrDeathTest, NonOwnerThreadDereferencesWeakPtr) { + // The default style "fast" does not support multi-threaded tests + // (introduces deadlock on Linux). + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + + // Main thread creates a Target object. + Target target; + + // Main thread creates an arrow referencing the Target (so target's + // thread ownership can not be implicitly moved). + Arrow arrow; + arrow.target = target.AsWeakPtr(); + + // Background thread tries to deref target, which violates thread ownership. + BackgroundThread background; + background.Start(); + ASSERT_DEATH(background.DeRef(&arrow), ""); +} + +TEST(WeakPtrDeathTest, NonOwnerThreadDeletesObject) { + // The default style "fast" does not support multi-threaded tests + // (introduces deadlock on Linux). + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + + scoped_ptr<Target> target(new Target()); + // Main thread creates an arrow referencing the Target (so target's thread + // ownership can not be implicitly moved). + Arrow arrow; + arrow.target = target->AsWeakPtr(); + + // Background thread tries to delete target, which violates thread ownership. + BackgroundThread background; + background.Start(); + ASSERT_DEATH(background.DeleteTarget(target.release()), ""); +} + +#endif + +} // namespace base
diff --git a/src/base/memory/weak_ptr_unittest.nc b/src/base/memory/weak_ptr_unittest.nc new file mode 100644 index 0000000..afc8060 --- /dev/null +++ b/src/base/memory/weak_ptr_unittest.nc
@@ -0,0 +1,138 @@ +// 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/memory/weak_ptr.h" + +namespace base { + +struct Producer : SupportsWeakPtr<Producer> {}; +struct DerivedProducer : Producer {}; +struct OtherDerivedProducer : Producer {}; +struct MultiplyDerivedProducer : Producer, + SupportsWeakPtr<MultiplyDerivedProducer> {}; +struct Unrelated {}; +struct DerivedUnrelated : Unrelated {}; + +#if defined(NCTEST_AUTO_DOWNCAST) // [r"invalid conversion from"] + +void WontCompile() { + Producer f; + WeakPtr<Producer> ptr = f.AsWeakPtr(); + WeakPtr<DerivedProducer> derived_ptr = ptr; +} + +#elif defined(NCTEST_STATIC_DOWNCAST) // [r"invalid conversion from"] + +void WontCompile() { + Producer f; + WeakPtr<Producer> ptr = f.AsWeakPtr(); + WeakPtr<DerivedProducer> derived_ptr = + static_cast<WeakPtr<DerivedProducer> >(ptr); +} + +#elif defined(NCTEST_AUTO_REF_DOWNCAST) // [r"invalid initialization of reference"] + +void WontCompile() { + Producer f; + WeakPtr<Producer> ptr = f.AsWeakPtr(); + WeakPtr<DerivedProducer>& derived_ptr = ptr; +} + +#elif defined(NCTEST_STATIC_REF_DOWNCAST) // [r"invalid static_cast"] + +void WontCompile() { + Producer f; + WeakPtr<Producer> ptr = f.AsWeakPtr(); + WeakPtr<DerivedProducer>& derived_ptr = + static_cast<WeakPtr<DerivedProducer>&>(ptr); +} + +#elif defined(NCTEST_STATIC_ASWEAKPTR_DOWNCAST) // [r"no matching function"] + +void WontCompile() { + Producer f; + WeakPtr<DerivedProducer> ptr = + SupportsWeakPtr<Producer>::StaticAsWeakPtr<DerivedProducer>(&f); +} + +#elif defined(NCTEST_UNSAFE_HELPER_DOWNCAST) // [r"invalid conversion from"] + +void WontCompile() { + Producer f; + WeakPtr<DerivedProducer> ptr = AsWeakPtr(&f); +} + +#elif defined(NCTEST_UNSAFE_INSTANTIATED_HELPER_DOWNCAST) // [r"no matching function"] + +void WontCompile() { + Producer f; + WeakPtr<DerivedProducer> ptr = AsWeakPtr<DerivedProducer>(&f); +} + +#elif defined(NCTEST_UNSAFE_WRONG_INSANTIATED_HELPER_DOWNCAST) // [r"invalid conversion from"] + +void WontCompile() { + Producer f; + WeakPtr<DerivedProducer> ptr = AsWeakPtr<Producer>(&f); +} + +#elif defined(NCTEST_UNSAFE_HELPER_CAST) // [r"cannot convert"] + +void WontCompile() { + DerivedProducer f; + WeakPtr<OtherDerivedProducer> ptr = AsWeakPtr(&f); +} + +#elif defined(NCTEST_UNSAFE_INSTANTIATED_HELPER_SIDECAST) // [r"no matching function"] + +void WontCompile() { + DerivedProducer f; + WeakPtr<OtherDerivedProducer> ptr = AsWeakPtr<OtherDerivedProducer>(&f); +} + +#elif defined(NCTEST_UNSAFE_WRONG_INSTANTIATED_HELPER_SIDECAST) // [r"cannot convert"] + +void WontCompile() { + DerivedProducer f; + WeakPtr<OtherDerivedProducer> ptr = AsWeakPtr<DerivedProducer>(&f); +} + +#elif defined(NCTEST_UNRELATED_HELPER) // [r"cannot convert"] + +void WontCompile() { + DerivedProducer f; + WeakPtr<Unrelated> ptr = AsWeakPtr(&f); +} + +#elif defined(NCTEST_UNRELATED_INSTANTIATED_HELPER) // [r"no matching function"] + +void WontCompile() { + DerivedProducer f; + WeakPtr<Unrelated> ptr = AsWeakPtr<Unrelated>(&f); +} + +#elif defined(NCTEST_COMPLETELY_UNRELATED_HELPER) // [r"array with negative size"] + +void WontCompile() { + Unrelated f; + WeakPtr<Unrelated> ptr = AsWeakPtr(&f); +} + +#elif defined(NCTEST_DERIVED_COMPLETELY_UNRELATED_HELPER) // [r"array with negative size"] + +void WontCompile() { + DerivedUnrelated f; + WeakPtr<Unrelated> ptr = AsWeakPtr(&f); +} + +#elif defined(NCTEST_AMBIGUOUS_ANCESTORS) // [r"ambiguous base"] + +void WontCompile() { + MultiplyDerivedProducer f; + WeakPtr<MultiplyDerivedProducer> ptr = AsWeakPtr(&f); +} + +#endif + +}
diff --git a/src/base/message_loop.cc b/src/base/message_loop.cc new file mode 100644 index 0000000..2f8b032 --- /dev/null +++ b/src/base/message_loop.cc
@@ -0,0 +1,864 @@ +// 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/message_loop.h" + +#include <algorithm> + +#include "base/atomic_sequence_num.h" +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/debug/alias.h" +#include "base/debug/trace_event.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop_proxy_impl.h" +#include "base/message_pump_default.h" +#include "base/metrics/histogram.h" +#include "base/metrics/statistics_recorder.h" +#include "base/run_loop.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/thread_task_runner_handle.h" +#include "base/threading/thread_local.h" +#include "base/time.h" +#include "base/tracked_objects.h" + +#if defined(OS_MACOSX) +#include "base/message_pump_mac.h" +#endif +#if defined(OS_POSIX) && !defined(OS_IOS) && \ + (!defined(__LB_SHELL__) || defined(__LB_ANDROID__)) +#include "base/message_pump_libevent.h" +#endif +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) +#include "base/message_pump_android.h" +#endif +#if defined(OS_STARBOARD) +#include "base/message_pump_io_starboard.h" +#include "base/message_pump_ui_starboard.h" +#endif +#if defined(__LB_SHELL__) && !defined(__LB_ANDROID__) +#include "base/message_pump_shell.h" +#endif + +#if defined(TOOLKIT_GTK) +#include <gdk/gdk.h> +#include <gdk/gdkx.h> +#endif + +using base::PendingTask; +using base::TimeDelta; +using base::TimeTicks; + +namespace { + +// A lazily created thread local storage for quick access to a thread's message +// loop, if one exists. This should be safe and free of static constructors. +base::LazyInstance<base::ThreadLocalPointer<MessageLoop> > lazy_tls_ptr = + LAZY_INSTANCE_INITIALIZER; + +// Assign a sequential number to each message loops ever created so that +// we can use that as a unique ID for them, for debug tracing purposes. +base::LazyInstance<base::AtomicSequenceNumber> s_id_generator = + LAZY_INSTANCE_INITIALIZER; + +// Logical events for Histogram profiling. Run with -message-loop-histogrammer +// to get an accounting of messages and actions taken on each thread. +const int kTaskRunEvent = 0x1; +const int kTimerEvent = 0x2; + +// Provide range of message IDs for use in histogramming and debug display. +const int kLeastNonZeroMessageId = 1; +const int kMaxMessageId = 1099; +const int kNumberOfDistinctMessagesDisplayed = 1100; + +// Provide a macro that takes an expression (such as a constant, or macro +// constant) and creates a pair to initalize an array of pairs. In this case, +// our pair consists of the expressions value, and the "stringized" version +// of the expression (i.e., the exrpression put in quotes). For example, if +// we have: +// #define FOO 2 +// #define BAR 5 +// then the following: +// VALUE_TO_NUMBER_AND_NAME(FOO + BAR) +// will expand to: +// {7, "FOO + BAR"} +// We use the resulting array as an argument to our histogram, which reads the +// number as a bucket identifier, and proceeds to use the corresponding name +// in the pair (i.e., the quoted string) when printing out a histogram. +#define VALUE_TO_NUMBER_AND_NAME(name) {name, #name}, + +const base::LinearHistogram::DescriptionPair event_descriptions_[] = { + // Provide some pretty print capability in our histogram for our internal + // messages. + + // A few events we handle (kindred to messages), and used to profile actions. + VALUE_TO_NUMBER_AND_NAME(kTaskRunEvent) + VALUE_TO_NUMBER_AND_NAME(kTimerEvent) + + {-1, NULL} // The list must be null terminated, per API to histogram. +}; + +bool enable_histogrammer_ = false; + +MessageLoop::MessagePumpFactory* message_pump_for_ui_factory_ = NULL; + +// Create a process-wide unique ID to represent this task in trace events. This +// will be mangled with a Process ID hash to reduce the likelyhood of colliding +// with MessageLoop pointers on other processes. +uint64 GetTaskTraceID(const PendingTask& task, MessageLoop* loop) { + return (static_cast<uint64>(task.sequence_num) << 32) | + loop->id(); +} + +} // namespace + +//------------------------------------------------------------------------------ + +#if defined(OS_WIN) + +// Upon a SEH exception in this thread, it restores the original unhandled +// exception filter. +static int SEHFilter(LPTOP_LEVEL_EXCEPTION_FILTER old_filter) { + ::SetUnhandledExceptionFilter(old_filter); + return EXCEPTION_CONTINUE_SEARCH; +} + +// Retrieves a pointer to the current unhandled exception filter. There +// is no standalone getter method. +static LPTOP_LEVEL_EXCEPTION_FILTER GetTopSEHFilter() { + LPTOP_LEVEL_EXCEPTION_FILTER top_filter = NULL; + top_filter = ::SetUnhandledExceptionFilter(0); + ::SetUnhandledExceptionFilter(top_filter); + return top_filter; +} + +#endif // defined(OS_WIN) + +//------------------------------------------------------------------------------ + +MessageLoop::TaskObserver::TaskObserver() { +} + +MessageLoop::TaskObserver::~TaskObserver() { +} + +MessageLoop::DestructionObserver::~DestructionObserver() { +} + +//------------------------------------------------------------------------------ + +MessageLoop::MessageLoop(Type type) + : type_(type), + nestable_tasks_allowed_(true), + exception_restoration_(false), + message_histogram_(NULL), + run_loop_(NULL), +#ifdef OS_WIN + os_modal_loop_(false), +#endif // OS_WIN + next_sequence_num_(0) { + DCHECK(!current()) << "should only have one message loop per thread"; + lazy_tls_ptr.Pointer()->Set(this); + + id_ = s_id_generator.Get().GetNext(); + + message_loop_proxy_ = new base::MessageLoopProxyImpl(); + thread_task_runner_handle_.reset( + new base::ThreadTaskRunnerHandle(message_loop_proxy_)); + +// TODO(rvargas): Get rid of the OS guards. +#if defined(OS_WIN) +#define MESSAGE_PUMP_UI new base::MessagePumpForUI() +#define MESSAGE_PUMP_IO new base::MessagePumpForIO() +#elif defined(OS_IOS) +#define MESSAGE_PUMP_UI base::MessagePumpMac::Create() +#define MESSAGE_PUMP_IO new base::MessagePumpIOSForIO() +#elif defined(OS_MACOSX) +#define MESSAGE_PUMP_UI base::MessagePumpMac::Create() +#define MESSAGE_PUMP_IO new base::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 base::MessagePumpDefault(); +#elif defined(OS_STARBOARD) +#define MESSAGE_PUMP_UI new base::MessagePumpUIStarboard() +#define MESSAGE_PUMP_IO new base::MessagePumpIOStarboard() +#elif defined(__LB_SHELL__) && !defined(__LB_ANDROID__) +#define MESSAGE_PUMP_UI new base::MessagePumpShell() +#define MESSAGE_PUMP_IO new base::MessagePumpShell() +#elif defined(OS_POSIX) // POSIX but not MACOSX. +#define MESSAGE_PUMP_UI new base::MessagePumpForUI() +#define MESSAGE_PUMP_IO new base::MessagePumpLibevent() +#else +#error Not implemented +#endif + + if (type_ == TYPE_UI) { + if (message_pump_for_ui_factory_) + pump_ = message_pump_for_ui_factory_(); + else + pump_ = MESSAGE_PUMP_UI; + } else if (type_ == TYPE_IO) { + pump_ = MESSAGE_PUMP_IO; + } else { + DCHECK_EQ(TYPE_DEFAULT, type_); + pump_ = new base::MessagePumpDefault(); + } +} + +MessageLoop::~MessageLoop() { + DCHECK_EQ(this, current()); + + DCHECK(!run_loop_); + + // Clean up any unprocessed tasks, but take care: deleting a task could + // result in the addition of more tasks (e.g., via DeleteSoon). We set a + // limit on the number of times we will allow a deleted task to generate more + // tasks. Normally, we should only pass through this loop once or twice. If + // we end up hitting the loop limit, then it is probably due to one task that + // is being stubborn. Inspect the queues to see who is left. + bool did_work; + for (int i = 0; i < 100; ++i) { + DeletePendingTasks(); + ReloadWorkQueue(); + // If we end up with empty queues, then break out of the loop. + did_work = DeletePendingTasks(); + if (!did_work) + break; + } + DCHECK(!did_work); + + // Let interested parties have one last shot at accessing this. + FOR_EACH_OBSERVER(DestructionObserver, destruction_observers_, + WillDestroyCurrentMessageLoop()); + + thread_task_runner_handle_.reset(); + + // Tell the message_loop_proxy that we are dying. + static_cast<base::MessageLoopProxyImpl*>(message_loop_proxy_.get())-> + WillDestroyCurrentMessageLoop(); + message_loop_proxy_ = NULL; + + // OK, now make it so that no one can find us. + lazy_tls_ptr.Pointer()->Set(NULL); + +#if defined(OS_WIN) + // If we left the high-resolution timer activated, deactivate it now. + // Doing this is not-critical, it is mainly to make sure we track + // the high resolution timer activations properly in our unit tests. + if (!high_resolution_timer_expiration_.is_null()) { + base::Time::ActivateHighResolutionTimer(false); + high_resolution_timer_expiration_ = base::TimeTicks(); + } +#endif +} + +// static +MessageLoop* MessageLoop::current() { + // TODO(darin): sadly, we cannot enable this yet since people call us even + // when they have no intention of using us. + // DCHECK(loop) << "Ouch, did you forget to initialize me?"; + return lazy_tls_ptr.Pointer()->Get(); +} + +// static +void MessageLoop::EnableHistogrammer(bool enable) { + enable_histogrammer_ = enable; +} + +// static +void MessageLoop::InitMessagePumpForUIFactory(MessagePumpFactory* factory) { + DCHECK(!message_pump_for_ui_factory_); + message_pump_for_ui_factory_ = factory; +} + +void MessageLoop::AddDestructionObserver( + DestructionObserver* destruction_observer) { + DCHECK_EQ(this, current()); + destruction_observers_.AddObserver(destruction_observer); +} + +void MessageLoop::RemoveDestructionObserver( + DestructionObserver* destruction_observer) { + DCHECK_EQ(this, current()); + destruction_observers_.RemoveObserver(destruction_observer); +} + +void MessageLoop::PostTask( + const tracked_objects::Location& from_here, const base::Closure& task) { + DCHECK(!task.is_null()) << from_here.ToString(); + PendingTask pending_task( + from_here, task, CalculateDelayedRuntime(TimeDelta()), true); + AddToIncomingQueue(&pending_task); +} + +void MessageLoop::PostDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + TimeDelta delay) { + DCHECK(!task.is_null()) << from_here.ToString(); + PendingTask pending_task( + from_here, task, CalculateDelayedRuntime(delay), true); + AddToIncomingQueue(&pending_task); +} + +void MessageLoop::PostNonNestableTask( + const tracked_objects::Location& from_here, + const base::Closure& task) { + DCHECK(!task.is_null()) << from_here.ToString(); + PendingTask pending_task( + from_here, task, CalculateDelayedRuntime(TimeDelta()), false); + AddToIncomingQueue(&pending_task); +} + +void MessageLoop::PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + TimeDelta delay) { + DCHECK(!task.is_null()) << from_here.ToString(); + PendingTask pending_task( + from_here, task, CalculateDelayedRuntime(delay), false); + AddToIncomingQueue(&pending_task); +} + +void MessageLoop::Run() { + base::RunLoop run_loop; + run_loop.Run(); +} + +void MessageLoop::RunUntilIdle() { + base::RunLoop run_loop; + run_loop.RunUntilIdle(); +} + +void MessageLoop::QuitWhenIdle() { + DCHECK_EQ(this, current()); + if (run_loop_) { + run_loop_->quit_when_idle_received_ = true; +#if defined(OS_STARBOARD) + // The IO loop may have already been idle, in which case it's waiting on the + // SbSocketWaiter. This call to ScheduleWork() ensures that the Idle + // delegate callback will be called again soon. This should be safe and + // appropriate to call here, even on non-Starboard message pumps. + pump_->ScheduleWork(); +#endif + } else { + NOTREACHED() << "Must be inside Run to call Quit"; + } +} + +void MessageLoop::QuitNow() { + DCHECK_EQ(this, current()); + if (run_loop_) { + pump_->Quit(); + } else { + NOTREACHED() << "Must be inside Run to call Quit"; + } +} + +bool MessageLoop::IsType(Type type) const { + return type_ == type; +} + +static void QuitCurrentWhenIdle() { + MessageLoop::current()->QuitWhenIdle(); +} + +// static +base::Closure MessageLoop::QuitWhenIdleClosure() { + return base::Bind(&QuitCurrentWhenIdle); +} + +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. + pump_->ScheduleWork(); + } +} + +bool MessageLoop::NestableTasksAllowed() const { + return nestable_tasks_allowed_; +} + +bool MessageLoop::IsNested() { + return run_loop_->run_depth_ > 1; +} + +void MessageLoop::AddTaskObserver(TaskObserver* task_observer) { + DCHECK_EQ(this, current()); + task_observers_.AddObserver(task_observer); +} + +void MessageLoop::RemoveTaskObserver(TaskObserver* task_observer) { + DCHECK_EQ(this, current()); + task_observers_.RemoveObserver(task_observer); +} + +void MessageLoop::AssertIdle() const { + // We only check |incoming_queue_|, since we don't want to lock |work_queue_|. + base::AutoLock lock(incoming_queue_lock_); + DCHECK(incoming_queue_.empty()); +} + +bool MessageLoop::is_running() const { + DCHECK_EQ(this, current()); + return run_loop_ != NULL; +} + +//------------------------------------------------------------------------------ + +// Runs the loop in two different SEH modes: +// enable_SEH_restoration_ = false : any unhandled exception goes to the last +// one that calls SetUnhandledExceptionFilter(). +// enable_SEH_restoration_ = true : any unhandled exception goes to the filter +// that was existed before the loop was run. +void MessageLoop::RunHandler() { +#if defined(OS_WIN) + if (exception_restoration_) { + RunInternalInSEHFrame(); + return; + } +#endif + + RunInternal(); +} + +#if defined(OS_WIN) +__declspec(noinline) void MessageLoop::RunInternalInSEHFrame() { + LPTOP_LEVEL_EXCEPTION_FILTER current_filter = GetTopSEHFilter(); + __try { + RunInternal(); + } __except(SEHFilter(current_filter)) { + } + return; +} +#endif + +void MessageLoop::RunInternal() { + DCHECK_EQ(this, current()); + + StartHistogrammer(); + +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) && !defined(__LB_ANDROID__) && \ + !defined(OS_STARBOARD) + if (run_loop_->dispatcher_ && type() == TYPE_UI) { + static_cast<base::MessagePumpForUI*>(pump_.get())-> + RunWithDispatcher(this, run_loop_->dispatcher_); + return; + } +#endif + + pump_->Run(this); +} + +bool MessageLoop::ProcessNextDelayedNonNestableTask() { + if (run_loop_->run_depth_ != 1) + return false; + + if (deferred_non_nestable_work_queue_.empty()) + return false; + + PendingTask pending_task = deferred_non_nestable_work_queue_.front(); + deferred_non_nestable_work_queue_.pop(); + + RunTask(pending_task); + return true; +} + +void MessageLoop::RunTask(const PendingTask& pending_task) { + TRACE_EVENT_FLOW_END0("task", "MessageLoop::PostTask", + TRACE_ID_MANGLE(GetTaskTraceID(pending_task, this))); + TRACE_EVENT2("task", "MessageLoop::RunTask", + "src_file", pending_task.posted_from.file_name(), + "src_func", pending_task.posted_from.function_name()); + DCHECK(nestable_tasks_allowed_); + // Execute the task and assume the worst: It is probably not reentrant. + nestable_tasks_allowed_ = false; + + // Before running the task, store the program counter where it was posted + // and deliberately alias it to ensure it is on the stack if the task + // crashes. Be careful not to assume that the variable itself will have the + // expected value when displayed by the optimizer in an optimized build. + // Look at a memory dump of the stack. + const void* program_counter = + pending_task.posted_from.program_counter(); + base::debug::Alias(&program_counter); + + HistogramEvent(kTaskRunEvent); + + tracked_objects::TrackedTime start_time = + tracked_objects::ThreadData::NowForStartOfRun(pending_task.birth_tally); + + FOR_EACH_OBSERVER(TaskObserver, task_observers_, + WillProcessTask(pending_task.time_posted)); + pending_task.task.Run(); + FOR_EACH_OBSERVER(TaskObserver, task_observers_, + DidProcessTask(pending_task.time_posted)); + + tracked_objects::ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, + start_time, tracked_objects::ThreadData::NowForEndOfRun()); + + nestable_tasks_allowed_ = true; +} + +bool MessageLoop::DeferOrRunPendingTask(const PendingTask& pending_task) { + if (pending_task.nestable || run_loop_->run_depth_ == 1) { + RunTask(pending_task); + // Show that we ran a task (Note: a new one might arrive as a + // consequence!). + return true; + } + + // We couldn't run the task now because we're in a nested message loop + // and the task isn't nestable. + deferred_non_nestable_work_queue_.push(pending_task); + return false; +} + +void MessageLoop::AddToDelayedWorkQueue(const PendingTask& pending_task) { + // Move to the delayed work queue. + delayed_work_queue_.push(pending_task); +} + +void MessageLoop::ReloadWorkQueue() { + // We can improve performance of our loading tasks from incoming_queue_ to + // work_queue_ by waiting until the last minute (work_queue_ is empty) to + // load. That reduces the number of locks-per-task significantly when our + // queues get large. + if (!work_queue_.empty()) + return; // Wait till we *really* need to lock and load. + + // Acquire all we can from the inter-thread queue with one lock acquisition. + { + base::AutoLock lock(incoming_queue_lock_); + if (incoming_queue_.empty()) + return; + incoming_queue_.Swap(&work_queue_); // Constant time + DCHECK(incoming_queue_.empty()); + } +} + +bool MessageLoop::DeletePendingTasks() { + bool did_work = !work_queue_.empty(); + while (!work_queue_.empty()) { + PendingTask pending_task = work_queue_.front(); + work_queue_.pop(); + if (!pending_task.delayed_run_time.is_null()) { + // We want to delete delayed tasks in the same order in which they would + // normally be deleted in case of any funny dependencies between delayed + // tasks. + AddToDelayedWorkQueue(pending_task); + } + } + did_work |= !deferred_non_nestable_work_queue_.empty(); + while (!deferred_non_nestable_work_queue_.empty()) { + deferred_non_nestable_work_queue_.pop(); + } + did_work |= !delayed_work_queue_.empty(); + + // Historically, we always delete the task regardless of valgrind status. It's + // not completely clear why we want to leak them in the loops above. This + // code is replicating legacy behavior, and should not be considered + // absolutely "correct" behavior. See TODO above about deleting all tasks + // when it's safe. + while (!delayed_work_queue_.empty()) { + delayed_work_queue_.pop(); + } + return did_work; +} + +TimeTicks MessageLoop::CalculateDelayedRuntime(TimeDelta delay) { + TimeTicks delayed_run_time; + if (delay > TimeDelta()) { + delayed_run_time = TimeTicks::Now() + delay; + +#if defined(OS_WIN) + if (high_resolution_timer_expiration_.is_null()) { + // Windows timers are granular to 15.6ms. If we only set high-res + // timers for those under 15.6ms, then a 18ms timer ticks at ~32ms, + // which as a percentage is pretty inaccurate. So enable high + // res timers for any timer which is within 2x of the granularity. + // This is a tradeoff between accuracy and power management. + bool needs_high_res_timers = delay.InMilliseconds() < + (2 * base::Time::kMinLowResolutionThresholdMs); + if (needs_high_res_timers) { + if (base::Time::ActivateHighResolutionTimer(true)) { + high_resolution_timer_expiration_ = TimeTicks::Now() + + TimeDelta::FromMilliseconds(kHighResolutionTimerModeLeaseTimeMs); + } + } + } +#endif + } else { + DCHECK_EQ(delay.InMilliseconds(), 0) << "delay should not be negative"; + } + +#if defined(OS_WIN) + if (!high_resolution_timer_expiration_.is_null()) { + if (TimeTicks::Now() > high_resolution_timer_expiration_) { + base::Time::ActivateHighResolutionTimer(false); + high_resolution_timer_expiration_ = TimeTicks(); + } + } +#endif + + return delayed_run_time; +} + +// Possibly called on a background thread! +void MessageLoop::AddToIncomingQueue(PendingTask* pending_task) { + // Warning: Don't try to short-circuit, and handle this thread's tasks more + // directly, as it could starve handling of foreign threads. Put every task + // into this queue. + + scoped_refptr<base::MessagePump> pump; + { + base::AutoLock locked(incoming_queue_lock_); + + // Initialize the sequence number. The sequence number is used for delayed + // tasks (to faciliate FIFO sorting when two tasks have the same + // delayed_run_time value) and for identifying the task in about:tracing. + pending_task->sequence_num = next_sequence_num_++; + + TRACE_EVENT_FLOW_BEGIN0("task", "MessageLoop::PostTask", + TRACE_ID_MANGLE(GetTaskTraceID(*pending_task, this))); + + bool was_empty = incoming_queue_.empty(); + incoming_queue_.push(*pending_task); + pending_task->task.Reset(); + if (!was_empty) + return; // Someone else should have started the sub-pump. + + pump = pump_; + } + // Since the incoming_queue_ may contain a task that destroys this message + // loop, we cannot exit incoming_queue_lock_ until we are done with |this|. + // We use a stack-based reference to the message pump so that we can call + // ScheduleWork outside of incoming_queue_lock_. + + pump->ScheduleWork(); +} + +//------------------------------------------------------------------------------ +// Method and data for histogramming events and actions taken by each instance +// on each thread. + +void MessageLoop::StartHistogrammer() { +#if !defined(OS_NACL) // NaCl build has no metrics code. + if (enable_histogrammer_ && !message_histogram_ + && base::StatisticsRecorder::IsActive()) { + DCHECK(!thread_name_.empty()); + message_histogram_ = base::LinearHistogram::FactoryGetWithRangeDescription( + "MsgLoop:" + thread_name_, + kLeastNonZeroMessageId, kMaxMessageId, + kNumberOfDistinctMessagesDisplayed, + message_histogram_->kHexRangePrintingFlag, + event_descriptions_); + } +#endif +} + +void MessageLoop::HistogramEvent(int event) { +#if !defined(OS_NACL) + if (message_histogram_) + message_histogram_->Add(event); +#endif +} + +bool MessageLoop::DoWork() { + if (!nestable_tasks_allowed_) { + // Task can't be executed right now. + return false; + } + + for (;;) { + ReloadWorkQueue(); + if (work_queue_.empty()) + break; + + // Execute oldest task. + do { + PendingTask pending_task = work_queue_.front(); + work_queue_.pop(); + if (!pending_task.delayed_run_time.is_null()) { + AddToDelayedWorkQueue(pending_task); + // If we changed the topmost task, then it is time to reschedule. + if (delayed_work_queue_.top().task.Equals(pending_task.task)) + pump_->ScheduleDelayedWork(pending_task.delayed_run_time); + } else { + if (DeferOrRunPendingTask(pending_task)) + return true; + } + } while (!work_queue_.empty()); + } + + // Nothing happened. + return false; +} + +bool MessageLoop::DoDelayedWork(TimeTicks* next_delayed_work_time) { + if (!nestable_tasks_allowed_ || delayed_work_queue_.empty()) { + recent_time_ = *next_delayed_work_time = TimeTicks(); + return false; + } + + // When we "fall behind," there will be a lot of tasks in the delayed work + // queue that are ready to run. To increase efficiency when we fall behind, + // we will only call Time::Now() intermittently, and then process all tasks + // that are ready to run before calling it again. As a result, the more we + // fall behind (and have a lot of ready-to-run delayed tasks), the more + // efficient we'll be at handling the tasks. + + TimeTicks next_run_time = delayed_work_queue_.top().delayed_run_time; + if (next_run_time > recent_time_) { + recent_time_ = TimeTicks::Now(); // Get a better view of Now(); + if (next_run_time > recent_time_) { + *next_delayed_work_time = next_run_time; + return false; + } + } + + PendingTask pending_task = delayed_work_queue_.top(); + delayed_work_queue_.pop(); + + if (!delayed_work_queue_.empty()) + *next_delayed_work_time = delayed_work_queue_.top().delayed_run_time; + + return DeferOrRunPendingTask(pending_task); +} + +bool MessageLoop::DoIdleWork() { + if (ProcessNextDelayedNonNestableTask()) + return true; + + if (run_loop_->quit_when_idle_received_) + pump_->Quit(); + + return false; +} + +void MessageLoop::DeleteSoonInternal(const tracked_objects::Location& from_here, + void(*deleter)(const void*), + const void* object) { + PostNonNestableTask(from_here, base::Bind(deleter, object)); +} + +void MessageLoop::ReleaseSoonInternal( + const tracked_objects::Location& from_here, + void(*releaser)(const void*), + const void* object) { + PostNonNestableTask(from_here, base::Bind(releaser, object)); +} + +//------------------------------------------------------------------------------ +// MessageLoopForUI + +#if defined(OS_WIN) +void MessageLoopForUI::DidProcessMessage(const MSG& message) { + pump_win()->DidProcessMessage(message); +} +#endif // defined(OS_WIN) + +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) +void MessageLoopForUI::Start() { + // No Histogram support for UI message loop as it is managed by Java side + static_cast<base::MessagePumpForUI*>(pump_.get())->Start(this); +} +#endif + +#if defined(OS_STARBOARD) +void MessageLoopForUI::Start() { + // No Histogram support for UI message loop as it is managed by Starboard. + static_cast<base::MessagePumpUIStarboard*>(pump_.get())->Start(this); +} +#endif + +#if defined(OS_IOS) +void MessageLoopForUI::Attach() { + static_cast<base::MessagePumpUIApplication*>(pump_.get())->Attach(this); +} +#endif + +#if !defined(OS_MACOSX) && !defined(OS_NACL) && !defined(OS_ANDROID) && \ + !defined(__LB_ANDROID__) && !defined(OS_STARBOARD) +void MessageLoopForUI::AddObserver(Observer* observer) { + pump_ui()->AddObserver(observer); +} + +void MessageLoopForUI::RemoveObserver(Observer* observer) { + pump_ui()->RemoveObserver(observer); +} +#endif // !defined(OS_MACOSX) && !defined(OS_NACL) && !defined(OS_ANDROID) + +//------------------------------------------------------------------------------ +// MessageLoopForIO + +#if defined(OS_WIN) + +void MessageLoopForIO::RegisterIOHandler(HANDLE file, IOHandler* handler) { + pump_io()->RegisterIOHandler(file, handler); +} + +bool MessageLoopForIO::RegisterJobObject(HANDLE job, IOHandler* handler) { + return pump_io()->RegisterJobObject(job, handler); +} + +bool MessageLoopForIO::WaitForIOCompletion(DWORD timeout, IOHandler* filter) { + return pump_io()->WaitForIOCompletion(timeout, filter); +} + +#elif defined(__LB_SHELL__) && !defined(__LB_ANDROID__) + +bool MessageLoopForIO::WatchSocket(int sock, + bool persistent, + Mode mode, + FileDescriptorWatcher *controller, + Watcher *delegate) { + return pump_io()->WatchSocket( + sock, + persistent, + mode, + controller, + delegate); +} + +#elif defined(OS_IOS) + +bool MessageLoopForIO::WatchFileDescriptor(int fd, + bool persistent, + Mode mode, + FileDescriptorWatcher *controller, + Watcher *delegate) { + return pump_io()->WatchFileDescriptor( + fd, + persistent, + mode, + controller, + delegate); +} + +#elif defined(OS_POSIX) && !defined(OS_NACL) + +bool MessageLoopForIO::WatchFileDescriptor(int fd, + bool persistent, + Mode mode, + FileDescriptorWatcher *controller, + Watcher *delegate) { + return pump_libevent()->WatchFileDescriptor( + fd, + persistent, + mode, + controller, + delegate); +} + +#endif
diff --git a/src/base/message_loop.h b/src/base/message_loop.h new file mode 100644 index 0000000..70a15b7 --- /dev/null +++ b/src/base/message_loop.h
@@ -0,0 +1,778 @@ +// 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_MESSAGE_LOOP_H_ +#define BASE_MESSAGE_LOOP_H_ + +#include <queue> +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/location.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop_proxy.h" +#include "base/message_pump.h" +#include "base/observer_list.h" +#include "base/pending_task.h" +#include "base/sequenced_task_runner_helpers.h" +#include "base/synchronization/lock.h" +#include "base/tracking_info.h" +#include "base/time.h" + +#if defined(OS_WIN) +// We need this to declare base::MessagePumpWin::Dispatcher, which we should +// really just eliminate. +#include "base/message_pump_win.h" +#elif defined(OS_STARBOARD) +#include "base/message_pump_io_starboard.h" +#elif defined(__LB_SHELL__) && !defined(__LB_ANDROID__) +#include "base/message_pump_shell.h" +#elif defined(OS_IOS) +#include "base/message_pump_io_ios.h" +#elif defined(OS_POSIX) +#include "base/message_pump_libevent.h" +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) && !defined(__LB_ANDROID__) + +#if defined(USE_AURA) && defined(USE_X11) && !defined(OS_NACL) +#include "base/message_pump_aurax11.h" +#else +#include "base/message_pump_gtk.h" +#endif + +#endif +#endif + +namespace base { +class Histogram; +class RunLoop; +class ThreadTaskRunnerHandle; +#if defined(OS_ANDROID) +class MessagePumpForUI; +#endif +#if defined(OS_STARBOARD) +class MessagePumpUIStarboard; +#endif +} // namespace base + +// A MessageLoop is used to process events for a particular thread. There is +// at most one MessageLoop instance per thread. +// +// Events include at a minimum Task instances submitted to PostTask or those +// managed by TimerManager. Depending on the type of message pump used by the +// MessageLoop other events such as UI messages may be processed. On Windows +// APC calls (as time permits) and signals sent to a registered set of HANDLEs +// may also be processed. +// +// NOTE: Unless otherwise specified, a MessageLoop's methods may only be called +// on the thread where the MessageLoop's Run method executes. +// +// NOTE: MessageLoop has task reentrancy protection. This means that if a +// task is being processed, a second task cannot start until the first task is +// finished. Reentrancy can happen when processing a task, and an inner +// message pump is created. That inner pump then processes native messages +// which could implicitly start an inner task. Inner message pumps are created +// with dialogs (DialogBox), common dialogs (GetOpenFileName), OLE functions +// (DoDragDrop), printer functions (StartDoc) and *many* others. +// +// Sample workaround when inner task processing is needed: +// HRESULT hr; +// { +// MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); +// hr = DoDragDrop(...); // Implicitly runs a modal message loop. +// } +// // Process |hr| (the result returned by DoDragDrop()). +// +// Please be SURE your task is reentrant (nestable) and all global variables +// are stable and accessible before calling SetNestableTasksAllowed(true). +// +class BASE_EXPORT MessageLoop : public base::MessagePump::Delegate { + public: +#if (defined(__LB_SHELL__) && !defined(__LB_ANDROID__)) + typedef base::MessagePumpShell::Dispatcher Dispatcher; + typedef base::MessagePumpShell::Observer Observer; +#elif !defined(OS_MACOSX) && !defined(OS_ANDROID) && \ + !defined(__LB_ANDROID__) && !defined(OS_STARBOARD) + typedef base::MessagePumpDispatcher Dispatcher; + typedef base::MessagePumpObserver Observer; +#endif + + // A MessageLoop has a particular type, which indicates the set of + // asynchronous events it may process in addition to tasks and timers. + // + // TYPE_DEFAULT + // This type of ML only supports tasks and timers. + // + // TYPE_UI + // This type of ML also supports native UI events (e.g., Windows messages). + // See also MessageLoopForUI. + // + // TYPE_IO + // This type of ML also supports asynchronous IO. See also + // MessageLoopForIO. + // + enum Type { + TYPE_DEFAULT, + TYPE_UI, + TYPE_IO + }; + + // 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); + virtual ~MessageLoop(); + + // Returns the MessageLoop object for the current thread, or null if none. + static MessageLoop* current(); + + static void EnableHistogrammer(bool enable_histogrammer); + + typedef base::MessagePump* (MessagePumpFactory)(); + // Using the given base::MessagePumpForUIFactory to override the default + // MessagePump implementation for 'TYPE_UI'. + static void InitMessagePumpForUIFactory(MessagePumpFactory* factory); + +#if (defined(__LB_SHELL__) && !defined(__LB_ANDROID__)) || defined(OS_STARBOARD) + inline std::size_t Size() const { + return work_queue_.size() + delayed_work_queue_.size() + + deferred_non_nestable_work_queue_.size() + incoming_queue_.size(); + } +#endif + + // 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 + // do final cleanup that depends on the MessageLoop. + // + // NOTE: Any tasks posted to the MessageLoop during this notification will + // not be run. Instead, they will be deleted. + // + class BASE_EXPORT DestructionObserver { + public: + virtual void WillDestroyCurrentMessageLoop() = 0; + + protected: + virtual ~DestructionObserver(); + }; + + // Add a DestructionObserver, which will start receiving notifications + // immediately. + void AddDestructionObserver(DestructionObserver* destruction_observer); + + // Remove a DestructionObserver. It is safe to call this method while a + // DestructionObserver is receiving a notification callback. + void RemoveDestructionObserver(DestructionObserver* destruction_observer); + + // The "PostTask" family of methods call the task's Run method asynchronously + // from within a message loop at some point in the future. + // + // With the PostTask variant, tasks are invoked in FIFO order, inter-mixed + // with normal UI or IO event processing. With the PostDelayedTask variant, + // tasks are called after at least approximately 'delay_ms' have elapsed. + // + // The NonNestable variants work similarly except that they promise never to + // dispatch the task from a nested invocation of MessageLoop::Run. Instead, + // such tasks get deferred until the top-most MessageLoop::Run is executing. + // + // The MessageLoop takes ownership of the Task, and deletes it after it has + // been Run(). + // + // PostTask(from_here, task) is equivalent to + // PostDelayedTask(from_here, task, 0). + // + // NOTE: These methods may be called on any thread. The Task will be invoked + // on the thread that executes MessageLoop::Run(). + void PostTask( + const tracked_objects::Location& from_here, + const base::Closure& task); + + void PostDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay); + + void PostNonNestableTask( + const tracked_objects::Location& from_here, + const base::Closure& task); + + void PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay); + + // A variant on PostTask that deletes the given object. This is useful + // if the object needs to live until the next run of the MessageLoop (for + // example, deleting a RenderProcessHost from within an IPC callback is not + // good). + // + // NOTE: This method may be called on any thread. The object will be deleted + // on the thread that executes MessageLoop::Run(). If this is not the same + // as the thread that calls PostDelayedTask(FROM_HERE, ), then T MUST inherit + // from RefCountedThreadSafe<T>! + template <class T> + void DeleteSoon(const tracked_objects::Location& from_here, const T* object) { + base::subtle::DeleteHelperInternal<T, void>::DeleteViaSequencedTaskRunner( + this, from_here, object); + } + + // A variant on PostTask that releases the given reference counted object + // (by calling its Release method). This is useful if the object needs to + // live until the next run of the MessageLoop, or if the object needs to be + // released on a particular thread. + // + // NOTE: This method may be called on any thread. The object will be + // released (and thus possibly deleted) on the thread that executes + // MessageLoop::Run(). If this is not the same as the thread that calls + // PostDelayedTask(FROM_HERE, ), then T MUST inherit from + // RefCountedThreadSafe<T>! + template <class T> + void ReleaseSoon(const tracked_objects::Location& from_here, + const T* object) { + base::subtle::ReleaseHelperInternal<T, void>::ReleaseViaSequencedTaskRunner( + this, from_here, object); + } + + // Deprecated: use RunLoop instead. + // Run the message loop. + void Run(); + + // Deprecated: use RunLoop instead. + // Process all pending tasks, windows messages, etc., but don't wait/sleep. + // Return as soon as all items that can be run are taken care of. + void RunUntilIdle(); + + // TODO(jbates) remove this. crbug.com/131220. See RunUntilIdle(). + void RunAllPending() { RunUntilIdle(); } + + // TODO(jbates) remove this. crbug.com/131220. See QuitWhenIdle(). + void Quit() { QuitWhenIdle(); } + + // Deprecated: use RunLoop instead. + // + // Signals the Run method to return when it becomes idle. It will continue to + // process pending messages and future messages as long as they are enqueued. + // Warning: if the MessageLoop remains busy, it may never quit. Only use this + // Quit method when looping procedures (such as web pages) have been shut + // down. + // + // This method may only be called on the same thread that called Run, and Run + // must still be on the call stack. + // + // Use QuitClosure variants if you need to Quit another thread's MessageLoop, + // but note that doing so is fairly dangerous if the target thread makes + // nested calls to MessageLoop::Run. The problem being that you won't know + // which nested run loop you are quitting, so be careful! + void QuitWhenIdle(); + + // Deprecated: use RunLoop instead. + // + // This method is a variant of Quit, that does not wait for pending messages + // to be processed before returning from Run. + void QuitNow(); + + // TODO(jbates) remove this. crbug.com/131220. See QuitWhenIdleClosure(). + static base::Closure QuitClosure() { return QuitWhenIdleClosure(); } + + // Deprecated: use RunLoop instead. + // Construct a Closure that will call QuitWhenIdle(). Useful to schedule an + // arbitrary MessageLoop to QuitWhenIdle. + static base::Closure QuitWhenIdleClosure(); + + // Returns true if this loop is |type|. This allows subclasses (especially + // those in tests) to specialize how they are identified. + virtual bool IsType(Type type) const; + + // Returns the type passed to the constructor. + Type type() const { return type_; } + + // Optional call to connect the thread name with this loop. + void set_thread_name(const std::string& thread_name) { + DCHECK(thread_name_.empty()) << "Should not rename this thread!"; + thread_name_ = thread_name; + } + const std::string& thread_name() const { return thread_name_; } + + // Gets the message loop proxy associated with this message loop. + scoped_refptr<base::MessageLoopProxy> message_loop_proxy() { + return message_loop_proxy_.get(); + } + + // Enables or disables the recursive task processing. This happens in the case + // of recursive message loops. Some unwanted message loop may occurs when + // using common controls or printer functions. By default, recursive task + // processing is disabled. + // + // Please utilize |ScopedNestableTaskAllower| instead of calling these methods + // directly. In general nestable message loops are to be avoided. They are + // dangerous and difficult to get right, so please use with extreme caution. + // + // The specific case where tasks get queued is: + // - The thread is running a message loop. + // - It receives a task #1 and execute it. + // - The task #1 implicitly start a message loop, like a MessageBox in the + // unit test. This can also be StartDoc or GetSaveFileName. + // - The thread receives a task #2 before or while in this second message + // loop. + // - With NestableTasksAllowed set to true, the task #2 will run right away. + // Otherwise, it will get executed right after task #1 completes at "thread + // message loop level". + void SetNestableTasksAllowed(bool allowed); + bool NestableTasksAllowed() const; + + // Enables nestable tasks on |loop| while in scope. + class ScopedNestableTaskAllower { + public: + explicit ScopedNestableTaskAllower(MessageLoop* loop) + : loop_(loop), + old_state_(loop_->NestableTasksAllowed()) { + loop_->SetNestableTasksAllowed(true); + } + ~ScopedNestableTaskAllower() { + loop_->SetNestableTasksAllowed(old_state_); + } + + private: + MessageLoop* loop_; + bool old_state_; + }; + + // Enables or disables the restoration during an exception of the unhandled + // exception filter that was active when Run() was called. This can happen + // if some third party code call SetUnhandledExceptionFilter() and never + // restores the previous filter. + void set_exception_restoration(bool restore) { + exception_restoration_ = restore; + } + + // Returns true if we are currently running a nested message loop. + bool IsNested(); + + // A TaskObserver is an object that receives task notifications from the + // MessageLoop. + // + // NOTE: A TaskObserver implementation should be extremely fast! + class BASE_EXPORT TaskObserver { + public: + TaskObserver(); + + // This method is called before processing a task. + virtual void WillProcessTask(base::TimeTicks time_posted) = 0; + + // This method is called after processing a task. + virtual void DidProcessTask(base::TimeTicks time_posted) = 0; + + protected: + virtual ~TaskObserver(); + }; + + // These functions can only be called on the same thread that |this| is + // running on. + void AddTaskObserver(TaskObserver* task_observer); + void RemoveTaskObserver(TaskObserver* task_observer); + + // Returns true if the message loop has high resolution timers enabled. + // Provided for testing. + bool high_resolution_timers_enabled() { +#if defined(OS_WIN) + return !high_resolution_timer_expiration_.is_null(); +#else + return true; +#endif + } + + // When we go into high resolution timer mode, we will stay in hi-res mode + // for at least 1s. + static const int kHighResolutionTimerModeLeaseTimeMs = 1000; + + // Asserts that the MessageLoop is "idle". + void AssertIdle() const; + +#if defined(OS_WIN) + void set_os_modal_loop(bool os_modal_loop) { + os_modal_loop_ = os_modal_loop; + } + + bool os_modal_loop() const { + return os_modal_loop_; + } +#endif // OS_WIN + + // Can only be called from the thread that owns the MessageLoop. + bool is_running() const; + + int id() { return id_; } + + //---------------------------------------------------------------------------- + protected: + friend class base::RunLoop; + +#if defined(OS_WIN) + base::MessagePumpWin* pump_win() { + return static_cast<base::MessagePumpWin*>(pump_.get()); + } +#elif defined(OS_POSIX) && !defined(OS_IOS) && \ + (!defined(__LB_SHELL__) || defined(__LB_ANDROID__)) + base::MessagePumpLibevent* pump_libevent() { + return static_cast<base::MessagePumpLibevent*>(pump_.get()); + } +#endif + + // 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() + // flag invoking respectively RunInternalInSEHFrame() or RunInternal(). + void RunHandler(); + +#if defined(OS_WIN) + __declspec(noinline) void RunInternalInSEHFrame(); +#endif + + // A surrounding stack frame around the running of the message loop that + // supports all saving and restoring of state, as is needed for any/all (ugly) + // recursive calls. + void RunInternal(); + + // Called to process any delayed non-nestable tasks. + bool ProcessNextDelayedNonNestableTask(); + + // Runs the specified PendingTask. + void RunTask(const base::PendingTask& pending_task); + + // Calls RunTask or queues the pending_task on the deferred task list if it + // cannot be run right now. Returns true if the task was run. + bool DeferOrRunPendingTask(const base::PendingTask& pending_task); + + // Adds the pending task to delayed_work_queue_. + void AddToDelayedWorkQueue(const base::PendingTask& pending_task); + + // Adds the pending task to our incoming_queue_. + // + // Caller retains ownership of |pending_task|, but this function will + // reset the value of pending_task->task. This is needed to ensure + // that the posting call stack does not retain pending_task->task + // beyond this function call. + void AddToIncomingQueue(base::PendingTask* pending_task); + + // Load tasks from the incoming_queue_ into work_queue_ if the latter is + // empty. The former requires a lock to access, while the latter is directly + // accessible on this thread. + void ReloadWorkQueue(); + + // Delete tasks that haven't run yet without running them. Used in the + // destructor to make sure all the task's destructors get called. Returns + // true if some work was done. + bool DeletePendingTasks(); + + // Calculates the time at which a PendingTask should run. + base::TimeTicks CalculateDelayedRuntime(base::TimeDelta delay); + + // Start recording histogram info about events and action IF it was enabled + // and IF the statistics recorder can accept a registration of our histogram. + void StartHistogrammer(); + + // Add occurrence of event to our histogram, so that we can see what is being + // done in a specific MessageLoop instance (i.e., specific thread). + // If message_histogram_ is NULL, this is a no-op. + void HistogramEvent(int event); + + // base::MessagePump::Delegate methods: + virtual bool DoWork() OVERRIDE; + virtual bool DoDelayedWork(base::TimeTicks* next_delayed_work_time) OVERRIDE; + virtual bool DoIdleWork() OVERRIDE; + + 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. + base::TaskQueue work_queue_; + + // Contains delayed tasks, sorted by their 'delayed_run_time' property. + base::DelayedTaskQueue delayed_work_queue_; + + // A recent snapshot of Time::Now(), used to check delayed_work_queue_. + base::TimeTicks recent_time_; + + // A queue of non-nestable tasks that we had to defer because when it came + // time to execute them we were in a nested message loop. They will execute + // once we're out of nested message loops. + base::TaskQueue deferred_non_nestable_work_queue_; + + scoped_refptr<base::MessagePump> pump_; + + ObserverList<DestructionObserver> destruction_observers_; + + // A recursion block that prevents accidentally running additional tasks when + // insider a (accidentally induced?) nested message pump. + bool nestable_tasks_allowed_; + + bool exception_restoration_; + + std::string thread_name_; + // A profiling histogram showing the counts of various messages and events. + base::Histogram* message_histogram_; + + // A null terminated list which creates an incoming_queue of tasks that are + // acquired under a mutex for processing on this instance's thread. These + // tasks have not yet been sorted out into items for our work_queue_ vs items + // that will be handled by the TimerManager. + base::TaskQueue incoming_queue_; + // Protect access to incoming_queue_. + mutable base::Lock incoming_queue_lock_; + + base::RunLoop* run_loop_; + +#if defined(OS_WIN) + base::TimeTicks high_resolution_timer_expiration_; + // Should be set to true before calling Windows APIs like TrackPopupMenu, etc + // which enter a modal message loop. + bool os_modal_loop_; +#endif + + // The next sequence number to use for delayed tasks. Updating this counter is + // protected by incoming_queue_lock_. + int next_sequence_num_; + + ObserverList<TaskObserver> task_observers_; + + // The message loop proxy associated with this message loop, if one exists. + scoped_refptr<base::MessageLoopProxy> message_loop_proxy_; + scoped_ptr<base::ThreadTaskRunnerHandle> thread_task_runner_handle_; + + // A unique ID (over time) identifying this message loop. + int id_; + + private: + template <class T, class R> friend class base::subtle::DeleteHelperInternal; + template <class T, class R> friend class base::subtle::ReleaseHelperInternal; + + void DeleteSoonInternal(const tracked_objects::Location& from_here, + void(*deleter)(const void*), + const void* object); + void ReleaseSoonInternal(const tracked_objects::Location& from_here, + void(*releaser)(const void*), + const void* object); + + DISALLOW_COPY_AND_ASSIGN(MessageLoop); +}; + +//----------------------------------------------------------------------------- +// MessageLoopForUI extends MessageLoop with methods that are particular to a +// MessageLoop instantiated with TYPE_UI. +// +// This class is typically used like so: +// MessageLoopForUI::current()->...call some method... +// +class BASE_EXPORT MessageLoopForUI : public MessageLoop { + public: +#if defined(OS_WIN) + typedef base::MessagePumpForUI::MessageFilter MessageFilter; +#endif + + MessageLoopForUI() : MessageLoop(TYPE_UI) { + } + + // Returns the MessageLoopForUI of the current thread. + static MessageLoopForUI* current() { + MessageLoop* loop = MessageLoop::current(); + DCHECK(loop); + DCHECK_EQ(MessageLoop::TYPE_UI, loop->type()); + 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 + // PostTask() to work. + void Attach(); +#endif + +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) || defined(OS_STARBOARD) + // On Android, the UI message loop is handled by Java side. So Run() should + // never be called. Instead use Start(), which will forward all the native UI + // events to the Java message loop. + // Also Starboard. + void Start(); +#elif !defined(OS_MACOSX) + // Please see message_pump_win/message_pump_glib for definitions of these + // methods. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + +#if defined(OS_WIN) + // Plese see MessagePumpForUI for definitions of this method. + void SetMessageFilter(scoped_ptr<MessageFilter> message_filter) { + pump_ui()->SetMessageFilter(message_filter.Pass()); + } +#endif + + protected: +#if defined(USE_AURA) && defined(USE_X11) && !defined(OS_NACL) + friend class base::MessagePumpAuraX11; +#endif + + // TODO(rvargas): Make this platform independent. + base::MessagePumpForUI* pump_ui() { + return static_cast<base::MessagePumpForUI*>(pump_.get()); + } +#endif // !defined(OS_MACOSX) +}; + +// Do not add any member variables to MessageLoopForUI! This is important b/c +// MessageLoopForUI is often allocated via MessageLoop(TYPE_UI). Any extra +// data that you need should be stored on the MessageLoop's pump_ instance. +COMPILE_ASSERT(sizeof(MessageLoop) == sizeof(MessageLoopForUI), + MessageLoopForUI_should_not_have_extra_member_variables); + +//----------------------------------------------------------------------------- +// MessageLoopForIO extends MessageLoop with methods that are particular to a +// MessageLoop instantiated with TYPE_IO. +// +// This class is typically used like so: +// MessageLoopForIO::current()->...call some method... +// +class BASE_EXPORT MessageLoopForIO : public MessageLoop { + public: +#if defined(OS_WIN) + typedef base::MessagePumpForIO::IOHandler IOHandler; + typedef base::MessagePumpForIO::IOContext IOContext; + typedef base::MessagePumpForIO::IOObserver IOObserver; +#elif defined(OS_STARBOARD) + typedef base::MessagePumpIOStarboard::Watcher Watcher; + typedef base::MessagePumpIOStarboard::SocketWatcher SocketWatcher; + typedef base::MessagePumpIOStarboard::IOObserver IOObserver; + + enum Mode{WATCH_READ = base::MessagePumpIOStarboard::WATCH_READ, + WATCH_WRITE = base::MessagePumpIOStarboard::WATCH_WRITE, + WATCH_READ_WRITE = base::MessagePumpIOStarboard::WATCH_READ_WRITE}; +#elif defined(__LB_SHELL__) && !defined(__LB_ANDROID__) + typedef base::MessagePumpShell::Watcher Watcher; + typedef base::MessagePumpShell::FileDescriptorWatcher FileDescriptorWatcher; + typedef base::MessagePumpShell::IOObserver IOObserver; + + enum Mode { + WATCH_READ = base::MessagePumpShell::WATCH_READ, + WATCH_WRITE = base::MessagePumpShell::WATCH_WRITE, + WATCH_READ_WRITE = base::MessagePumpShell::WATCH_READ_WRITE + }; +#elif defined(OS_IOS) + typedef base::MessagePumpIOSForIO::Watcher Watcher; + typedef base::MessagePumpIOSForIO::FileDescriptorWatcher + FileDescriptorWatcher; + typedef base::MessagePumpIOSForIO::IOObserver IOObserver; + + enum Mode { + WATCH_READ = base::MessagePumpIOSForIO::WATCH_READ, + WATCH_WRITE = base::MessagePumpIOSForIO::WATCH_WRITE, + WATCH_READ_WRITE = base::MessagePumpIOSForIO::WATCH_READ_WRITE + }; +#elif defined(OS_POSIX) + typedef base::MessagePumpLibevent::Watcher Watcher; + typedef base::MessagePumpLibevent::FileDescriptorWatcher + FileDescriptorWatcher; + typedef base::MessagePumpLibevent::IOObserver IOObserver; + + enum Mode { + WATCH_READ = base::MessagePumpLibevent::WATCH_READ, + WATCH_WRITE = base::MessagePumpLibevent::WATCH_WRITE, + WATCH_READ_WRITE = base::MessagePumpLibevent::WATCH_READ_WRITE + }; + +#endif + + MessageLoopForIO() : MessageLoop(TYPE_IO) { + } + + // Returns the MessageLoopForIO of the current thread. + static MessageLoopForIO* current() { + MessageLoop* loop = MessageLoop::current(); + DCHECK_EQ(MessageLoop::TYPE_IO, loop->type()); + return static_cast<MessageLoopForIO*>(loop); + } + + void AddIOObserver(IOObserver* io_observer) { + pump_io()->AddIOObserver(io_observer); + } + + void RemoveIOObserver(IOObserver* io_observer) { + pump_io()->RemoveIOObserver(io_observer); + } + +#if defined(OS_WIN) + // Please see MessagePumpWin for definitions of these methods. + void RegisterIOHandler(HANDLE file, IOHandler* handler); + bool RegisterJobObject(HANDLE job, IOHandler* handler); + bool WaitForIOCompletion(DWORD timeout, IOHandler* filter); + + protected: + // TODO(rvargas): Make this platform independent. + base::MessagePumpForIO* pump_io() { + return static_cast<base::MessagePumpForIO*>(pump_.get()); + } + +#elif defined(OS_STARBOARD) + bool Watch(SbSocket socket, + bool persistent, + int mode, + SocketWatcher* controller, + Watcher* delegate) { + return pump_io()->Watch(socket, persistent, mode, controller, delegate); + } + + protected: + base::MessagePumpIOStarboard* pump_io() { + return static_cast<base::MessagePumpIOStarboard*>(pump_.get()); + } + +#elif defined(__LB_SHELL__) && !defined(__LB_ANDROID__) + bool WatchSocket(int sock, + bool persistent, + Mode mode, + FileDescriptorWatcher *controller, + Watcher *delegate); + + protected: + base::MessagePumpForIO* pump_io() { + return static_cast<base::MessagePumpForIO*>(pump_.get()); + } + +#elif defined(OS_IOS) + // Please see MessagePumpIOSForIO for definition. + bool WatchFileDescriptor(int fd, + bool persistent, + Mode mode, + FileDescriptorWatcher *controller, + Watcher *delegate); + + private: + base::MessagePumpIOSForIO* pump_io() { + return static_cast<base::MessagePumpIOSForIO*>(pump_.get()); + } + +#elif defined(OS_POSIX) + // Please see MessagePumpLibevent for definition. + bool WatchFileDescriptor(int fd, + bool persistent, + Mode mode, + FileDescriptorWatcher* controller, + Watcher* delegate); + + private: + base::MessagePumpLibevent* pump_io() { + return static_cast<base::MessagePumpLibevent*>(pump_.get()); + } +#endif // defined(OS_POSIX) +}; + +// Do not add any member variables to MessageLoopForIO! This is important b/c +// MessageLoopForIO is often allocated via MessageLoop(TYPE_IO). Any extra +// data that you need should be stored on the MessageLoop's pump_ instance. +COMPILE_ASSERT(sizeof(MessageLoop) == sizeof(MessageLoopForIO), + MessageLoopForIO_should_not_have_extra_member_variables); + +#endif // BASE_MESSAGE_LOOP_H_
diff --git a/src/base/message_loop_proxy.cc b/src/base/message_loop_proxy.cc new file mode 100644 index 0000000..755564b --- /dev/null +++ b/src/base/message_loop_proxy.cc
@@ -0,0 +1,17 @@ +// 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/message_loop_proxy.h" + +#include "base/bind.h" + +namespace base { + +MessageLoopProxy::MessageLoopProxy() { +} + +MessageLoopProxy::~MessageLoopProxy() { +} + +} // namespace base
diff --git a/src/base/message_loop_proxy.h b/src/base/message_loop_proxy.h new file mode 100644 index 0000000..ce949dd --- /dev/null +++ b/src/base/message_loop_proxy.h
@@ -0,0 +1,38 @@ +// 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_MESSAGE_LOOP_PROXY_H_ +#define BASE_MESSAGE_LOOP_PROXY_H_ + +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" + +namespace base { + +// This class provides a thread-safe refcounted interface to the Post* methods +// of a message loop. This class can outlive the target message loop. +// MessageLoopProxy objects are constructed automatically for all MessageLoops. +// So, to access them, you can use any of the following: +// Thread::message_loop_proxy() +// MessageLoop::current()->message_loop_proxy() +// MessageLoopProxy::current() +// +// TODO(akalin): Now that we have the *TaskRunner interfaces, we can +// merge this with MessageLoopProxyImpl. +class BASE_EXPORT MessageLoopProxy : public SingleThreadTaskRunner { + public: + // Gets the MessageLoopProxy for the current message loop, creating one if + // needed. + static scoped_refptr<MessageLoopProxy> current(); + + protected: + MessageLoopProxy(); + virtual ~MessageLoopProxy(); +}; + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_PROXY_H_
diff --git a/src/base/message_loop_proxy_impl.cc b/src/base/message_loop_proxy_impl.cc new file mode 100644 index 0000000..c3efe26 --- /dev/null +++ b/src/base/message_loop_proxy_impl.cc
@@ -0,0 +1,86 @@ +// 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/message_loop_proxy_impl.h" + +#include "base/location.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +MessageLoopProxyImpl::~MessageLoopProxyImpl() { +} + +bool MessageLoopProxyImpl::PostDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) { + return PostTaskHelper(from_here, task, delay, true); +} + +bool MessageLoopProxyImpl::PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) { + return PostTaskHelper(from_here, task, delay, false); +} + +bool MessageLoopProxyImpl::RunsTasksOnCurrentThread() const { + return thread_id_ == PlatformThread::CurrentId(); +} + +// MessageLoop::DestructionObserver implementation +void MessageLoopProxyImpl::WillDestroyCurrentMessageLoop() { + AutoLock lock(message_loop_lock_); + target_message_loop_ = NULL; +} + +void MessageLoopProxyImpl::OnDestruct() const { + // We shouldn't use MessageLoop::current() since it uses LazyInstance which + // may be deleted by ~AtExitManager when a WorkerPool thread calls this + // function. + // http://crbug.com/63678 + bool delete_later = false; + { + AutoLock lock(message_loop_lock_); + if (target_message_loop_) { + base::ThreadRestrictions::ScopedAllowSingleton allow_singleton; + if (MessageLoop::current() != target_message_loop_) { + target_message_loop_->DeleteSoon(FROM_HERE, this); + delete_later = true; + } + } + } + if (!delete_later) + delete this; +} + +MessageLoopProxyImpl::MessageLoopProxyImpl() + : thread_id_(PlatformThread::CurrentId()), + target_message_loop_(MessageLoop::current()) {} + +bool MessageLoopProxyImpl::PostTaskHelper( + const tracked_objects::Location& from_here, const base::Closure& task, + base::TimeDelta delay, bool nestable) { + AutoLock lock(message_loop_lock_); + if (target_message_loop_) { + if (nestable) { + target_message_loop_->PostDelayedTask(from_here, task, delay); + } else { + target_message_loop_->PostNonNestableDelayedTask(from_here, task, delay); + } + return true; + } + return false; +} + +scoped_refptr<MessageLoopProxy> +MessageLoopProxy::current() { + MessageLoop* cur_loop = MessageLoop::current(); + if (!cur_loop) + return NULL; + return cur_loop->message_loop_proxy(); +} + +} // namespace base
diff --git a/src/base/message_loop_proxy_impl.h b/src/base/message_loop_proxy_impl.h new file mode 100644 index 0000000..a01b415 --- /dev/null +++ b/src/base/message_loop_proxy_impl.h
@@ -0,0 +1,65 @@ +// 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_MESSAGE_LOOP_PROXY_IMPL_H_ +#define BASE_MESSAGE_LOOP_PROXY_IMPL_H_ + +#include "base/base_export.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/synchronization/lock.h" +#include "base/threading/platform_thread.h" + +namespace base { + +// A stock implementation of MessageLoopProxy that is created and managed by a +// MessageLoop. For now a MessageLoopProxyImpl can only be created as part of a +// MessageLoop. +class BASE_EXPORT MessageLoopProxyImpl : public MessageLoopProxy { + public: + // MessageLoopProxy implementation + virtual bool PostDelayedTask(const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) OVERRIDE; + virtual bool PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) OVERRIDE; + virtual bool RunsTasksOnCurrentThread() const OVERRIDE; + + protected: + virtual ~MessageLoopProxyImpl(); + + // Override OnDestruct so that we can delete the object on the target message + // loop if it still exists. + virtual void OnDestruct() const OVERRIDE; + + private: + // Allow the MessageLoop to create a MessageLoopProxyImpl. + friend class ::MessageLoop; + friend class DeleteHelper<MessageLoopProxyImpl>; + + MessageLoopProxyImpl(); + + // Called directly by MessageLoop::~MessageLoop. + virtual void WillDestroyCurrentMessageLoop(); + + + bool PostTaskHelper(const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay, + bool nestable); + + PlatformThreadId thread_id_; + + // The lock that protects access to target_message_loop_. + mutable base::Lock message_loop_lock_; + MessageLoop* target_message_loop_; + + DISALLOW_COPY_AND_ASSIGN(MessageLoopProxyImpl); +}; + +} // namespace base + +#endif // BASE_MESSAGE_LOOP_PROXY_IMPL_H_
diff --git a/src/base/message_loop_proxy_impl_unittest.cc b/src/base/message_loop_proxy_impl_unittest.cc new file mode 100644 index 0000000..e97d8f1 --- /dev/null +++ b/src/base/message_loop_proxy_impl_unittest.cc
@@ -0,0 +1,126 @@ +// 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/message_loop_proxy_impl.h" + +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + + +class MessageLoopProxyImplTest : public testing::Test { + public: + void Release() const { + AssertOnIOThread(); + Quit(); + } + + void Quit() const { + loop_.PostTask(FROM_HERE, MessageLoop::QuitClosure()); + } + + void AssertOnIOThread() const { + ASSERT_TRUE(io_thread_->message_loop_proxy()->BelongsToCurrentThread()); + ASSERT_EQ(io_thread_->message_loop_proxy(), + base::MessageLoopProxy::current()); + } + + void AssertOnFileThread() const { + ASSERT_TRUE(file_thread_->message_loop_proxy()->BelongsToCurrentThread()); + ASSERT_EQ(file_thread_->message_loop_proxy(), + base::MessageLoopProxy::current()); + } + + protected: + virtual void SetUp() OVERRIDE { + io_thread_.reset(new base::Thread("MessageLoopProxyImplTest_IO")); + file_thread_.reset(new base::Thread("MessageLoopProxyImplTest_File")); + io_thread_->Start(); + file_thread_->Start(); + } + + virtual void TearDown() OVERRIDE { + io_thread_->Stop(); + file_thread_->Stop(); + } + + static void BasicFunction(MessageLoopProxyImplTest* test) { + test->AssertOnFileThread(); + test->Quit(); + } + + static void AssertNotRun() { + FAIL() << "Callback Should not get executed."; + } + + class DeletedOnFile { + public: + explicit DeletedOnFile(MessageLoopProxyImplTest* test) : test_(test) {} + + ~DeletedOnFile() { + test_->AssertOnFileThread(); + test_->Quit(); + } + + private: + MessageLoopProxyImplTest* test_; + }; + + scoped_ptr<base::Thread> io_thread_; + scoped_ptr<base::Thread> file_thread_; + + private: + mutable MessageLoop loop_; +}; + +TEST_F(MessageLoopProxyImplTest, Release) { + EXPECT_TRUE(io_thread_->message_loop_proxy()->ReleaseSoon(FROM_HERE, this)); + MessageLoop::current()->Run(); +} + +TEST_F(MessageLoopProxyImplTest, Delete) { + DeletedOnFile* deleted_on_file = new DeletedOnFile(this); + EXPECT_TRUE(file_thread_->message_loop_proxy()->DeleteSoon( + FROM_HERE, deleted_on_file)); + MessageLoop::current()->Run(); +} + +TEST_F(MessageLoopProxyImplTest, PostTask) { + EXPECT_TRUE(file_thread_->message_loop_proxy()->PostTask( + FROM_HERE, base::Bind(&MessageLoopProxyImplTest::BasicFunction, + base::Unretained(this)))); + MessageLoop::current()->Run(); +} + +TEST_F(MessageLoopProxyImplTest, PostTaskAfterThreadExits) { + scoped_ptr<base::Thread> test_thread( + new base::Thread("MessageLoopProxyImplTest_Dummy")); + test_thread->Start(); + scoped_refptr<base::MessageLoopProxy> message_loop_proxy = + test_thread->message_loop_proxy(); + test_thread->Stop(); + + bool ret = message_loop_proxy->PostTask( + FROM_HERE, + base::Bind(&MessageLoopProxyImplTest::AssertNotRun)); + EXPECT_FALSE(ret); +} + +TEST_F(MessageLoopProxyImplTest, PostTaskAfterThreadIsDeleted) { + scoped_refptr<base::MessageLoopProxy> message_loop_proxy; + { + scoped_ptr<base::Thread> test_thread( + new base::Thread("MessageLoopProxyImplTest_Dummy")); + test_thread->Start(); + message_loop_proxy = test_thread->message_loop_proxy(); + } + bool ret = message_loop_proxy->PostTask( + FROM_HERE, + base::Bind(&MessageLoopProxyImplTest::AssertNotRun)); + EXPECT_FALSE(ret); +}
diff --git a/src/base/message_loop_proxy_unittest.cc b/src/base/message_loop_proxy_unittest.cc new file mode 100644 index 0000000..4776502 --- /dev/null +++ b/src/base/message_loop_proxy_unittest.cc
@@ -0,0 +1,266 @@ +// 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/message_loop_proxy.h" + +#include "base/atomic_sequence_num.h" +#include "base/bind.h" +#include "base/debug/leak_annotations.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +class MessageLoopProxyTest : public testing::Test { + public: + MessageLoopProxyTest() + : current_loop_(new MessageLoop()), + task_thread_("task_thread"), + thread_sync_(true, false) { + } + + void DeleteCurrentMessageLoop() { + current_loop_.reset(); + } + + protected: + virtual void SetUp() OVERRIDE { + // Use SetUp() instead of the constructor to avoid posting a task to a + // partialy constructed object. + task_thread_.Start(); + + // Allow us to pause the |task_thread_|'s MessageLoop. + task_thread_.message_loop()->PostTask( + FROM_HERE, + Bind(&MessageLoopProxyTest::BlockTaskThreadHelper, Unretained(this))); + } + + virtual void TearDown() OVERRIDE { + // Make sure the |task_thread_| is not blocked, and stop the thread + // fully before destuction because its tasks may still depend on the + // |thread_sync_| event. + thread_sync_.Signal(); + task_thread_.Stop(); + DeleteCurrentMessageLoop(); + } + + // Make LoopRecorder threadsafe so that there is defined behavior even if a + // threading mistake sneaks into the PostTaskAndReplyRelay implementation. + class LoopRecorder : public RefCountedThreadSafe<LoopRecorder> { + public: + LoopRecorder(MessageLoop** run_on, MessageLoop** deleted_on, + int* destruct_order) + : run_on_(run_on), + deleted_on_(deleted_on), + destruct_order_(destruct_order) { + } + + void RecordRun() { + *run_on_ = MessageLoop::current(); + } + + private: + friend class RefCountedThreadSafe<LoopRecorder>; + ~LoopRecorder() { + *deleted_on_ = MessageLoop::current(); + *destruct_order_ = g_order.GetNext(); + } + + MessageLoop** run_on_; + MessageLoop** deleted_on_; + int* destruct_order_; + }; + + static void RecordLoop(scoped_refptr<LoopRecorder> recorder) { + recorder->RecordRun(); + } + + static void RecordLoopAndQuit(scoped_refptr<LoopRecorder> recorder) { + recorder->RecordRun(); + MessageLoop::current()->Quit(); + } + + void UnblockTaskThread() { + thread_sync_.Signal(); + } + + void BlockTaskThreadHelper() { + thread_sync_.Wait(); + } + + static StaticAtomicSequenceNumber g_order; + + scoped_ptr<MessageLoop> current_loop_; + Thread task_thread_; + + private: + base::WaitableEvent thread_sync_; +}; + +StaticAtomicSequenceNumber MessageLoopProxyTest::g_order; + +TEST_F(MessageLoopProxyTest, PostTaskAndReply_Basic) { + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr<LoopRecorder> task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr<LoopRecorder> reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + ASSERT_TRUE(task_thread_.message_loop_proxy()->PostTaskAndReply( + FROM_HERE, + Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder))); + + // Die if base::Bind doesn't retain a reference to the recorders. + task_recoder = NULL; + reply_recoder = NULL; + ASSERT_FALSE(task_deleted_on); + ASSERT_FALSE(reply_deleted_on); + + UnblockTaskThread(); + current_loop_->Run(); + + EXPECT_EQ(task_thread_.message_loop(), task_run_on); + EXPECT_EQ(current_loop_.get(), task_deleted_on); + EXPECT_EQ(current_loop_.get(), reply_run_on); + EXPECT_EQ(current_loop_.get(), reply_deleted_on); + EXPECT_LT(task_delete_order, reply_delete_order); +} + +TEST_F(MessageLoopProxyTest, PostTaskAndReplyOnDeletedThreadDoesNotLeak) { + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr<LoopRecorder> task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr<LoopRecorder> reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + // Grab a MessageLoopProxy to a dead MessageLoop. + scoped_refptr<MessageLoopProxy> task_loop_proxy = + task_thread_.message_loop_proxy(); + UnblockTaskThread(); + task_thread_.Stop(); + + ASSERT_FALSE(task_loop_proxy->PostTaskAndReply( + FROM_HERE, + Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder))); + + // The relay should have properly deleted its resources leaving us as the only + // reference. + EXPECT_EQ(task_delete_order, reply_delete_order); + ASSERT_TRUE(task_recoder->HasOneRef()); + ASSERT_TRUE(reply_recoder->HasOneRef()); + + // Nothing should have run though. + EXPECT_FALSE(task_run_on); + EXPECT_FALSE(reply_run_on); +} + +TEST_F(MessageLoopProxyTest, PostTaskAndReply_SameLoop) { + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr<LoopRecorder> task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr<LoopRecorder> reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + // Enqueue the relay. + ASSERT_TRUE(current_loop_->message_loop_proxy()->PostTaskAndReply( + FROM_HERE, + Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder))); + + // Die if base::Bind doesn't retain a reference to the recorders. + task_recoder = NULL; + reply_recoder = NULL; + ASSERT_FALSE(task_deleted_on); + ASSERT_FALSE(reply_deleted_on); + + current_loop_->Run(); + + EXPECT_EQ(current_loop_.get(), task_run_on); + EXPECT_EQ(current_loop_.get(), task_deleted_on); + EXPECT_EQ(current_loop_.get(), reply_run_on); + EXPECT_EQ(current_loop_.get(), reply_deleted_on); + EXPECT_LT(task_delete_order, reply_delete_order); +} + +TEST_F(MessageLoopProxyTest, PostTaskAndReply_DeadReplyLoopDoesNotDelete) { + // Annotate the scope as having memory leaks to suppress heapchecker reports. + ANNOTATE_SCOPED_MEMORY_LEAK; + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr<LoopRecorder> task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr<LoopRecorder> reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + // Enqueue the relay. + task_thread_.message_loop_proxy()->PostTaskAndReply( + FROM_HERE, + Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder)); + + // Die if base::Bind doesn't retain a reference to the recorders. + task_recoder = NULL; + reply_recoder = NULL; + ASSERT_FALSE(task_deleted_on); + ASSERT_FALSE(reply_deleted_on); + + UnblockTaskThread(); + + // Mercilessly whack the current loop before |reply| gets to run. + current_loop_.reset(); + + // This should ensure the relay has been run. We need to record the + // MessageLoop pointer before stopping the thread because Thread::Stop() will + // NULL out its own pointer. + MessageLoop* task_loop = task_thread_.message_loop(); + task_thread_.Stop(); + + EXPECT_EQ(task_loop, task_run_on); + ASSERT_FALSE(task_deleted_on); + EXPECT_FALSE(reply_run_on); + ASSERT_FALSE(reply_deleted_on); + EXPECT_EQ(task_delete_order, reply_delete_order); + + // The PostTaskAndReplyRelay is leaked here. Even if we had a reference to + // it, we cannot just delete it because PostTaskAndReplyRelay's destructor + // checks that MessageLoop::current() is the the same as when the + // PostTaskAndReplyRelay object was constructed. However, this loop must have + // aleady been deleted in order to perform this test. See + // http://crbug.com/86301. +} + +} // namespace + +} // namespace base
diff --git a/src/base/message_loop_unittest.cc b/src/base/message_loop_unittest.cc new file mode 100644 index 0000000..b4bf2f3 --- /dev/null +++ b/src/base/message_loop_unittest.cc
@@ -0,0 +1,2152 @@ +// 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 <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop.h" +#include "base/posix/eintr_wrapper.h" +#include "base/run_loop.h" +#include "base/thread_task_runner_handle.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include "base/message_pump_win.h" +#include "base/win/scoped_handle.h" +#endif + +using base::PlatformThread; +using base::Thread; +using base::Time; +using base::TimeDelta; +using base::TimeTicks; + +// TODO(darin): Platform-specific MessageLoop tests should be grouped together +// to avoid chopping this file up with so many #ifdefs. + +namespace { + +class Foo : public base::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 base::RefCounted<Foo>; + + ~Foo() {} + + int test_count_; + 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, base::Bind( + &Foo::Test0, foo.get())); + MessageLoop::current()->PostTask(FROM_HERE, base::Bind( + &Foo::Test1ConstRef, foo.get(), a)); + MessageLoop::current()->PostTask(FROM_HERE, base::Bind( + &Foo::Test1Ptr, foo.get(), &b)); + MessageLoop::current()->PostTask(FROM_HERE, base::Bind( + &Foo::Test1Int, foo.get(), 100)); + MessageLoop::current()->PostTask(FROM_HERE, base::Bind( + &Foo::Test2Ptr, foo.get(), &a, &c)); + MessageLoop::current()->PostTask(FROM_HERE, base::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, base::Bind( + &MessageLoop::Quit, base::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, base::Bind( + &Foo::Test0, foo.get())); + MessageLoop::current()->PostTask(FROM_HERE, base::Bind( + &Foo::Test1ConstRef, foo.get(), a)); + MessageLoop::current()->PostTask(FROM_HERE, base::Bind( + &Foo::Test1Ptr, foo.get(), &b)); + MessageLoop::current()->PostTask(FROM_HERE, base::Bind( + &Foo::Test1Int, foo.get(), 100)); + MessageLoop::current()->PostTask(FROM_HERE, base::Bind( + &Foo::Test2Ptr, foo.get(), &a, &c)); + MessageLoop::current()->PostTask(FROM_HERE, base::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, base::Bind( + &MessageLoop::Quit, base::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"); +} + +// This function runs slowly to simulate a large amount of work being done. +static void SlowFunc(TimeDelta pause, int* quit_counter) { + PlatformThread::Sleep(pause); + if (--(*quit_counter) == 0) + MessageLoop::current()->Quit(); +} + +// This function records the time when Run was called in a Time object, which is +// useful for building a variety of MessageLoop tests. +static 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); +} + +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, base::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, + base::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, + base::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, + base::Bind(&RecordRunTimeFunc, &run_time1, &num_tasks), kDelay); + loop.PostDelayedTask( + FROM_HERE, + base::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, base::Bind(&SlowFunc, kPause, &num_tasks)); + loop.PostDelayedTask( + FROM_HERE, + base::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, + base::Bind(&RecordRunTimeFunc, &run_time1, &num_tasks)); + + loop.PostDelayedTask( + FROM_HERE, base::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, + base::Bind(&RecordRunTimeFunc, &run_time1, &num_tasks), + TimeDelta::FromSeconds(1000)); + loop.PostDelayedTask( + FROM_HERE, + base::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)); + loop.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; + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + MessageLoop::current()->Quit(); +} + +void RunTest_PostDelayedTask_SharedTimer_SubPump() { + MessageLoop loop(MessageLoop::TYPE_UI); + + // 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_time; + + loop.PostTask(FROM_HERE, base::Bind(&SubPumpFunc)); + + // This very delayed task should never run. + loop.PostDelayedTask( + FROM_HERE, + base::Bind(&RecordRunTimeFunc, &run_time, &num_tasks), + TimeDelta::FromSeconds(1000)); + + // This slightly delayed task should run from within SubPumpFunc). + loop.PostDelayedTask( + FROM_HERE, + base::Bind(&PostQuitMessage, 0), + TimeDelta::FromMilliseconds(10)); + + Time start_time = Time::Now(); + + loop.Run(); + EXPECT_EQ(1, 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)); + loop.RunUntilIdle(); + + 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 base::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 base::RefCounted<RecordDeletionProbe>; + + ~RecordDeletionProbe() { + *was_deleted_ = true; + if (post_on_delete_) + MessageLoop::current()->PostTask( + FROM_HERE, + base::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, base::Bind(&RecordDeletionProbe::Run, + new RecordDeletionProbe(NULL, &a_was_deleted))); + // TODO(ajwong): Do we really need 1000ms here? + loop.PostDelayedTask( + FROM_HERE, base::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, base::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, + base::Bind(&NestingFunc, depth)); + + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->Run(); + } + MessageLoop::current()->Quit(); +} + +#if defined(OS_WIN) + +LONG WINAPI BadExceptionHandler(EXCEPTION_POINTERS *ex_info) { + ADD_FAILURE() << "bad exception handler"; + ::ExitProcess(ex_info->ExceptionRecord->ExceptionCode); + return EXCEPTION_EXECUTE_HANDLER; +} + +// This task throws an SEH exception: initially write to an invalid address. +// If the right SEH filter is installed, it will fix the error. +class Crasher : public base::RefCounted<Crasher> { + public: + // Ctor. If trash_SEH_handler is true, the task will override the unhandled + // exception handler with one sure to crash this test. + explicit Crasher(bool trash_SEH_handler) + : trash_SEH_handler_(trash_SEH_handler) { + } + + void Run() { + PlatformThread::Sleep(TimeDelta::FromMilliseconds(1)); + if (trash_SEH_handler_) + ::SetUnhandledExceptionFilter(&BadExceptionHandler); + // Generate a SEH fault. We do it in asm to make sure we know how to undo + // the damage. + +#if defined(_M_IX86) + + __asm { + mov eax, dword ptr [Crasher::bad_array_] + mov byte ptr [eax], 66 + } + +#elif defined(_M_X64) + + bad_array_[0] = 66; + +#else +#error "needs architecture support" +#endif + + MessageLoop::current()->Quit(); + } + // Points the bad array to a valid memory location. + static void FixError() { + bad_array_ = &valid_store_; + } + + private: + bool trash_SEH_handler_; + static volatile char* bad_array_; + static char valid_store_; +}; + +volatile char* Crasher::bad_array_ = 0; +char Crasher::valid_store_ = 0; + +// This SEH filter fixes the problem and retries execution. Fixing requires +// that the last instruction: mov eax, [Crasher::bad_array_] to be retried +// so we move the instruction pointer 5 bytes back. +LONG WINAPI HandleCrasherException(EXCEPTION_POINTERS *ex_info) { + if (ex_info->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) + return EXCEPTION_EXECUTE_HANDLER; + + Crasher::FixError(); + +#if defined(_M_IX86) + + ex_info->ContextRecord->Eip -= 5; + +#elif defined(_M_X64) + + ex_info->ContextRecord->Rip -= 5; + +#endif + + return EXCEPTION_CONTINUE_EXECUTION; +} + +void RunTest_Crasher(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + if (::IsDebuggerPresent()) + return; + + LPTOP_LEVEL_EXCEPTION_FILTER old_SEH_filter = + ::SetUnhandledExceptionFilter(&HandleCrasherException); + + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&Crasher::Run, new Crasher(false))); + MessageLoop::current()->set_exception_restoration(true); + MessageLoop::current()->Run(); + MessageLoop::current()->set_exception_restoration(false); + + ::SetUnhandledExceptionFilter(old_SEH_filter); +} + +void RunTest_CrasherNasty(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + if (::IsDebuggerPresent()) + return; + + LPTOP_LEVEL_EXCEPTION_FILTER old_SEH_filter = + ::SetUnhandledExceptionFilter(&HandleCrasherException); + + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&Crasher::Run, new Crasher(true))); + MessageLoop::current()->set_exception_restoration(true); + MessageLoop::current()->Run(); + MessageLoop::current()->set_exception_restoration(false); + + ::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, + base::Bind(&NestingFunc, &depth)); + MessageLoop::current()->Run(); + EXPECT_EQ(depth, 0); +} + +#if defined(OS_WIN) +const wchar_t* const kMessageBoxTitle = L"MessageLoop Unit Test"; +#endif + +enum TaskType { + MESSAGEBOX, + ENDDIALOG, + RECURSIVE, + TIMEDMESSAGELOOP, + QUITMESSAGELOOP, + ORDERED, + PUMPS, + SLEEP, + RUNS, +}; + +// Saves the order in which the tasks executed. +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_; +}; + +// 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. +void MessageBoxFunc(TaskList* order, int cookie, bool is_reentrant) { + order->RecordStart(MESSAGEBOX, cookie); + if (is_reentrant) + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageBox(NULL, L"Please wait...", kMessageBoxTitle, MB_OK); + order->RecordEnd(MESSAGEBOX, cookie); +} + +// Will end the MessageBox. +void EndDialogFunc(TaskList* order, int cookie) { + order->RecordStart(ENDDIALOG, cookie); + HWND window = GetActiveWindow(); + if (window != NULL) { + EXPECT_NE(EndDialog(window, IDCONTINUE), 0); + // Cheap way to signal that the window wasn't found if RunEnd() isn't + // called. + order->RecordEnd(ENDDIALOG, cookie); + } +} + +#endif // defined(OS_WIN) + +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, + base::Bind(&RecursiveFunc, order, cookie, depth - 1, is_reentrant)); + } + 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()->Quit(); + 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, + TaskList* order, + bool is_reentrant) { + target->PostTask(FROM_HERE, + base::Bind(&RecursiveFunc, order, 1, 2, is_reentrant)); + target->PostTask(FROM_HERE, + base::Bind(&MessageBoxFunc, order, 2, is_reentrant)); + target->PostTask(FROM_HERE, + base::Bind(&RecursiveFunc, order, 3, 2, is_reentrant)); + // The trick here is that for recursive task processing, this task will be + // ran _inside_ the MessageBox message loop, dismissing the MessageBox + // without a chance. + // For non-recursive task processing, this will be executed _after_ the + // MessageBox will have been dismissed by the code below, where + // expect_window_ is true. + target->PostTask(FROM_HERE, + base::Bind(&EndDialogFunc, order, 4)); + target->PostTask(FROM_HERE, + base::Bind(&QuitFunc, order, 5)); + + // Enforce that every tasks are sent before starting to run the main thread + // message loop. + ASSERT_TRUE(SetEvent(event)); + + // Poll for the MessageBox. Don't do this at home! At the speed we do it, + // you will never realize one MessageBox was shown. + for (; expect_window;) { + HWND window = FindWindow(L"#32770", kMessageBoxTitle); + if (window) { + // Dismiss it. + for (;;) { + HWND button = FindWindowEx(window, NULL, L"Button", NULL); + if (button != NULL) { + EXPECT_EQ(0, SendMessage(button, WM_LBUTTONDOWN, 0, 0)); + EXPECT_EQ(0, SendMessage(button, WM_LBUTTONUP, 0, 0)); + break; + } + } + break; + } + } +} + +#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, + base::Bind(&RecursiveFunc, &order, 1, 2, false)); + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&RecursiveFunc, &order, 2, 2, false)); + MessageLoop::current()->PostTask( + FROM_HERE, + base::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, base::Bind(&RecursiveSlowFunc, &order, 1, 2, false)); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&RecursiveSlowFunc, &order, 2, 2, false)); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&OrderedFunc, &order, 3), + TimeDelta::FromMilliseconds(5)); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::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)); +#if (defined(__LB_SHELL__) && !defined(__LB_ANDROID__)) || defined(OS_STARBOARD) + if (message_loop_type == MessageLoop::TYPE_DEFAULT) { + // lbshell messagepump gives delayed tasks higher priority, which causes + // the process order to be a bit different. the messagepump src code is in + // external\chromium\base\message_pump_shell.cc, function + // void MessagePumpShell::Run(Delegate * delegate); +#endif + 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)); +#if (defined(__LB_SHELL__) && !defined(__LB_ANDROID__)) || defined(OS_STARBOARD) + } else { + EXPECT_EQ(order.Get(8), TaskItem(QUITMESSAGELOOP, 4, true)); + EXPECT_EQ(order.Get(9), TaskItem(QUITMESSAGELOOP, 4, false)); + EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 2, false)); + } +#endif + 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, base::Bind(&RecursiveFunc, &order, 1, 2, true)); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&RecursiveFunc, &order, 2, 2, true)); + MessageLoop::current()->PostTask( + FROM_HERE, base::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. + +// A side effect of this test is the generation a beep. Sorry. +void RunTest_RecursiveDenial2(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + Thread worker("RecursiveDenial2_worker"); + Thread::Options options; + options.message_loop_type = message_loop_type; + ASSERT_EQ(true, worker.StartWithOptions(options)); + TaskList order; + base::win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL)); + worker.message_loop()->PostTask(FROM_HERE, + base::Bind(&RecursiveFuncWin, + MessageLoop::current(), + event.Get(), + true, + &order, + false)); + // Let the other thread execute. + WaitForSingleObject(event, INFINITE); + MessageLoop::current()->Run(); + + ASSERT_EQ(order.Size(), 17); + EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(2), TaskItem(MESSAGEBOX, 2, true)); + EXPECT_EQ(order.Get(3), TaskItem(MESSAGEBOX, 2, false)); + EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order.Get(5), TaskItem(RECURSIVE, 3, false)); + // When EndDialogFunc is processed, the window is already dismissed, hence no + // "end" entry. + EXPECT_EQ(order.Get(6), TaskItem(ENDDIALOG, 4, true)); + EXPECT_EQ(order.Get(7), TaskItem(QUITMESSAGELOOP, 5, true)); + EXPECT_EQ(order.Get(8), TaskItem(QUITMESSAGELOOP, 5, false)); + EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 3, false)); + EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order.Get(16), TaskItem(RECURSIVE, 3, false)); +} + +// A side effect of this test is the generation a beep. Sorry. This test also +// needs to process windows messages on the current thread. +void RunTest_RecursiveSupport2(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + Thread worker("RecursiveSupport2_worker"); + Thread::Options options; + options.message_loop_type = message_loop_type; + ASSERT_EQ(true, worker.StartWithOptions(options)); + TaskList order; + base::win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL)); + worker.message_loop()->PostTask(FROM_HERE, + base::Bind(&RecursiveFuncWin, + MessageLoop::current(), + event.Get(), + false, + &order, + true)); + // Let the other thread execute. + WaitForSingleObject(event, INFINITE); + MessageLoop::current()->Run(); + + ASSERT_EQ(order.Size(), 18); + EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(2), TaskItem(MESSAGEBOX, 2, true)); + // Note that this executes in the MessageBox modal loop. + EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 3, false)); + EXPECT_EQ(order.Get(5), TaskItem(ENDDIALOG, 4, true)); + EXPECT_EQ(order.Get(6), TaskItem(ENDDIALOG, 4, false)); + EXPECT_EQ(order.Get(7), TaskItem(MESSAGEBOX, 2, false)); + /* The order can subtly change here. The reason is that when RecursiveFunc(1) + is called in the main thread, if it is faster than getting to the + PostTask(FROM_HERE, base::Bind(&QuitFunc) execution, the order of task + execution can change. We don't care anyway that the order isn't correct. + EXPECT_EQ(order.Get(8), TaskItem(QUITMESSAGELOOP, 5, true)); + EXPECT_EQ(order.Get(9), TaskItem(QUITMESSAGELOOP, 5, 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, 3, true)); + EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 3, false)); + EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(16), TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order.Get(17), TaskItem(RECURSIVE, 3, false)); +} + +#endif // defined(OS_WIN) + +void FuncThatPumps(TaskList* order, int cookie) { + order->RecordStart(PUMPS, cookie); + { + MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); + MessageLoop::current()->RunUntilIdle(); + } + order->RecordEnd(PUMPS, cookie); +} + +void FuncThatRuns(TaskList* order, int cookie, base::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, + base::Bind(&OrderedFunc, &order, 1)); + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&OrderedFunc, &order, 2)); + MessageLoop::current()->PostTask(FROM_HERE, + base::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, + base::Bind(&FuncThatPumps, &order, 1)); + if (use_delayed) { + MessageLoop::current()->PostNonNestableDelayedTask( + FROM_HERE, + base::Bind(&OrderedFunc, &order, 2), + TimeDelta::FromMilliseconds(1)); + } else { + MessageLoop::current()->PostNonNestableTask( + FROM_HERE, + base::Bind(&OrderedFunc, &order, 2)); + } + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&OrderedFunc, &order, 3)); + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&SleepFunc, &order, 4, TimeDelta::FromMilliseconds(50))); + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&OrderedFunc, &order, 5)); + if (use_delayed) { + MessageLoop::current()->PostNonNestableDelayedTask( + FROM_HERE, + base::Bind(&QuitFunc, &order, 6), + TimeDelta::FromMilliseconds(2)); + } else { + MessageLoop::current()->PostNonNestableTask( + FROM_HERE, + base::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; + + base::RunLoop run_loop; + + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&FuncThatRuns, &order, 1, base::Unretained(&run_loop))); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&OrderedFunc, &order, 2)); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&FuncThatQuitsNow)); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&OrderedFunc, &order, 3)); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&FuncThatQuitsNow)); + MessageLoop::current()->PostTask( + FROM_HERE, base::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; + + base::RunLoop run_loop; + + run_loop.Quit(); + + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&OrderedFunc, &order, 1)); // never runs + MessageLoop::current()->PostTask( + FROM_HERE, base::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; + + base::RunLoop run_loop; + + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&OrderedFunc, &order, 1)); + MessageLoop::current()->PostTask( + FROM_HERE, run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&OrderedFunc, &order, 2)); // never runs + MessageLoop::current()->PostTask( + FROM_HERE, base::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; + + base::RunLoop run_loop; + + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&FuncThatRuns, &order, 1, base::Unretained(&run_loop))); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&OrderedFunc, &order, 2)); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&FuncThatQuitsNow)); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&OrderedFunc, &order, 3)); + MessageLoop::current()->PostTask( + FROM_HERE, run_loop.QuitClosure()); // has no affect + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&OrderedFunc, &order, 4)); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&FuncThatQuitsNow)); + + base::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; + + base::RunLoop outer_run_loop; + base::RunLoop nested_run_loop; + + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&FuncThatRuns, &order, 1, base::Unretained(&nested_run_loop))); + MessageLoop::current()->PostTask( + FROM_HERE, outer_run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, base::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; + + base::RunLoop outer_run_loop; + base::RunLoop nested_run_loop; + + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&FuncThatRuns, &order, 1, base::Unretained(&nested_run_loop))); + MessageLoop::current()->PostTask( + FROM_HERE, nested_run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, base::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; + + base::RunLoop outer_run_loop; + base::RunLoop nested_run_loop; + base::RunLoop bogus_run_loop; + + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&FuncThatRuns, &order, 1, base::Unretained(&nested_run_loop))); + MessageLoop::current()->PostTask( + FROM_HERE, bogus_run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, base::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; + + base::RunLoop outer_run_loop; + base::RunLoop nested_loop1; + base::RunLoop nested_loop2; + base::RunLoop nested_loop3; + base::RunLoop nested_loop4; + + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&FuncThatRuns, &order, 1, base::Unretained(&nested_loop1))); + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&FuncThatRuns, &order, 2, base::Unretained(&nested_loop2))); + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&FuncThatRuns, &order, 3, base::Unretained(&nested_loop3))); + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&FuncThatRuns, &order, 4, base::Unretained(&nested_loop4))); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&OrderedFunc, &order, 5)); + MessageLoop::current()->PostTask( + FROM_HERE, outer_run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&OrderedFunc, &order, 6)); + MessageLoop::current()->PostTask( + FROM_HERE, nested_loop1.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&OrderedFunc, &order, 7)); + MessageLoop::current()->PostTask( + FROM_HERE, nested_loop2.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&OrderedFunc, &order, 8)); + MessageLoop::current()->PostTask( + FROM_HERE, nested_loop3.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&OrderedFunc, &order, 9)); + MessageLoop::current()->PostTask( + FROM_HERE, nested_loop4.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, base::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( + FROM_HERE, + base::Bind(&PostNTasksThenQuit, posts_remaining - 1)); + } else { + MessageLoop::current()->QuitWhenIdle(); + } +} + +void RunTest_RecursivePosts(MessageLoop::Type message_loop_type, + int num_times) { + MessageLoop loop(message_loop_type); + loop.PostTask(FROM_HERE, base::Bind(&PostNTasksThenQuit, num_times)); + loop.Run(); +} + +#if defined(OS_WIN) + +class DispatcherImpl : public MessageLoopForUI::Dispatcher { + public: + DispatcherImpl() : dispatch_count_(0) {} + + virtual bool Dispatch(const base::NativeEvent& msg) OVERRIDE { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + // Do not count WM_TIMER since it is not what we post and it will cause + // flakiness. + if (msg.message != WM_TIMER) + ++dispatch_count_; + // We treat WM_LBUTTONUP as the last message. + return msg.message != WM_LBUTTONUP; + } + + int dispatch_count_; +}; + +void MouseDownUp() { + PostMessage(NULL, WM_LBUTTONDOWN, 0, 0); + PostMessage(NULL, WM_LBUTTONUP, 'A', 0); +} + +void RunTest_Dispatcher(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&MouseDownUp), + TimeDelta::FromMilliseconds(100)); + DispatcherImpl dispatcher; + base::RunLoop run_loop(&dispatcher); + run_loop.Run(); + ASSERT_EQ(2, dispatcher.dispatch_count_); +} + +LRESULT CALLBACK MsgFilterProc(int code, WPARAM wparam, LPARAM lparam) { + if (code == base::MessagePumpForUI::kMessageFilterCode) { + MSG* msg = reinterpret_cast<MSG*>(lparam); + if (msg->message == WM_LBUTTONDOWN) + return TRUE; + } + return FALSE; +} + +void RunTest_DispatcherWithMessageHook(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&MouseDownUp), + TimeDelta::FromMilliseconds(100)); + HHOOK msg_hook = SetWindowsHookEx(WH_MSGFILTER, + MsgFilterProc, + NULL, + GetCurrentThreadId()); + DispatcherImpl dispatcher; + base::RunLoop run_loop(&dispatcher); + run_loop.Run(); + ASSERT_EQ(1, dispatcher.dispatch_count_); + UnhookWindowsHookEx(msg_hook); +} + +class TestIOHandler : public MessageLoopForIO::IOHandler { + public: + TestIOHandler(const wchar_t* name, HANDLE signal, bool wait); + + virtual void OnIOCompleted(MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, DWORD error); + + void Init(); + void WaitForIO(); + OVERLAPPED* context() { return &context_.overlapped; } + DWORD size() { return sizeof(buffer_); } + + private: + char buffer_[48]; + MessageLoopForIO::IOContext context_; + HANDLE signal_; + base::win::ScopedHandle file_; + bool wait_; +}; + +TestIOHandler::TestIOHandler(const wchar_t* name, HANDLE signal, bool wait) + : signal_(signal), wait_(wait) { + memset(buffer_, 0, sizeof(buffer_)); + memset(&context_, 0, sizeof(context_)); + context_.handler = this; + + file_.Set(CreateFile(name, GENERIC_READ, 0, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL)); + EXPECT_TRUE(file_.IsValid()); +} + +void TestIOHandler::Init() { + MessageLoopForIO::current()->RegisterIOHandler(file_, this); + + DWORD read; + EXPECT_FALSE(ReadFile(file_, buffer_, size(), &read, context())); + EXPECT_EQ(ERROR_IO_PENDING, GetLastError()); + if (wait_) + WaitForIO(); +} + +void TestIOHandler::OnIOCompleted(MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, DWORD error) { + ASSERT_TRUE(context == &context_); + ASSERT_TRUE(SetEvent(signal_)); +} + +void TestIOHandler::WaitForIO() { + EXPECT_TRUE(MessageLoopForIO::current()->WaitForIOCompletion(300, this)); + EXPECT_TRUE(MessageLoopForIO::current()->WaitForIOCompletion(400, this)); +} + +void RunTest_IOHandler() { + base::win::ScopedHandle callback_called(CreateEvent(NULL, TRUE, FALSE, NULL)); + ASSERT_TRUE(callback_called.IsValid()); + + const wchar_t* kPipeName = L"\\\\.\\pipe\\iohandler_pipe"; + base::win::ScopedHandle server( + CreateNamedPipe(kPipeName, PIPE_ACCESS_OUTBOUND, 0, 1, 0, 0, 0, NULL)); + ASSERT_TRUE(server.IsValid()); + + Thread thread("IOHandler test"); + Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + ASSERT_TRUE(thread.StartWithOptions(options)); + + MessageLoop* thread_loop = thread.message_loop(); + ASSERT_TRUE(NULL != thread_loop); + + TestIOHandler handler(kPipeName, callback_called, false); + thread_loop->PostTask(FROM_HERE, base::Bind(&TestIOHandler::Init, + base::Unretained(&handler))); + // Make sure the thread runs and sleeps for lack of work. + base::PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); + + const char buffer[] = "Hello there!"; + DWORD written; + EXPECT_TRUE(WriteFile(server, buffer, sizeof(buffer), &written, NULL)); + + DWORD result = WaitForSingleObject(callback_called, 1000); + EXPECT_EQ(WAIT_OBJECT_0, result); + + thread.Stop(); +} + +void RunTest_WaitForIO() { + base::win::ScopedHandle callback1_called( + CreateEvent(NULL, TRUE, FALSE, NULL)); + base::win::ScopedHandle callback2_called( + CreateEvent(NULL, TRUE, FALSE, NULL)); + ASSERT_TRUE(callback1_called.IsValid()); + ASSERT_TRUE(callback2_called.IsValid()); + + const wchar_t* kPipeName1 = L"\\\\.\\pipe\\iohandler_pipe1"; + const wchar_t* kPipeName2 = L"\\\\.\\pipe\\iohandler_pipe2"; + base::win::ScopedHandle server1( + CreateNamedPipe(kPipeName1, PIPE_ACCESS_OUTBOUND, 0, 1, 0, 0, 0, NULL)); + base::win::ScopedHandle server2( + CreateNamedPipe(kPipeName2, PIPE_ACCESS_OUTBOUND, 0, 1, 0, 0, 0, NULL)); + ASSERT_TRUE(server1.IsValid()); + ASSERT_TRUE(server2.IsValid()); + + Thread thread("IOHandler test"); + Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + ASSERT_TRUE(thread.StartWithOptions(options)); + + MessageLoop* thread_loop = thread.message_loop(); + ASSERT_TRUE(NULL != thread_loop); + + TestIOHandler handler1(kPipeName1, callback1_called, false); + TestIOHandler handler2(kPipeName2, callback2_called, true); + thread_loop->PostTask(FROM_HERE, base::Bind(&TestIOHandler::Init, + base::Unretained(&handler1))); + // TODO(ajwong): Do we really need such long Sleeps in ths function? + // Make sure the thread runs and sleeps for lack of work. + TimeDelta delay = TimeDelta::FromMilliseconds(100); + base::PlatformThread::Sleep(delay); + thread_loop->PostTask(FROM_HERE, base::Bind(&TestIOHandler::Init, + base::Unretained(&handler2))); + base::PlatformThread::Sleep(delay); + + // At this time handler1 is waiting to be called, and the thread is waiting + // on the Init method of handler2, filtering only handler2 callbacks. + + const char buffer[] = "Hello there!"; + DWORD written; + EXPECT_TRUE(WriteFile(server1, buffer, sizeof(buffer), &written, NULL)); + base::PlatformThread::Sleep(2 * delay); + EXPECT_EQ(WAIT_TIMEOUT, WaitForSingleObject(callback1_called, 0)) << + "handler1 has not been called"; + + EXPECT_TRUE(WriteFile(server2, buffer, sizeof(buffer), &written, NULL)); + + HANDLE objects[2] = { callback1_called.Get(), callback2_called.Get() }; + DWORD result = WaitForMultipleObjects(2, objects, TRUE, 1000); + EXPECT_EQ(WAIT_OBJECT_0, result); + + thread.Stop(); +} + +#endif // defined(OS_WIN) + +} // namespace + +//----------------------------------------------------------------------------- +// Each test is run against each type of MessageLoop. That way we are sure +// 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); +#if !defined(OS_STARBOARD) + RunTest_PostTask(MessageLoop::TYPE_UI); +#endif + RunTest_PostTask(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, PostTask_SEH) { + RunTest_PostTask_SEH(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_PostTask_SEH(MessageLoop::TYPE_UI); +#endif + RunTest_PostTask_SEH(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, PostDelayedTask_Basic) { + RunTest_PostDelayedTask_Basic(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_PostDelayedTask_Basic(MessageLoop::TYPE_UI); +#endif + RunTest_PostDelayedTask_Basic(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, PostDelayedTask_InDelayOrder) { + RunTest_PostDelayedTask_InDelayOrder(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_PostDelayedTask_InDelayOrder(MessageLoop::TYPE_UI); +#endif + RunTest_PostDelayedTask_InDelayOrder(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, PostDelayedTask_InPostOrder) { + RunTest_PostDelayedTask_InPostOrder(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_PostDelayedTask_InPostOrder(MessageLoop::TYPE_UI); +#endif + RunTest_PostDelayedTask_InPostOrder(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, PostDelayedTask_InPostOrder_2) { + RunTest_PostDelayedTask_InPostOrder_2(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_PostDelayedTask_InPostOrder_2(MessageLoop::TYPE_UI); +#endif + RunTest_PostDelayedTask_InPostOrder_2(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, PostDelayedTask_InPostOrder_3) { + RunTest_PostDelayedTask_InPostOrder_3(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_PostDelayedTask_InPostOrder_3(MessageLoop::TYPE_UI); +#endif + RunTest_PostDelayedTask_InPostOrder_3(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, PostDelayedTask_SharedTimer) { + RunTest_PostDelayedTask_SharedTimer(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_PostDelayedTask_SharedTimer(MessageLoop::TYPE_UI); +#endif + RunTest_PostDelayedTask_SharedTimer(MessageLoop::TYPE_IO); +} + +#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); + RunTest_Crasher(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, CrasherNasty) { + RunTest_CrasherNasty(MessageLoop::TYPE_DEFAULT); + RunTest_CrasherNasty(MessageLoop::TYPE_UI); + RunTest_CrasherNasty(MessageLoop::TYPE_IO); +} +#endif // defined(OS_WIN) + +TEST(MessageLoopTest, Nesting) { + RunTest_Nesting(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_Nesting(MessageLoop::TYPE_UI); +#endif + RunTest_Nesting(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RecursiveDenial1) { + RunTest_RecursiveDenial1(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_RecursiveDenial1(MessageLoop::TYPE_UI); +#endif + RunTest_RecursiveDenial1(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RecursiveDenial3) { + RunTest_RecursiveDenial3(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_RecursiveDenial3(MessageLoop::TYPE_UI); +#endif + RunTest_RecursiveDenial3(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RecursiveSupport1) { + RunTest_RecursiveSupport1(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_RecursiveSupport1(MessageLoop::TYPE_UI); +#endif + 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); + RunTest_RecursiveDenial2(MessageLoop::TYPE_UI); + RunTest_RecursiveDenial2(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RecursiveSupport2) { + // This test requires a UI loop + RunTest_RecursiveSupport2(MessageLoop::TYPE_UI); +} +#endif // defined(OS_WIN) + +TEST(MessageLoopTest, NonNestableWithNoNesting) { + RunTest_NonNestableWithNoNesting(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_NonNestableWithNoNesting(MessageLoop::TYPE_UI); +#endif + RunTest_NonNestableWithNoNesting(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, NonNestableInNestedLoop) { + RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_DEFAULT, false); +#if !defined(OS_STARBOARD) + RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_UI, false); +#endif + RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_IO, false); +} + +TEST(MessageLoopTest, NonNestableDelayedInNestedLoop) { + RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_DEFAULT, true); +#if !defined(OS_STARBOARD) + RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_UI, true); +#endif + RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_IO, true); +} + +TEST(MessageLoopTest, QuitNow) { + RunTest_QuitNow(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_QuitNow(MessageLoop::TYPE_UI); +#endif + RunTest_QuitNow(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RunLoopQuitTop) { + RunTest_RunLoopQuitTop(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_RunLoopQuitTop(MessageLoop::TYPE_UI); +#endif + RunTest_RunLoopQuitTop(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RunLoopQuitNested) { + RunTest_RunLoopQuitNested(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_RunLoopQuitNested(MessageLoop::TYPE_UI); +#endif + RunTest_RunLoopQuitNested(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RunLoopQuitBogus) { + RunTest_RunLoopQuitBogus(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_RunLoopQuitBogus(MessageLoop::TYPE_UI); +#endif + RunTest_RunLoopQuitBogus(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RunLoopQuitDeep) { + RunTest_RunLoopQuitDeep(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_RunLoopQuitDeep(MessageLoop::TYPE_UI); +#endif + RunTest_RunLoopQuitDeep(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RunLoopQuitOrderBefore) { + RunTest_RunLoopQuitOrderBefore(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_RunLoopQuitOrderBefore(MessageLoop::TYPE_UI); +#endif + RunTest_RunLoopQuitOrderBefore(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RunLoopQuitOrderDuring) { + RunTest_RunLoopQuitOrderDuring(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_RunLoopQuitOrderDuring(MessageLoop::TYPE_UI); +#endif + RunTest_RunLoopQuitOrderDuring(MessageLoop::TYPE_IO); +} + +TEST(MessageLoopTest, RunLoopQuitOrderAfter) { + RunTest_RunLoopQuitOrderAfter(MessageLoop::TYPE_DEFAULT); +#if !defined(OS_STARBOARD) + RunTest_RunLoopQuitOrderAfter(MessageLoop::TYPE_UI); +#endif + RunTest_RunLoopQuitOrderAfter(MessageLoop::TYPE_IO); +} + +class DummyTaskObserver : public MessageLoop::TaskObserver { + public: + explicit DummyTaskObserver(int num_tasks) + : num_tasks_started_(0), + num_tasks_processed_(0), + num_tasks_(num_tasks) {} + + virtual ~DummyTaskObserver() {} + + virtual void WillProcessTask(TimeTicks time_posted) OVERRIDE { + num_tasks_started_++; + EXPECT_TRUE(time_posted != TimeTicks()); + EXPECT_LE(num_tasks_started_, num_tasks_); + EXPECT_EQ(num_tasks_started_, num_tasks_processed_ + 1); + } + + virtual void DidProcessTask(TimeTicks time_posted) OVERRIDE { + num_tasks_processed_++; + EXPECT_TRUE(time_posted != TimeTicks()); + EXPECT_LE(num_tasks_started_, num_tasks_); + EXPECT_EQ(num_tasks_started_, num_tasks_processed_); + } + + int num_tasks_started() const { return num_tasks_started_; } + int num_tasks_processed() const { return num_tasks_processed_; } + + private: + int num_tasks_started_; + int num_tasks_processed_; + const int num_tasks_; + + DISALLOW_COPY_AND_ASSIGN(DummyTaskObserver); +}; + +TEST(MessageLoopTest, TaskObserver) { + const int kNumPosts = 6; + DummyTaskObserver observer(kNumPosts); + + MessageLoop loop; + loop.AddTaskObserver(&observer); + loop.PostTask(FROM_HERE, base::Bind(&PostNTasksThenQuit, kNumPosts)); + loop.Run(); + loop.RemoveTaskObserver(&observer); + + EXPECT_EQ(kNumPosts, observer.num_tasks_started()); + EXPECT_EQ(kNumPosts, observer.num_tasks_processed()); +} + +#if defined(OS_WIN) +TEST(MessageLoopTest, Dispatcher) { + // This test requires a UI loop + RunTest_Dispatcher(MessageLoop::TYPE_UI); +} + +TEST(MessageLoopTest, DispatcherWithMessageHook) { + // This test requires a UI loop + RunTest_DispatcherWithMessageHook(MessageLoop::TYPE_UI); +} + +TEST(MessageLoopTest, IOHandler) { + RunTest_IOHandler(); +} + +TEST(MessageLoopTest, WaitForIO) { + RunTest_WaitForIO(); +} + +TEST(MessageLoopTest, HighResolutionTimer) { + MessageLoop loop; + + const TimeDelta kFastTimer = TimeDelta::FromMilliseconds(5); + const TimeDelta kSlowTimer = TimeDelta::FromMilliseconds(100); + + EXPECT_FALSE(loop.high_resolution_timers_enabled()); + + // Post a fast task to enable the high resolution timers. + loop.PostDelayedTask(FROM_HERE, base::Bind(&PostNTasksThenQuit, 1), + kFastTimer); + loop.Run(); + EXPECT_TRUE(loop.high_resolution_timers_enabled()); + + // Post a slow task and verify high resolution timers + // are still enabled. + loop.PostDelayedTask(FROM_HERE, base::Bind(&PostNTasksThenQuit, 1), + kSlowTimer); + loop.Run(); + EXPECT_TRUE(loop.high_resolution_timers_enabled()); + + // Wait for a while so that high-resolution mode elapses. + base::PlatformThread::Sleep(TimeDelta::FromMilliseconds( + MessageLoop::kHighResolutionTimerModeLeaseTimeMs)); + + // Post a slow task to disable the high resolution timers. + loop.PostDelayedTask(FROM_HERE, base::Bind(&PostNTasksThenQuit, 1), + kSlowTimer); + loop.Run(); + EXPECT_FALSE(loop.high_resolution_timers_enabled()); +} + +#endif // defined(OS_WIN) + +#if defined(OS_POSIX) && !defined(OS_NACL) + +namespace { + +class QuitDelegate : public MessageLoopForIO::Watcher { + public: + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE { + MessageLoop::current()->Quit(); + } + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE { + MessageLoop::current()->Quit(); + } +}; + +// LB_SHELL platforms don't implement WatchFileDescriptor(). +// pipe() may not exist on some platforms. +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) +TEST(MessageLoopTest, FileDescriptorWatcherOutlivesMessageLoop) { + // Simulate a MessageLoop that dies before an FileDescriptorWatcher. + // This could happen when people use the Singleton pattern or atexit. + + // Create a file descriptor. Doesn't need to be readable or writable, + // as we don't need to actually get any notifications. + // pipe() is just the easiest way to do it. + int pipefds[2]; + int err = pipe(pipefds); + ASSERT_EQ(0, err); + int fd = pipefds[1]; + { + // Arrange for controller to live longer than message loop. + MessageLoopForIO::FileDescriptorWatcher controller; + { + MessageLoopForIO message_loop; + + QuitDelegate delegate; + message_loop.WatchFileDescriptor(fd, + true, MessageLoopForIO::WATCH_WRITE, &controller, &delegate); + // and don't run the message loop, just destroy it. + } + } + if (HANDLE_EINTR(close(pipefds[0])) < 0) + PLOG(ERROR) << "close"; + if (HANDLE_EINTR(close(pipefds[1])) < 0) + PLOG(ERROR) << "close"; +} + +TEST(MessageLoopTest, FileDescriptorWatcherDoubleStop) { + // Verify that it's ok to call StopWatchingFileDescriptor(). + // (Errors only showed up in valgrind.) + int pipefds[2]; + int err = pipe(pipefds); + ASSERT_EQ(0, err); + int fd = pipefds[1]; + { + // Arrange for message loop to live longer than controller. + MessageLoopForIO message_loop; + { + MessageLoopForIO::FileDescriptorWatcher controller; + + QuitDelegate delegate; + message_loop.WatchFileDescriptor(fd, + true, MessageLoopForIO::WATCH_WRITE, &controller, &delegate); + controller.StopWatchingFileDescriptor(); + } + } + if (HANDLE_EINTR(close(pipefds[0])) < 0) + PLOG(ERROR) << "close"; + if (HANDLE_EINTR(close(pipefds[1])) < 0) + PLOG(ERROR) << "close"; +} +#endif + +} // namespace + +#endif // defined(OS_POSIX) && !defined(OS_NACL) + +namespace { +// 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 DestructionObserverProbe : + public base::RefCounted<DestructionObserverProbe> { + public: + DestructionObserverProbe(bool* task_destroyed, + bool* destruction_observer_called) + : task_destroyed_(task_destroyed), + destruction_observer_called_(destruction_observer_called) { + } + virtual void Run() { + // This task should never run. + ADD_FAILURE(); + } + private: + friend class base::RefCounted<DestructionObserverProbe>; + + virtual ~DestructionObserverProbe() { + EXPECT_FALSE(*destruction_observer_called_); + *task_destroyed_ = true; + } + + bool* task_destroyed_; + bool* destruction_observer_called_; +}; + +class MLDestructionObserver : public MessageLoop::DestructionObserver { + public: + MLDestructionObserver(bool* task_destroyed, bool* destruction_observer_called) + : task_destroyed_(task_destroyed), + destruction_observer_called_(destruction_observer_called), + task_destroyed_before_message_loop_(false) { + } + virtual void WillDestroyCurrentMessageLoop() OVERRIDE { + task_destroyed_before_message_loop_ = *task_destroyed_; + *destruction_observer_called_ = true; + } + bool task_destroyed_before_message_loop() const { + return task_destroyed_before_message_loop_; + } + private: + bool* task_destroyed_; + bool* destruction_observer_called_; + bool task_destroyed_before_message_loop_; +}; + +} // namespace + +TEST(MessageLoopTest, DestructionObserverTest) { + // Verify that the destruction observer gets called at the very end (after + // all the pending tasks have been destroyed). + MessageLoop* loop = new MessageLoop; + const TimeDelta kDelay = TimeDelta::FromMilliseconds(100); + + bool task_destroyed = false; + bool destruction_observer_called = false; + + MLDestructionObserver observer(&task_destroyed, &destruction_observer_called); + loop->AddDestructionObserver(&observer); + loop->PostDelayedTask( + FROM_HERE, + base::Bind(&DestructionObserverProbe::Run, + new DestructionObserverProbe(&task_destroyed, + &destruction_observer_called)), + kDelay); + delete loop; + EXPECT_TRUE(observer.task_destroyed_before_message_loop()); + // The task should have been destroyed when we deleted the loop. + EXPECT_TRUE(task_destroyed); + EXPECT_TRUE(destruction_observer_called); +} + + +// Verify that MessageLoop sets ThreadMainTaskRunner::current() and it +// posts tasks on that message loop. +TEST(MessageLoopTest, ThreadMainTaskRunner) { + MessageLoop loop; + + scoped_refptr<Foo> foo(new Foo()); + std::string a("a"); + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, base::Bind( + &Foo::Test1ConstRef, foo.get(), a)); + + // Post quit task; + MessageLoop::current()->PostTask(FROM_HERE, base::Bind( + &MessageLoop::Quit, base::Unretained(MessageLoop::current()))); + + // Now kick things off + MessageLoop::current()->Run(); + + EXPECT_EQ(foo->test_count(), 1); + EXPECT_EQ(foo->result(), "a"); +} + +TEST(MessageLoopTest, IsType) { + MessageLoop loop(MessageLoop::TYPE_UI); + EXPECT_TRUE(loop.IsType(MessageLoop::TYPE_UI)); + EXPECT_FALSE(loop.IsType(MessageLoop::TYPE_IO)); + 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); +#if !defined(OS_STARBOARD) + RunTest_RecursivePosts(MessageLoop::TYPE_UI, kNumTimes); +#endif + RunTest_RecursivePosts(MessageLoop::TYPE_IO, kNumTimes); +}
diff --git a/src/base/message_pump.cc b/src/base/message_pump.cc new file mode 100644 index 0000000..de7c517 --- /dev/null +++ b/src/base/message_pump.cc
@@ -0,0 +1,15 @@ +// Copyright (c) 2010 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_pump.h" + +namespace base { + +MessagePump::MessagePump() { +} + +MessagePump::~MessagePump() { +} + +} // namespace base
diff --git a/src/base/message_pump.h b/src/base/message_pump.h new file mode 100644 index 0000000..b2e0a42 --- /dev/null +++ b/src/base/message_pump.h
@@ -0,0 +1,129 @@ +// 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_MESSAGE_PUMP_H_ +#define BASE_MESSAGE_PUMP_H_ + +#include "base/base_export.h" +#include "base/memory/ref_counted.h" + +namespace base { + +class TimeTicks; + +class BASE_EXPORT MessagePump : public RefCountedThreadSafe<MessagePump> { + public: + // Please see the comments above the Run method for an illustration of how + // these delegate methods are used. + class BASE_EXPORT Delegate { + public: + virtual ~Delegate() {} + + // Called from within Run in response to ScheduleWork or when the message + // pump would otherwise call DoDelayedWork. Returns true to indicate that + // work was done. DoDelayedWork will still be called if DoWork returns + // true, but DoIdleWork will not. + virtual bool DoWork() = 0; + + // Called from within Run in response to ScheduleDelayedWork or when the + // message pump would otherwise sleep waiting for more work. Returns true + // to indicate that delayed work was done. DoIdleWork will not be called + // if DoDelayedWork returns true. Upon return |next_delayed_work_time| + // indicates the time when DoDelayedWork should be called again. If + // |next_delayed_work_time| is null (per Time::is_null), then the queue of + // future delayed work (timer events) is currently empty, and no additional + // calls to this function need to be scheduled. + virtual bool DoDelayedWork(TimeTicks* next_delayed_work_time) = 0; + + // Called from within Run just before the message pump goes to sleep. + // Returns true to indicate that idle work was done. + virtual bool DoIdleWork() = 0; + }; + + MessagePump(); + + // The Run method is called to enter the message pump's run loop. + // + // Within the method, the message pump is responsible for processing native + // messages as well as for giving cycles to the delegate periodically. The + // message pump should take care to mix delegate callbacks with native + // message processing so neither type of event starves the other of cycles. + // + // The anatomy of a typical run loop: + // + // for (;;) { + // bool did_work = DoInternalWork(); + // if (should_quit_) + // break; + // + // did_work |= delegate_->DoWork(); + // if (should_quit_) + // break; + // + // TimeTicks next_time; + // did_work |= delegate_->DoDelayedWork(&next_time); + // if (should_quit_) + // break; + // + // if (did_work) + // continue; + // + // did_work = delegate_->DoIdleWork(); + // if (should_quit_) + // break; + // + // if (did_work) + // continue; + // + // WaitForWork(); + // } + // + // Here, DoInternalWork is some private method of the message pump that is + // responsible for dispatching the next UI message or notifying the next IO + // completion (for example). WaitForWork is a private method that simply + // blocks until there is more work of any type to do. + // + // Notice that the run loop cycles between calling DoInternalWork, DoWork, + // and DoDelayedWork methods. This helps ensure that none of these work + // queues starve the others. This is important for message pumps that are + // used to drive animations, for example. + // + // Notice also that after each callout to foreign code, the run loop checks + // to see if it should quit. The Quit method is responsible for setting this + // flag. No further work is done once the quit flag is set. + // + // NOTE: Care must be taken to handle Run being called again from within any + // of the callouts to foreign code. Native message pumps may also need to + // deal with other native message pumps being run outside their control + // (e.g., the MessageBox API on Windows pumps UI messages!). To be specific, + // the callouts (DoWork and DoDelayedWork) MUST still be provided even in + // nested sub-loops that are "seemingly" outside the control of this message + // pump. DoWork in particular must never be starved for time slices unless + // it returns false (meaning it has run out of things to do). + // + virtual void Run(Delegate* delegate) = 0; + + // Quit immediately from the most recently entered run loop. This method may + // only be used on the thread that called Run. + virtual void Quit() = 0; + + // Schedule a DoWork callback to happen reasonably soon. Does nothing if a + // DoWork callback is already scheduled. This method may be called from any + // thread. Once this call is made, DoWork should not be "starved" at least + // until it returns a value of false. + virtual void ScheduleWork() = 0; + + // Schedule a DoDelayedWork callback to happen at the specified time, + // cancelling any pending DoDelayedWork callback. This method may only be + // used on the thread that called Run. + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time) = 0; + + protected: + virtual ~MessagePump(); + friend class RefCountedThreadSafe<MessagePump>; +}; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_H_
diff --git a/src/base/message_pump_android.cc b/src/base/message_pump_android.cc new file mode 100644 index 0000000..f02da67 --- /dev/null +++ b/src/base/message_pump_android.cc
@@ -0,0 +1,139 @@ +// 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/message_pump_android.h" + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/run_loop.h" +#include "base/time.h" +#include "jni/SystemMessageHandler_jni.h" + +using base::android::ScopedJavaLocalRef; + +namespace { + +base::LazyInstance<base::android::ScopedJavaGlobalRef<jobject> > + g_system_message_handler_obj = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// ---------------------------------------------------------------------------- +// Native JNI methods called by Java. +// ---------------------------------------------------------------------------- +// This method can not move to anonymous namespace as it has been declared as +// 'static' in system_message_handler_jni.h. +static jboolean DoRunLoopOnce(JNIEnv* env, jobject obj, jint native_delegate) { + base::MessagePump::Delegate* delegate = + reinterpret_cast<base::MessagePump::Delegate*>(native_delegate); + DCHECK(delegate); + // This is based on MessagePumpForUI::DoRunLoop() from desktop. + // Note however that our system queue is handled in the java side. + // In desktop we inspect and process a single system message and then + // we call DoWork() / DoDelayedWork(). + // On Android, the java message queue may contain messages for other handlers + // that will be processed before calling here again. + bool more_work_is_plausible = delegate->DoWork(); + + // This is the time when we need to do delayed work. + base::TimeTicks delayed_work_time; + more_work_is_plausible |= delegate->DoDelayedWork(&delayed_work_time); + + // This is a major difference between android and other platforms: since we + // can't inspect it and process just one single message, instead we'll yeld + // the callstack, and post a message to call us back soon. + if (more_work_is_plausible) + return true; + + more_work_is_plausible = delegate->DoIdleWork(); + if (!more_work_is_plausible && !delayed_work_time.is_null()) { + // We only set the timer here as returning true would post a message. + jlong millis = + (delayed_work_time - base::TimeTicks::Now()).InMillisecondsRoundedUp(); + Java_SystemMessageHandler_setDelayedTimer(env, obj, millis); + } + return more_work_is_plausible; +} + +namespace base { + +MessagePumpForUI::MessagePumpForUI() + : run_loop_(NULL) { +} + +MessagePumpForUI::~MessagePumpForUI() { +} + +void MessagePumpForUI::Run(Delegate* delegate) { + NOTREACHED() << "UnitTests should rely on MessagePumpForUIStub in" + " test_stub_android.h"; +} + +void MessagePumpForUI::Start(Delegate* delegate) { + run_loop_ = new base::RunLoop(); + // Since the RunLoop was just created above, BeforeRun should be guaranteed to + // return true (it only returns false if the RunLoop has been Quit already). + if (!run_loop_->BeforeRun()) + NOTREACHED(); + + DCHECK(g_system_message_handler_obj.Get().is_null()); + + JNIEnv* env = base::android::AttachCurrentThread(); + DCHECK(env); + + g_system_message_handler_obj.Get().Reset( + Java_SystemMessageHandler_create(env, reinterpret_cast<jint>(delegate))); +} + +void MessagePumpForUI::Quit() { + if (!g_system_message_handler_obj.Get().is_null()) { + JNIEnv* env = base::android::AttachCurrentThread(); + DCHECK(env); + + Java_SystemMessageHandler_removeTimer(env, + g_system_message_handler_obj.Get().obj()); + g_system_message_handler_obj.Get().Reset(); + } + + if (run_loop_) { + run_loop_->AfterRun(); + delete run_loop_; + run_loop_ = NULL; + } +} + +void MessagePumpForUI::ScheduleWork() { + DCHECK(!g_system_message_handler_obj.Get().is_null()); + + JNIEnv* env = base::android::AttachCurrentThread(); + DCHECK(env); + + Java_SystemMessageHandler_setTimer(env, + g_system_message_handler_obj.Get().obj()); +} + +void MessagePumpForUI::ScheduleDelayedWork(const TimeTicks& delayed_work_time) { + DCHECK(!g_system_message_handler_obj.Get().is_null()); + + JNIEnv* env = base::android::AttachCurrentThread(); + DCHECK(env); + + jlong millis = + (delayed_work_time - base::TimeTicks::Now()).InMillisecondsRoundedUp(); + // Note that we're truncating to milliseconds as required by the java side, + // even though delayed_work_time is microseconds resolution. + Java_SystemMessageHandler_setDelayedTimer(env, + g_system_message_handler_obj.Get().obj(), millis); +} + +// static +bool MessagePumpForUI::RegisterBindings(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace base
diff --git a/src/base/message_pump_android.h b/src/base/message_pump_android.h new file mode 100644 index 0000000..5ef4bd6 --- /dev/null +++ b/src/base/message_pump_android.h
@@ -0,0 +1,45 @@ +// 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_MESSAGE_PUMP_ANDROID_H_ +#define BASE_MESSAGE_PUMP_ANDROID_H_ + +#include <jni.h> + +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/message_pump.h" + +namespace base { + +class RunLoop; +class TimeTicks; + +// This class implements a MessagePump needed for TYPE_UI MessageLoops on +// OS_ANDROID platform. +class BASE_EXPORT MessagePumpForUI : public MessagePump { + public: + MessagePumpForUI(); + + virtual void Run(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + virtual void ScheduleWork() OVERRIDE; + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time) OVERRIDE; + + virtual void Start(Delegate* delegate); + + static bool RegisterBindings(JNIEnv* env); + + protected: + virtual ~MessagePumpForUI(); + + private: + base::RunLoop* run_loop_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpForUI); +}; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_ANDROID_H_
diff --git a/src/base/message_pump_aurax11.cc b/src/base/message_pump_aurax11.cc new file mode 100644 index 0000000..9ba7989 --- /dev/null +++ b/src/base/message_pump_aurax11.cc
@@ -0,0 +1,307 @@ +// 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/message_pump_aurax11.h" + +#include <glib.h> +#include <X11/X.h> +#include <X11/extensions/XInput2.h> +#include <X11/XKBlib.h> + +#include "base/basictypes.h" +#include "base/message_loop.h" + +namespace { + +gboolean XSourcePrepare(GSource* source, gint* timeout_ms) { + if (XPending(base::MessagePumpAuraX11::GetDefaultXDisplay())) + *timeout_ms = 0; + else + *timeout_ms = -1; + return FALSE; +} + +gboolean XSourceCheck(GSource* source) { + return XPending(base::MessagePumpAuraX11::GetDefaultXDisplay()); +} + +gboolean XSourceDispatch(GSource* source, + GSourceFunc unused_func, + gpointer data) { + base::MessagePumpAuraX11* pump = static_cast<base::MessagePumpAuraX11*>(data); + return pump->DispatchXEvents(); +} + +GSourceFuncs XSourceFuncs = { + XSourcePrepare, + XSourceCheck, + XSourceDispatch, + NULL +}; + +// The connection is essentially a global that's accessed through a static +// method and destroyed whenever ~MessagePumpAuraX11() is called. We do this +// for historical reasons so user code can call +// MessagePumpForUI::GetDefaultXDisplay() where MessagePumpForUI is a typedef +// to whatever type in the current build. +// +// TODO(erg): This can be changed to something more sane like +// MessagePumpAuraX11::Current()->display() once MessagePumpGtk goes away. +Display* g_xdisplay = NULL; +int g_xinput_opcode = -1; + +bool InitializeXInput2Internal() { + Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay(); + if (!display) + return false; + + int event, err; + + int xiopcode; + if (!XQueryExtension(display, "XInputExtension", &xiopcode, &event, &err)) { + DVLOG(1) << "X Input extension not available."; + return false; + } + g_xinput_opcode = xiopcode; + +#if defined(USE_XI2_MT) + // USE_XI2_MT also defines the required XI2 minor minimum version. + int major = 2, minor = USE_XI2_MT; +#else + int major = 2, minor = 0; +#endif + if (XIQueryVersion(display, &major, &minor) == BadRequest) { + DVLOG(1) << "XInput2 not supported in the server."; + return false; + } +#if defined(USE_XI2_MT) + if (major < 2 || (major == 2 && minor < USE_XI2_MT)) { + DVLOG(1) << "XI version on server is " << major << "." << minor << ". " + << "But 2." << USE_XI2_MT << " is required."; + return false; + } +#endif + + return true; +} + +Window FindEventTarget(const base::NativeEvent& xev) { + Window target = xev->xany.window; + if (xev->type == GenericEvent && + static_cast<XIEvent*>(xev->xcookie.data)->extension == g_xinput_opcode) { + target = static_cast<XIDeviceEvent*>(xev->xcookie.data)->event; + } + return target; +} + +bool InitializeXInput2() { + static bool xinput2_supported = InitializeXInput2Internal(); + return xinput2_supported; +} + +bool InitializeXkb() { + Display* display = base::MessagePumpAuraX11::GetDefaultXDisplay(); + if (!display) + return false; + + int opcode, event, error; + int major = XkbMajorVersion; + int minor = XkbMinorVersion; + if (!XkbQueryExtension(display, &opcode, &event, &error, &major, &minor)) { + DVLOG(1) << "Xkb extension not available."; + return false; + } + + // Ask the server not to send KeyRelease event when the user holds down a key. + // crbug.com/138092 + Bool supported_return; + if (!XkbSetDetectableAutoRepeat(display, True, &supported_return)) { + DVLOG(1) << "XKB not supported in the server."; + return false; + } + + return true; +} + +} // namespace + +namespace base { + +MessagePumpAuraX11::MessagePumpAuraX11() : MessagePumpGlib(), + x_source_(NULL) { + InitializeXInput2(); + InitializeXkb(); + InitXSource(); + + // Can't put this in the initializer list because g_xdisplay may not exist + // until after InitXSource(). + x_root_window_ = DefaultRootWindow(g_xdisplay); +} + +// static +Display* MessagePumpAuraX11::GetDefaultXDisplay() { + if (!g_xdisplay) + g_xdisplay = XOpenDisplay(NULL); + return g_xdisplay; +} + +// static +bool MessagePumpAuraX11::HasXInput2() { + return InitializeXInput2(); +} + +// static +MessagePumpAuraX11* MessagePumpAuraX11::Current() { + MessageLoopForUI* loop = MessageLoopForUI::current(); + return static_cast<MessagePumpAuraX11*>(loop->pump_ui()); +} + +void MessagePumpAuraX11::AddDispatcherForWindow( + MessagePumpDispatcher* dispatcher, + unsigned long xid) { + dispatchers_.insert(std::make_pair(xid, dispatcher)); +} + +void MessagePumpAuraX11::RemoveDispatcherForWindow(unsigned long xid) { + dispatchers_.erase(xid); +} + +void MessagePumpAuraX11::AddDispatcherForRootWindow( + MessagePumpDispatcher* dispatcher) { + root_window_dispatchers_.AddObserver(dispatcher); +} + +void MessagePumpAuraX11::RemoveDispatcherForRootWindow( + MessagePumpDispatcher* dispatcher) { + root_window_dispatchers_.RemoveObserver(dispatcher); +} + +bool MessagePumpAuraX11::DispatchXEvents() { + Display* display = GetDefaultXDisplay(); + DCHECK(display); + MessagePumpDispatcher* dispatcher = + GetDispatcher() ? GetDispatcher() : this; + + // In the general case, we want to handle all pending events before running + // the tasks. This is what happens in the message_pump_glib case. + while (XPending(display)) { + XEvent xev; + XNextEvent(display, &xev); + if (dispatcher && ProcessXEvent(dispatcher, &xev)) + return TRUE; + } + return TRUE; +} + +void MessagePumpAuraX11::BlockUntilWindowMapped(unsigned long xid) { + XEvent event; + + Display* display = GetDefaultXDisplay(); + DCHECK(display); + + MessagePumpDispatcher* dispatcher = + GetDispatcher() ? GetDispatcher() : this; + + do { + // Block until there's a message of |event_mask| type on |w|. Then remove + // it from the queue and stuff it in |event|. + XWindowEvent(display, xid, StructureNotifyMask, &event); + ProcessXEvent(dispatcher, &event); + } while (event.type != MapNotify); +} + +MessagePumpAuraX11::~MessagePumpAuraX11() { + g_source_destroy(x_source_); + g_source_unref(x_source_); + XCloseDisplay(g_xdisplay); + g_xdisplay = NULL; +} + +void MessagePumpAuraX11::InitXSource() { + // CHECKs are to help track down crbug.com/113106. + CHECK(!x_source_); + Display* display = GetDefaultXDisplay(); + CHECK(display) << "Unable to get connection to X server"; + x_poll_.reset(new GPollFD()); + CHECK(x_poll_.get()); + x_poll_->fd = ConnectionNumber(display); + x_poll_->events = G_IO_IN; + + x_source_ = g_source_new(&XSourceFuncs, sizeof(GSource)); + g_source_add_poll(x_source_, x_poll_.get()); + g_source_set_can_recurse(x_source_, TRUE); + g_source_set_callback(x_source_, NULL, this, NULL); + g_source_attach(x_source_, g_main_context_default()); +} + +bool MessagePumpAuraX11::ProcessXEvent(MessagePumpDispatcher* dispatcher, + XEvent* xev) { + bool should_quit = false; + + bool have_cookie = false; + if (xev->type == GenericEvent && + XGetEventData(xev->xgeneric.display, &xev->xcookie)) { + have_cookie = true; + } + + if (!WillProcessXEvent(xev)) { + if (!dispatcher->Dispatch(xev)) { + should_quit = true; + Quit(); + } + DidProcessXEvent(xev); + } + + if (have_cookie) { + XFreeEventData(xev->xgeneric.display, &xev->xcookie); + } + + return should_quit; +} + +bool MessagePumpAuraX11::WillProcessXEvent(XEvent* xevent) { + if (!observers().might_have_observers()) + return false; + ObserverListBase<MessagePumpObserver>::Iterator it(observers()); + MessagePumpObserver* obs; + while ((obs = it.GetNext()) != NULL) { + if (obs->WillProcessEvent(xevent)) + return true; + } + return false; +} + +void MessagePumpAuraX11::DidProcessXEvent(XEvent* xevent) { + FOR_EACH_OBSERVER(MessagePumpObserver, observers(), DidProcessEvent(xevent)); +} + +MessagePumpDispatcher* MessagePumpAuraX11::GetDispatcherForXEvent( + const base::NativeEvent& xev) const { + ::Window x_window = FindEventTarget(xev); + DispatchersMap::const_iterator it = dispatchers_.find(x_window); + return it != dispatchers_.end() ? it->second : NULL; +} + +bool MessagePumpAuraX11::Dispatch(const base::NativeEvent& xev) { + // MappingNotify events (meaning that the keyboard or pointer buttons have + // been remapped) aren't associated with a window; send them to all + // dispatchers. + if (xev->type == MappingNotify) { + for (DispatchersMap::const_iterator it = dispatchers_.begin(); + it != dispatchers_.end(); ++it) { + it->second->Dispatch(xev); + } + return true; + } + + if (FindEventTarget(xev) == x_root_window_) { + FOR_EACH_OBSERVER(MessagePumpDispatcher, root_window_dispatchers_, + Dispatch(xev)); + return true; + } + MessagePumpDispatcher* dispatcher = GetDispatcherForXEvent(xev); + return dispatcher ? dispatcher->Dispatch(xev) : true; +} + +} // namespace base
diff --git a/src/base/message_pump_aurax11.h b/src/base/message_pump_aurax11.h new file mode 100644 index 0000000..486de4f --- /dev/null +++ b/src/base/message_pump_aurax11.h
@@ -0,0 +1,123 @@ +// 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_MESSAGE_PUMP_AURAX11_H +#define BASE_MESSAGE_PUMP_AURAX11_H + +#include <bitset> +#include <map> + +#include "base/memory/scoped_ptr.h" +#include "base/message_pump.h" +#include "base/message_pump_glib.h" +#include "base/message_pump_dispatcher.h" +#include "base/message_pump_observer.h" +#include "base/observer_list.h" + +// It would be nice to include the X11 headers here so that we use Window +// instead of its typedef of unsigned long, but we can't because everything in +// chrome includes us through base/message_loop.h, and X11's crappy #define +// heavy headers muck up half of chrome. + +typedef struct _GPollFD GPollFD; +typedef struct _GSource GSource; +typedef struct _XDisplay Display; + +namespace base { + +// This class implements a message-pump for dispatching X events. +// +// If there's a current dispatcher given through RunWithDispatcher(), that +// dispatcher receives events. Otherwise, we route to messages to dispatchers +// who have subscribed to messages from a specific X11 window. +class BASE_EXPORT MessagePumpAuraX11 : public MessagePumpGlib, + public MessagePumpDispatcher { + public: + MessagePumpAuraX11(); + + // Returns default X Display. + static Display* GetDefaultXDisplay(); + + // Returns true if the system supports XINPUT2. + static bool HasXInput2(); + + // Returns the UI message pump. + static MessagePumpAuraX11* Current(); + + // Adds/Removes |dispatcher| for the |xid|. This will route all messages from + // the window |xid| to |dispatcher. + void AddDispatcherForWindow(MessagePumpDispatcher* dispatcher, + unsigned long xid); + void RemoveDispatcherForWindow(unsigned long xid); + + // Adds/Removes |dispatcher| to receive all events sent to the X root + // window. A root window can have multiple dispatchers, and events on root + // windows will be dispatched to all. + void AddDispatcherForRootWindow(MessagePumpDispatcher* dispatcher); + void RemoveDispatcherForRootWindow(MessagePumpDispatcher* dispatcher); + + // Internal function. Called by the glib source dispatch function. Processes + // all available X events. + bool DispatchXEvents(); + + // Blocks on the X11 event queue until we receive notification from the + // xserver that |w| has been mapped; StructureNotifyMask events on |w| are + // pulled out from the queue and dispatched out of order. + // + // For those that know X11, this is really a wrapper around XWindowEvent + // which still makes sure the preempted event is dispatched instead of + // dropped on the floor. This method exists because mapping a window is + // asynchronous (and we receive an XEvent when mapped), while there are also + // functions which require a mapped window. + void BlockUntilWindowMapped(unsigned long xid); + + protected: + virtual ~MessagePumpAuraX11(); + + private: + typedef std::map<unsigned long, MessagePumpDispatcher*> DispatchersMap; + + // Initializes the glib event source for X. + void InitXSource(); + + // Dispatches the XEvent and returns true if we should exit the current loop + // of message processing. + bool ProcessXEvent(MessagePumpDispatcher* dispatcher, XEvent* event); + + // Sends the event to the observers. If an observer returns true, then it does + // not send the event to any other observers and returns true. Returns false + // if no observer returns true. + bool WillProcessXEvent(XEvent* xevent); + void DidProcessXEvent(XEvent* xevent); + + // Returns the Dispatcher based on the event's target window. + MessagePumpDispatcher* GetDispatcherForXEvent( + const base::NativeEvent& xev) const; + + // Overridden from MessagePumpDispatcher: + virtual bool Dispatch(const base::NativeEvent& event) OVERRIDE; + + // The event source for X events. + GSource* x_source_; + + // The poll attached to |x_source_|. + scoped_ptr<GPollFD> x_poll_; + + DispatchersMap dispatchers_; + + // Dispatch calls can cause addition of new dispatchers as we iterate + // through them. Use ObserverList to ensure the iterator remains valid across + // additions. + ObserverList<MessagePumpDispatcher> root_window_dispatchers_; + + unsigned long x_root_window_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpAuraX11); +}; + +typedef MessagePumpAuraX11 MessagePumpForUI; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_AURAX11_H
diff --git a/src/base/message_pump_default.cc b/src/base/message_pump_default.cc new file mode 100644 index 0000000..649e2aa --- /dev/null +++ b/src/base/message_pump_default.cc
@@ -0,0 +1,85 @@ +// Copyright (c) 2006-2008 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_pump_default.h" + +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" + +#if defined(OS_MACOSX) +#include "base/mac/scoped_nsautorelease_pool.h" +#endif + +namespace base { + +MessagePumpDefault::MessagePumpDefault() + : keep_running_(true), + event_(false, false) { +} + +void MessagePumpDefault::Run(Delegate* delegate) { + DCHECK(keep_running_) << "Quit must have been called outside of Run!"; + + for (;;) { +#if defined(OS_MACOSX) + mac::ScopedNSAutoreleasePool autorelease_pool; +#endif + + bool did_work = delegate->DoWork(); + if (!keep_running_) + break; + + did_work |= delegate->DoDelayedWork(&delayed_work_time_); + if (!keep_running_) + break; + + if (did_work) + continue; + + did_work = delegate->DoIdleWork(); + if (!keep_running_) + break; + + if (did_work) + continue; + + base::ThreadRestrictions::ScopedAllowWait allow_wait; + if (delayed_work_time_.is_null()) { + event_.Wait(); + } else { + TimeDelta delay = delayed_work_time_ - TimeTicks::Now(); + if (delay > TimeDelta()) { + event_.TimedWait(delay); + } else { + // It looks like delayed_work_time_ indicates a time in the past, so we + // need to call DoDelayedWork now. + delayed_work_time_ = TimeTicks(); + } + } + // Since event_ is auto-reset, we don't need to do anything special here + // other than service each delegate method. + } + + keep_running_ = true; +} + +void MessagePumpDefault::Quit() { + keep_running_ = false; +} + +void MessagePumpDefault::ScheduleWork() { + // Since this can be called on any thread, we need to ensure that our Run + // loop wakes up. + event_.Signal(); +} + +void MessagePumpDefault::ScheduleDelayedWork( + const TimeTicks& delayed_work_time) { + // We know that we can't be blocked on Wait right now since this method can + // only be called on the same thread as Run, so we only need to update our + // record of how long to sleep when we do sleep. + delayed_work_time_ = delayed_work_time; +} + +} // namespace base
diff --git a/src/base/message_pump_default.h b/src/base/message_pump_default.h new file mode 100644 index 0000000..6deec99 --- /dev/null +++ b/src/base/message_pump_default.h
@@ -0,0 +1,42 @@ +// 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_MESSAGE_PUMP_DEFAULT_H_ +#define BASE_MESSAGE_PUMP_DEFAULT_H_ + +#include "base/message_pump.h" +#include "base/time.h" +#include "base/synchronization/waitable_event.h" + +namespace base { + +class MessagePumpDefault : public MessagePump { + public: + MessagePumpDefault(); + + // MessagePump methods: + virtual void Run(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + virtual void ScheduleWork() OVERRIDE; + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time) OVERRIDE; + + protected: + virtual ~MessagePumpDefault() {} + + private: + // This flag is set to false when Run should return. + bool keep_running_; + + // Used to sleep until there is more work to do. + WaitableEvent event_; + + // The time at which we should call DoDelayedWork. + TimeTicks delayed_work_time_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpDefault); +}; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_DEFAULT_H_
diff --git a/src/base/message_pump_dispatcher.h b/src/base/message_pump_dispatcher.h new file mode 100644 index 0000000..c3ccf3c --- /dev/null +++ b/src/base/message_pump_dispatcher.h
@@ -0,0 +1,32 @@ +// 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_MESSAGE_PUMP_DISPATCHER_H +#define BASE_MESSAGE_PUMP_DISPATCHER_H + +#include "base/base_export.h" +#include "base/event_types.h" + +namespace base { + +// Dispatcher is used during a nested invocation of Run to dispatch events when +// |RunLoop(dispatcher).Run()| is used. If |RunLoop().Run()| is invoked, +// MessageLoop does not dispatch events (or invoke TranslateMessage), rather +// every message is passed to Dispatcher's Dispatch method for dispatch. It is +// up to the Dispatcher whether or not to dispatch the event. +// +// The nested loop is exited by either posting a quit, or returning false +// from Dispatch. +class BASE_EXPORT MessagePumpDispatcher { + public: + virtual ~MessagePumpDispatcher() {} + + // Dispatches the event. If true is returned processing continues as + // normal. If false is returned, the nested loop exits immediately. + virtual bool Dispatch(const NativeEvent& event) = 0; +}; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_DISPATCHER_H
diff --git a/src/base/message_pump_glib.cc b/src/base/message_pump_glib.cc new file mode 100644 index 0000000..7436558 --- /dev/null +++ b/src/base/message_pump_glib.cc
@@ -0,0 +1,335 @@ +// 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/message_pump_glib.h" + +#include <fcntl.h> +#include <math.h> + +#include <glib.h> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/threading/platform_thread.h" + +namespace { + +// Return a timeout suitable for the glib loop, -1 to block forever, +// 0 to return right away, or a timeout in milliseconds from now. +int GetTimeIntervalMilliseconds(const base::TimeTicks& from) { + if (from.is_null()) + return -1; + + // Be careful here. TimeDelta has a precision of microseconds, but we want a + // value in milliseconds. If there are 5.5ms left, should the delay be 5 or + // 6? It should be 6 to avoid executing delayed work too early. + int delay = static_cast<int>( + ceil((from - base::TimeTicks::Now()).InMillisecondsF())); + + // If this value is negative, then we need to run delayed work soon. + return delay < 0 ? 0 : delay; +} + +// A brief refresher on GLib: +// GLib sources have four callbacks: Prepare, Check, Dispatch and Finalize. +// On each iteration of the GLib pump, it calls each source's Prepare function. +// This function should return TRUE if it wants GLib to call its Dispatch, and +// FALSE otherwise. It can also set a timeout in this case for the next time +// Prepare should be called again (it may be called sooner). +// After the Prepare calls, GLib does a poll to check for events from the +// system. File descriptors can be attached to the sources. The poll may block +// if none of the Prepare calls returned TRUE. It will block indefinitely, or +// by the minimum time returned by a source in Prepare. +// After the poll, GLib calls Check for each source that returned FALSE +// from Prepare. The return value of Check has the same meaning as for Prepare, +// making Check a second chance to tell GLib we are ready for Dispatch. +// Finally, GLib calls Dispatch for each source that is ready. If Dispatch +// returns FALSE, GLib will destroy the source. Dispatch calls may be recursive +// (i.e., you can call Run from them), but Prepare and Check cannot. +// Finalize is called when the source is destroyed. +// NOTE: It is common for subsytems to want to process pending events while +// doing intensive work, for example the flash plugin. They usually use the +// following pattern (recommended by the GTK docs): +// while (gtk_events_pending()) { +// gtk_main_iteration(); +// } +// +// gtk_events_pending just calls g_main_context_pending, which does the +// following: +// - Call prepare on all the sources. +// - Do the poll with a timeout of 0 (not blocking). +// - Call check on all the sources. +// - *Does not* call dispatch on the sources. +// - Return true if any of prepare() or check() returned true. +// +// gtk_main_iteration just calls g_main_context_iteration, which does the whole +// thing, respecting the timeout for the poll (and block, although it is +// expected not to if gtk_events_pending returned true), and call dispatch. +// +// Thus it is important to only return true from prepare or check if we +// actually have events or work to do. We also need to make sure we keep +// internal state consistent so that if prepare/check return true when called +// from gtk_events_pending, they will still return true when called right +// after, from gtk_main_iteration. +// +// For the GLib pump we try to follow the Windows UI pump model: +// - Whenever we receive a wakeup event or the timer for delayed work expires, +// we run DoWork and/or DoDelayedWork. That part will also run in the other +// event pumps. +// - We also run DoWork, DoDelayedWork, and possibly DoIdleWork in the main +// loop, around event handling. + +struct WorkSource : public GSource { + base::MessagePumpGlib* pump; +}; + +gboolean WorkSourcePrepare(GSource* source, + gint* timeout_ms) { + *timeout_ms = static_cast<WorkSource*>(source)->pump->HandlePrepare(); + // We always return FALSE, so that our timeout is honored. If we were + // to return TRUE, the timeout would be considered to be 0 and the poll + // would never block. Once the poll is finished, Check will be called. + return FALSE; +} + +gboolean WorkSourceCheck(GSource* source) { + // Only return TRUE if Dispatch should be called. + return static_cast<WorkSource*>(source)->pump->HandleCheck(); +} + +gboolean WorkSourceDispatch(GSource* source, + GSourceFunc unused_func, + gpointer unused_data) { + + static_cast<WorkSource*>(source)->pump->HandleDispatch(); + // Always return TRUE so our source stays registered. + return TRUE; +} + +// I wish these could be const, but g_source_new wants non-const. +GSourceFuncs WorkSourceFuncs = { + WorkSourcePrepare, + WorkSourceCheck, + WorkSourceDispatch, + NULL +}; + +} // namespace + + +namespace base { + +struct MessagePumpGlib::RunState { + Delegate* delegate; + MessagePumpDispatcher* dispatcher; + + // Used to flag that the current Run() invocation should return ASAP. + bool should_quit; + + // Used to count how many Run() invocations are on the stack. + int run_depth; + + // This keeps the state of whether the pump got signaled that there was new + // work to be done. Since we eat the message on the wake up pipe as soon as + // we get it, we keep that state here to stay consistent. + bool has_work; +}; + +MessagePumpGlib::MessagePumpGlib() + : state_(NULL), + context_(g_main_context_default()), + wakeup_gpollfd_(new GPollFD) { + // Create our wakeup pipe, which is used to flag when work was scheduled. + int fds[2]; + int ret = pipe(fds); + DCHECK_EQ(ret, 0); + (void)ret; // Prevent warning in release mode. + + wakeup_pipe_read_ = fds[0]; + wakeup_pipe_write_ = fds[1]; + wakeup_gpollfd_->fd = wakeup_pipe_read_; + wakeup_gpollfd_->events = G_IO_IN; + + work_source_ = g_source_new(&WorkSourceFuncs, sizeof(WorkSource)); + static_cast<WorkSource*>(work_source_)->pump = this; + g_source_add_poll(work_source_, wakeup_gpollfd_.get()); + // Use a low priority so that we let other events in the queue go first. + g_source_set_priority(work_source_, G_PRIORITY_DEFAULT_IDLE); + // This is needed to allow Run calls inside Dispatch. + g_source_set_can_recurse(work_source_, TRUE); + g_source_attach(work_source_, context_); +} + +void MessagePumpGlib::RunWithDispatcher(Delegate* delegate, + MessagePumpDispatcher* dispatcher) { +#ifndef NDEBUG + // Make sure we only run this on one thread. X/GTK only has one message pump + // so we can only have one UI loop per process. + static base::PlatformThreadId thread_id = base::PlatformThread::CurrentId(); + DCHECK(thread_id == base::PlatformThread::CurrentId()) << + "Running MessagePumpGlib on two different threads; " + "this is unsupported by GLib!"; +#endif + + RunState state; + state.delegate = delegate; + state.dispatcher = dispatcher; + state.should_quit = false; + state.run_depth = state_ ? state_->run_depth + 1 : 1; + state.has_work = false; + + RunState* previous_state = state_; + state_ = &state; + + // We really only do a single task for each iteration of the loop. If we + // have done something, assume there is likely something more to do. This + // will mean that we don't block on the message pump until there was nothing + // more to do. We also set this to true to make sure not to block on the + // first iteration of the loop, so RunUntilIdle() works correctly. + bool more_work_is_plausible = true; + + // We run our own loop instead of using g_main_loop_quit in one of the + // callbacks. This is so we only quit our own loops, and we don't quit + // nested loops run by others. TODO(deanm): Is this what we want? + for (;;) { + // Don't block if we think we have more work to do. + bool block = !more_work_is_plausible; + + more_work_is_plausible = g_main_context_iteration(context_, block); + if (state_->should_quit) + break; + + more_work_is_plausible |= state_->delegate->DoWork(); + if (state_->should_quit) + break; + + more_work_is_plausible |= + state_->delegate->DoDelayedWork(&delayed_work_time_); + if (state_->should_quit) + break; + + if (more_work_is_plausible) + continue; + + more_work_is_plausible = state_->delegate->DoIdleWork(); + if (state_->should_quit) + break; + } + + state_ = previous_state; +} + +// Return the timeout we want passed to poll. +int MessagePumpGlib::HandlePrepare() { + // We know we have work, but we haven't called HandleDispatch yet. Don't let + // the pump block so that we can do some processing. + if (state_ && // state_ may be null during tests. + state_->has_work) + return 0; + + // We don't think we have work to do, but make sure not to block + // longer than the next time we need to run delayed work. + return GetTimeIntervalMilliseconds(delayed_work_time_); +} + +bool MessagePumpGlib::HandleCheck() { + if (!state_) // state_ may be null during tests. + return false; + + // We usually have a single message on the wakeup pipe, since we are only + // signaled when the queue went from empty to non-empty, but there can be + // two messages if a task posted a task, hence we read at most two bytes. + // The glib poll will tell us whether there was data, so this read + // shouldn't block. + if (wakeup_gpollfd_->revents & G_IO_IN) { + char msg[2]; + const int num_bytes = HANDLE_EINTR(read(wakeup_pipe_read_, msg, 2)); + if (num_bytes < 1) { + NOTREACHED() << "Error reading from the wakeup pipe."; + } + DCHECK((num_bytes == 1 && msg[0] == '!') || + (num_bytes == 2 && msg[0] == '!' && msg[1] == '!')); + // Since we ate the message, we need to record that we have more work, + // because HandleCheck() may be called without HandleDispatch being called + // afterwards. + state_->has_work = true; + } + + if (state_->has_work) + return true; + + if (GetTimeIntervalMilliseconds(delayed_work_time_) == 0) { + // The timer has expired. That condition will stay true until we process + // that delayed work, so we don't need to record this differently. + return true; + } + + return false; +} + +void MessagePumpGlib::HandleDispatch() { + state_->has_work = false; + if (state_->delegate->DoWork()) { + // NOTE: on Windows at this point we would call ScheduleWork (see + // MessagePumpGlib::HandleWorkMessage in message_pump_win.cc). But here, + // instead of posting a message on the wakeup pipe, we can avoid the + // syscalls and just signal that we have more work. + state_->has_work = true; + } + + if (state_->should_quit) + return; + + state_->delegate->DoDelayedWork(&delayed_work_time_); +} + +void MessagePumpGlib::AddObserver(MessagePumpObserver* observer) { + observers_.AddObserver(observer); +} + +void MessagePumpGlib::RemoveObserver(MessagePumpObserver* observer) { + observers_.RemoveObserver(observer); +} + +void MessagePumpGlib::Run(Delegate* delegate) { + RunWithDispatcher(delegate, NULL); +} + +void MessagePumpGlib::Quit() { + if (state_) { + state_->should_quit = true; + } else { + NOTREACHED() << "Quit called outside Run!"; + } +} + +void MessagePumpGlib::ScheduleWork() { + // This can be called on any thread, so we don't want to touch any state + // variables as we would then need locks all over. This ensures that if + // we are sleeping in a poll that we will wake up. + char msg = '!'; + if (HANDLE_EINTR(write(wakeup_pipe_write_, &msg, 1)) != 1) { + NOTREACHED() << "Could not write to the UI message loop wakeup pipe!"; + } +} + +void MessagePumpGlib::ScheduleDelayedWork(const TimeTicks& delayed_work_time) { + // We need to wake up the loop in case the poll timeout needs to be + // adjusted. This will cause us to try to do work, but that's ok. + delayed_work_time_ = delayed_work_time; + ScheduleWork(); +} + +MessagePumpGlib::~MessagePumpGlib() { + g_source_destroy(work_source_); + g_source_unref(work_source_); + close(wakeup_pipe_read_); + close(wakeup_pipe_write_); +} + +MessagePumpDispatcher* MessagePumpGlib::GetDispatcher() { + return state_ ? state_->dispatcher : NULL; +} + +} // namespace base
diff --git a/src/base/message_pump_glib.h b/src/base/message_pump_glib.h new file mode 100644 index 0000000..48afb55 --- /dev/null +++ b/src/base/message_pump_glib.h
@@ -0,0 +1,110 @@ +// 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_MESSAGE_PUMP_GLIB_H_ +#define BASE_MESSAGE_PUMP_GLIB_H_ + +#include "base/base_export.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_pump.h" +#include "base/observer_list.h" +#include "base/time.h" + +typedef struct _GMainContext GMainContext; +typedef struct _GPollFD GPollFD; +typedef struct _GSource GSource; + +namespace base { + +// MessagePumpObserver is notified prior to an event being dispatched. As +// Observers are notified of every change, they have to be FAST! The platform +// specific implementation of the class is in message_pump_gtk/message_pump_x. +class MessagePumpObserver; + +// MessagePumpDispatcher is used during a nested invocation of Run to dispatch +// events. If Run is invoked with a non-NULL MessagePumpDispatcher, MessageLoop +// does not dispatch events (or invoke gtk_main_do_event), rather every event is +// passed to Dispatcher's Dispatch method for dispatch. It is up to the +// Dispatcher to dispatch, or not, the event. The platform specific +// implementation of the class is in message_pump_gtk/message_pump_x. +class MessagePumpDispatcher; + +// This class implements a base MessagePump needed for TYPE_UI MessageLoops on +// platforms using GLib. +class BASE_EXPORT MessagePumpGlib : public MessagePump { + public: + MessagePumpGlib(); + + // Like MessagePump::Run, but events are routed through dispatcher. + virtual void RunWithDispatcher(Delegate* delegate, + MessagePumpDispatcher* dispatcher); + + // Internal methods used for processing the pump callbacks. They are + // public for simplicity but should not be used directly. HandlePrepare + // is called during the prepare step of glib, and returns a timeout that + // will be passed to the poll. HandleCheck is called after the poll + // has completed, and returns whether or not HandleDispatch should be called. + // HandleDispatch is called if HandleCheck returned true. + int HandlePrepare(); + bool HandleCheck(); + void HandleDispatch(); + + // Adds an Observer, which will start receiving notifications immediately. + void AddObserver(MessagePumpObserver* observer); + + // Removes an Observer. It is safe to call this method while an Observer is + // receiving a notification callback. + void RemoveObserver(MessagePumpObserver* observer); + + // Overridden from MessagePump: + virtual void Run(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + virtual void ScheduleWork() OVERRIDE; + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time) OVERRIDE; + + protected: + virtual ~MessagePumpGlib(); + + // Returns the dispatcher for the current run state (|state_->dispatcher|). + MessagePumpDispatcher* GetDispatcher(); + + ObserverList<MessagePumpObserver>& observers() { return observers_; } + + private: + // We may make recursive calls to Run, so we save state that needs to be + // separate between them in this structure type. + struct RunState; + + RunState* state_; + + // This is a GLib structure that we can add event sources to. We use the + // default GLib context, which is the one to which all GTK events are + // dispatched. + GMainContext* context_; + + // This is the time when we need to do delayed work. + TimeTicks delayed_work_time_; + + // The work source. It is shared by all calls to Run and destroyed when + // the message pump is destroyed. + GSource* work_source_; + + // We use a wakeup pipe to make sure we'll get out of the glib polling phase + // when another thread has scheduled us to do some work. There is a glib + // mechanism g_main_context_wakeup, but this won't guarantee that our event's + // Dispatch() will be called. + int wakeup_pipe_read_; + int wakeup_pipe_write_; + // Use a scoped_ptr to avoid needing the definition of GPollFD in the header. + scoped_ptr<GPollFD> wakeup_gpollfd_; + + // List of observers. + ObserverList<MessagePumpObserver> observers_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpGlib); +}; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_GLIB_H_
diff --git a/src/base/message_pump_glib_unittest.cc b/src/base/message_pump_glib_unittest.cc new file mode 100644 index 0000000..e23cc1e --- /dev/null +++ b/src/base/message_pump_glib_unittest.cc
@@ -0,0 +1,577 @@ +// 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/message_pump_glib.h" + +#include <glib.h> +#include <math.h> + +#include <algorithm> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(TOOLKIT_GTK) +#include <gtk/gtk.h> +#endif + +namespace { + +// This class injects dummy "events" into the GLib loop. When "handled" these +// events can run tasks. This is intended to mock gtk events (the corresponding +// GLib source runs at the same priority). +class EventInjector { + public: + EventInjector() : processed_events_(0) { + source_ = static_cast<Source*>(g_source_new(&SourceFuncs, sizeof(Source))); + source_->injector = this; + g_source_attach(source_, NULL); + g_source_set_can_recurse(source_, TRUE); + } + + ~EventInjector() { + g_source_destroy(source_); + g_source_unref(source_); + } + + int HandlePrepare() { + // If the queue is empty, block. + if (events_.empty()) + return -1; + base::TimeDelta delta = events_[0].time - base::Time::NowFromSystemTime(); + return std::max(0, static_cast<int>(ceil(delta.InMillisecondsF()))); + } + + bool HandleCheck() { + if (events_.empty()) + return false; + return events_[0].time <= base::Time::NowFromSystemTime(); + } + + void HandleDispatch() { + if (events_.empty()) + return; + Event event = events_[0]; + events_.erase(events_.begin()); + ++processed_events_; + if (!event.callback.is_null()) + event.callback.Run(); + else if (!event.task.is_null()) + event.task.Run(); + } + + // Adds an event to the queue. When "handled", executes |callback|. + // delay_ms is relative to the last event if any, or to Now() otherwise. + void AddEvent(int delay_ms, const base::Closure& callback) { + AddEventHelper(delay_ms, callback, base::Closure()); + } + + void AddDummyEvent(int delay_ms) { + AddEventHelper(delay_ms, base::Closure(), base::Closure()); + } + + void AddEventAsTask(int delay_ms, const base::Closure& task) { + AddEventHelper(delay_ms, base::Closure(), task); + } + + void Reset() { + processed_events_ = 0; + events_.clear(); + } + + int processed_events() const { return processed_events_; } + + private: + struct Event { + base::Time time; + base::Closure callback; + base::Closure task; + }; + + struct Source : public GSource { + EventInjector* injector; + }; + + void AddEventHelper( + int delay_ms, const base::Closure& callback, const base::Closure& task) { + base::Time last_time; + if (!events_.empty()) + last_time = (events_.end()-1)->time; + else + last_time = base::Time::NowFromSystemTime(); + + base::Time future = last_time + base::TimeDelta::FromMilliseconds(delay_ms); + EventInjector::Event event = {future, callback, task}; + events_.push_back(event); + } + + static gboolean Prepare(GSource* source, gint* timeout_ms) { + *timeout_ms = static_cast<Source*>(source)->injector->HandlePrepare(); + return FALSE; + } + + static gboolean Check(GSource* source) { + return static_cast<Source*>(source)->injector->HandleCheck(); + } + + static gboolean Dispatch(GSource* source, + GSourceFunc unused_func, + gpointer unused_data) { + static_cast<Source*>(source)->injector->HandleDispatch(); + return TRUE; + } + + Source* source_; + std::vector<Event> events_; + int processed_events_; + static GSourceFuncs SourceFuncs; + DISALLOW_COPY_AND_ASSIGN(EventInjector); +}; + +GSourceFuncs EventInjector::SourceFuncs = { + EventInjector::Prepare, + EventInjector::Check, + EventInjector::Dispatch, + NULL +}; + +void IncrementInt(int *value) { + ++*value; +} + +// Checks how many events have been processed by the injector. +void ExpectProcessedEvents(EventInjector* injector, int count) { + EXPECT_EQ(injector->processed_events(), count); +} + +// Posts a task on the current message loop. +void PostMessageLoopTask(const tracked_objects::Location& from_here, + const base::Closure& task) { + MessageLoop::current()->PostTask(from_here, task); +} + +// Test fixture. +class MessagePumpGLibTest : public testing::Test { + public: + MessagePumpGLibTest() : loop_(NULL), injector_(NULL) { } + + // Overridden from testing::Test: + virtual void SetUp() OVERRIDE { + loop_ = new MessageLoop(MessageLoop::TYPE_UI); + injector_ = new EventInjector(); + } + virtual void TearDown() OVERRIDE { + delete injector_; + injector_ = NULL; + delete loop_; + loop_ = NULL; + } + + MessageLoop* loop() const { return loop_; } + EventInjector* injector() const { return injector_; } + + private: + MessageLoop* loop_; + EventInjector* injector_; + DISALLOW_COPY_AND_ASSIGN(MessagePumpGLibTest); +}; + +} // namespace + +TEST_F(MessagePumpGLibTest, TestQuit) { + // Checks that Quit works and that the basic infrastructure is working. + + // Quit from a task + loop()->PostTask(FROM_HERE, MessageLoop::QuitClosure()); + loop()->Run(); + EXPECT_EQ(0, injector()->processed_events()); + + injector()->Reset(); + // Quit from an event + injector()->AddEvent(0, MessageLoop::QuitClosure()); + loop()->Run(); + EXPECT_EQ(1, injector()->processed_events()); +} + +TEST_F(MessagePumpGLibTest, TestEventTaskInterleave) { + // Checks that tasks posted by events are executed before the next event if + // the posted task queue is empty. + // MessageLoop doesn't make strong guarantees that it is the case, but the + // current implementation ensures it and the tests below rely on it. + // If changes cause this test to fail, it is reasonable to change it, but + // TestWorkWhileWaitingForEvents and TestEventsWhileWaitingForWork have to be + // changed accordingly, otherwise they can become flaky. + injector()->AddEventAsTask(0, base::Bind(&base::DoNothing)); + base::Closure check_task = + base::Bind(&ExpectProcessedEvents, base::Unretained(injector()), 2); + base::Closure posted_task = + base::Bind(&PostMessageLoopTask, FROM_HERE, check_task); + injector()->AddEventAsTask(0, posted_task); + injector()->AddEventAsTask(0, base::Bind(&base::DoNothing)); + injector()->AddEvent(0, MessageLoop::QuitClosure()); + loop()->Run(); + EXPECT_EQ(4, injector()->processed_events()); + + injector()->Reset(); + injector()->AddEventAsTask(0, base::Bind(&base::DoNothing)); + check_task = + base::Bind(&ExpectProcessedEvents, base::Unretained(injector()), 2); + posted_task = base::Bind(&PostMessageLoopTask, FROM_HERE, check_task); + injector()->AddEventAsTask(0, posted_task); + injector()->AddEventAsTask(10, base::Bind(&base::DoNothing)); + injector()->AddEvent(0, MessageLoop::QuitClosure()); + loop()->Run(); + EXPECT_EQ(4, injector()->processed_events()); +} + +TEST_F(MessagePumpGLibTest, TestWorkWhileWaitingForEvents) { + int task_count = 0; + // Tests that we process tasks while waiting for new events. + // The event queue is empty at first. + for (int i = 0; i < 10; ++i) { + loop()->PostTask(FROM_HERE, base::Bind(&IncrementInt, &task_count)); + } + // After all the previous tasks have executed, enqueue an event that will + // quit. + loop()->PostTask( + FROM_HERE, + base::Bind(&EventInjector::AddEvent, base::Unretained(injector()), 0, + MessageLoop::QuitClosure())); + loop()->Run(); + ASSERT_EQ(10, task_count); + EXPECT_EQ(1, injector()->processed_events()); + + // Tests that we process delayed tasks while waiting for new events. + injector()->Reset(); + task_count = 0; + for (int i = 0; i < 10; ++i) { + loop()->PostDelayedTask( + FROM_HERE, + base::Bind(&IncrementInt, &task_count), + base::TimeDelta::FromMilliseconds(10*i)); + } + // After all the previous tasks have executed, enqueue an event that will + // quit. + // This relies on the fact that delayed tasks are executed in delay order. + // That is verified in message_loop_unittest.cc. + loop()->PostDelayedTask( + FROM_HERE, + base::Bind(&EventInjector::AddEvent, base::Unretained(injector()), 10, + MessageLoop::QuitClosure()), + base::TimeDelta::FromMilliseconds(150)); + loop()->Run(); + ASSERT_EQ(10, task_count); + EXPECT_EQ(1, injector()->processed_events()); +} + +TEST_F(MessagePumpGLibTest, TestEventsWhileWaitingForWork) { + // Tests that we process events while waiting for work. + // The event queue is empty at first. + for (int i = 0; i < 10; ++i) { + injector()->AddDummyEvent(0); + } + // After all the events have been processed, post a task that will check that + // the events have been processed (note: the task executes after the event + // that posted it has been handled, so we expect 11 at that point). + base::Closure check_task = + base::Bind(&ExpectProcessedEvents, base::Unretained(injector()), 11); + base::Closure posted_task = + base::Bind(&PostMessageLoopTask, FROM_HERE, check_task); + injector()->AddEventAsTask(10, posted_task); + + // And then quit (relies on the condition tested by TestEventTaskInterleave). + injector()->AddEvent(10, MessageLoop::QuitClosure()); + loop()->Run(); + + EXPECT_EQ(12, injector()->processed_events()); +} + +namespace { + +// This class is a helper for the concurrent events / posted tasks test below. +// It will quit the main loop once enough tasks and events have been processed, +// while making sure there is always work to do and events in the queue. +class ConcurrentHelper : public base::RefCounted<ConcurrentHelper> { + public: + explicit ConcurrentHelper(EventInjector* injector) + : injector_(injector), + event_count_(kStartingEventCount), + task_count_(kStartingTaskCount) { + } + + void FromTask() { + if (task_count_ > 0) { + --task_count_; + } + if (task_count_ == 0 && event_count_ == 0) { + MessageLoop::current()->Quit(); + } else { + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&ConcurrentHelper::FromTask, this)); + } + } + + void FromEvent() { + if (event_count_ > 0) { + --event_count_; + } + if (task_count_ == 0 && event_count_ == 0) { + MessageLoop::current()->Quit(); + } else { + injector_->AddEventAsTask( + 0, base::Bind(&ConcurrentHelper::FromEvent, this)); + } + } + + int event_count() const { return event_count_; } + int task_count() const { return task_count_; } + + private: + friend class base::RefCounted<ConcurrentHelper>; + + ~ConcurrentHelper() {} + + static const int kStartingEventCount = 20; + static const int kStartingTaskCount = 20; + + EventInjector* injector_; + int event_count_; + int task_count_; +}; + +} // namespace + +TEST_F(MessagePumpGLibTest, TestConcurrentEventPostedTask) { + // Tests that posted tasks don't starve events, nor the opposite. + // We use the helper class above. We keep both event and posted task queues + // full, the helper verifies that both tasks and events get processed. + // If that is not the case, either event_count_ or task_count_ will not get + // to 0, and MessageLoop::Quit() will never be called. + scoped_refptr<ConcurrentHelper> helper = new ConcurrentHelper(injector()); + + // Add 2 events to the queue to make sure it is always full (when we remove + // the event before processing it). + injector()->AddEventAsTask( + 0, base::Bind(&ConcurrentHelper::FromEvent, helper.get())); + injector()->AddEventAsTask( + 0, base::Bind(&ConcurrentHelper::FromEvent, helper.get())); + + // Similarly post 2 tasks. + loop()->PostTask( + FROM_HERE, base::Bind(&ConcurrentHelper::FromTask, helper.get())); + loop()->PostTask( + FROM_HERE, base::Bind(&ConcurrentHelper::FromTask, helper.get())); + + loop()->Run(); + EXPECT_EQ(0, helper->event_count()); + EXPECT_EQ(0, helper->task_count()); +} + +namespace { + +void AddEventsAndDrainGLib(EventInjector* injector) { + // Add a couple of dummy events + injector->AddDummyEvent(0); + injector->AddDummyEvent(0); + // Then add an event that will quit the main loop. + injector->AddEvent(0, MessageLoop::QuitClosure()); + + // Post a couple of dummy tasks + MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&base::DoNothing)); + MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&base::DoNothing)); + + // Drain the events + while (g_main_context_pending(NULL)) { + g_main_context_iteration(NULL, FALSE); + } +} + +} // namespace + +TEST_F(MessagePumpGLibTest, TestDrainingGLib) { + // Tests that draining events using GLib works. + loop()->PostTask( + FROM_HERE, + base::Bind(&AddEventsAndDrainGLib, base::Unretained(injector()))); + loop()->Run(); + + EXPECT_EQ(3, injector()->processed_events()); +} + + +namespace { + +#if defined(TOOLKIT_GTK) +void AddEventsAndDrainGtk(EventInjector* injector) { + // Add a couple of dummy events + injector->AddDummyEvent(0); + injector->AddDummyEvent(0); + // Then add an event that will quit the main loop. + injector->AddEvent(0, MessageLoop::QuitClosure()); + + // Post a couple of dummy tasks + MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&base::DoNothing)); + MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&base::DoNothing)); + + // Drain the events + while (gtk_events_pending()) { + gtk_main_iteration(); + } +} +#endif + +} // namespace + +#if defined(TOOLKIT_GTK) +TEST_F(MessagePumpGLibTest, TestDrainingGtk) { + // Tests that draining events using Gtk works. + loop()->PostTask( + FROM_HERE, + base::Bind(&AddEventsAndDrainGtk, base::Unretained(injector()))); + loop()->Run(); + + EXPECT_EQ(3, injector()->processed_events()); +} +#endif + +namespace { + +// Helper class that lets us run the GLib message loop. +class GLibLoopRunner : public base::RefCounted<GLibLoopRunner> { + public: + GLibLoopRunner() : quit_(false) { } + + void RunGLib() { + while (!quit_) { + g_main_context_iteration(NULL, TRUE); + } + } + + void RunLoop() { +#if defined(TOOLKIT_GTK) + while (!quit_) { + gtk_main_iteration(); + } +#else + while (!quit_) { + g_main_context_iteration(NULL, TRUE); + } +#endif + } + + void Quit() { + quit_ = true; + } + + void Reset() { + quit_ = false; + } + + private: + friend class base::RefCounted<GLibLoopRunner>; + + ~GLibLoopRunner() {} + + bool quit_; +}; + +void TestGLibLoopInternal(EventInjector* injector) { + // Allow tasks to be processed from 'native' event loops. + MessageLoop::current()->SetNestableTasksAllowed(true); + scoped_refptr<GLibLoopRunner> runner = new GLibLoopRunner(); + + int task_count = 0; + // Add a couple of dummy events + injector->AddDummyEvent(0); + injector->AddDummyEvent(0); + // Post a couple of dummy tasks + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&IncrementInt, &task_count)); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&IncrementInt, &task_count)); + // Delayed events + injector->AddDummyEvent(10); + injector->AddDummyEvent(10); + // Delayed work + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&IncrementInt, &task_count), + base::TimeDelta::FromMilliseconds(30)); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&GLibLoopRunner::Quit, runner.get()), + base::TimeDelta::FromMilliseconds(40)); + + // Run a nested, straight GLib message loop. + runner->RunGLib(); + + ASSERT_EQ(3, task_count); + EXPECT_EQ(4, injector->processed_events()); + MessageLoop::current()->Quit(); +} + +void TestGtkLoopInternal(EventInjector* injector) { + // Allow tasks to be processed from 'native' event loops. + MessageLoop::current()->SetNestableTasksAllowed(true); + scoped_refptr<GLibLoopRunner> runner = new GLibLoopRunner(); + + int task_count = 0; + // Add a couple of dummy events + injector->AddDummyEvent(0); + injector->AddDummyEvent(0); + // Post a couple of dummy tasks + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&IncrementInt, &task_count)); + MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&IncrementInt, &task_count)); + // Delayed events + injector->AddDummyEvent(10); + injector->AddDummyEvent(10); + // Delayed work + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&IncrementInt, &task_count), + base::TimeDelta::FromMilliseconds(30)); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&GLibLoopRunner::Quit, runner.get()), + base::TimeDelta::FromMilliseconds(40)); + + // Run a nested, straight Gtk message loop. + runner->RunLoop(); + + ASSERT_EQ(3, task_count); + EXPECT_EQ(4, injector->processed_events()); + MessageLoop::current()->Quit(); +} + +} // namespace + +TEST_F(MessagePumpGLibTest, TestGLibLoop) { + // Tests that events and posted tasks are correctly executed if the message + // loop is not run by MessageLoop::Run() but by a straight GLib loop. + // Note that in this case we don't make strong guarantees about niceness + // between events and posted tasks. + loop()->PostTask( + FROM_HERE, + base::Bind(&TestGLibLoopInternal, base::Unretained(injector()))); + loop()->Run(); +} + +TEST_F(MessagePumpGLibTest, TestGtkLoop) { + // Tests that events and posted tasks are correctly executed if the message + // loop is not run by MessageLoop::Run() but by a straight Gtk loop. + // Note that in this case we don't make strong guarantees about niceness + // between events and posted tasks. + loop()->PostTask( + FROM_HERE, + base::Bind(&TestGtkLoopInternal, base::Unretained(injector()))); + loop()->Run(); +}
diff --git a/src/base/message_pump_gtk.cc b/src/base/message_pump_gtk.cc new file mode 100644 index 0000000..780b4d8 --- /dev/null +++ b/src/base/message_pump_gtk.cc
@@ -0,0 +1,114 @@ +// 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/message_pump_gtk.h" + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +#include "base/profiler/scoped_profile.h" +#include "base/debug/trace_event.h" + +namespace { + +const char* EventToTypeString(const GdkEvent* event) { + switch (event->type) { + case GDK_NOTHING: return "GDK_NOTHING"; + case GDK_DELETE: return "GDK_DELETE"; + case GDK_DESTROY: return "GDK_DESTROY"; + case GDK_EXPOSE: return "GDK_EXPOSE"; + case GDK_MOTION_NOTIFY: return "GDK_MOTION_NOTIFY"; + case GDK_BUTTON_PRESS: return "GDK_BUTTON_PRESS"; + case GDK_2BUTTON_PRESS: return "GDK_2BUTTON_PRESS"; + case GDK_3BUTTON_PRESS: return "GDK_3BUTTON_PRESS"; + case GDK_BUTTON_RELEASE: return "GDK_BUTTON_RELEASE"; + case GDK_KEY_PRESS: return "GDK_KEY_PRESS"; + case GDK_KEY_RELEASE: return "GDK_KEY_RELEASE"; + case GDK_ENTER_NOTIFY: return "GDK_ENTER_NOTIFY"; + case GDK_LEAVE_NOTIFY: return "GDK_LEAVE_NOTIFY"; + case GDK_FOCUS_CHANGE: return "GDK_FOCUS_CHANGE"; + case GDK_CONFIGURE: return "GDK_CONFIGURE"; + case GDK_MAP: return "GDK_MAP"; + case GDK_UNMAP: return "GDK_UNMAP"; + case GDK_PROPERTY_NOTIFY: return "GDK_PROPERTY_NOTIFY"; + case GDK_SELECTION_CLEAR: return "GDK_SELECTION_CLEAR"; + case GDK_SELECTION_REQUEST: return "GDK_SELECTION_REQUEST"; + case GDK_SELECTION_NOTIFY: return "GDK_SELECTION_NOTIFY"; + case GDK_PROXIMITY_IN: return "GDK_PROXIMITY_IN"; + case GDK_PROXIMITY_OUT: return "GDK_PROXIMITY_OUT"; + case GDK_DRAG_ENTER: return "GDK_DRAG_ENTER"; + case GDK_DRAG_LEAVE: return "GDK_DRAG_LEAVE"; + case GDK_DRAG_MOTION: return "GDK_DRAG_MOTION"; + case GDK_DRAG_STATUS: return "GDK_DRAG_STATUS"; + case GDK_DROP_START: return "GDK_DROP_START"; + case GDK_DROP_FINISHED: return "GDK_DROP_FINISHED"; + case GDK_CLIENT_EVENT: return "GDK_CLIENT_EVENT"; + case GDK_VISIBILITY_NOTIFY: return "GDK_VISIBILITY_NOTIFY"; + case GDK_NO_EXPOSE: return "GDK_NO_EXPOSE"; + case GDK_SCROLL: return "GDK_SCROLL"; + case GDK_WINDOW_STATE: return "GDK_WINDOW_STATE"; + case GDK_SETTING: return "GDK_SETTING"; + case GDK_OWNER_CHANGE: return "GDK_OWNER_CHANGE"; + case GDK_GRAB_BROKEN: return "GDK_GRAB_BROKEN"; + case GDK_DAMAGE: return "GDK_DAMAGE"; + default: + return "Unknown Gdk Event"; + } +} + +} + +namespace base { + +MessagePumpGtk::MessagePumpGtk() : MessagePumpGlib() { + gdk_event_handler_set(&EventDispatcher, this, NULL); +} + +void MessagePumpGtk::DispatchEvents(GdkEvent* event) { + UNSHIPPED_TRACE_EVENT1("task", "MessagePumpGtk::DispatchEvents", + "type", EventToTypeString(event)); + + WillProcessEvent(event); + + MessagePumpDispatcher* dispatcher = GetDispatcher(); + if (!dispatcher) + gtk_main_do_event(event); + else if (!dispatcher->Dispatch(event)) + Quit(); + + DidProcessEvent(event); +} + +// static +Display* MessagePumpGtk::GetDefaultXDisplay() { + static GdkDisplay* display = gdk_display_get_default(); + if (!display) { + // GTK / GDK has not been initialized, which is a decision we wish to + // support, for example for the GPU process. + static Display* xdisplay = XOpenDisplay(NULL); + return xdisplay; + } + return GDK_DISPLAY_XDISPLAY(display); +} + +MessagePumpGtk::~MessagePumpGtk() { + gdk_event_handler_set(reinterpret_cast<GdkEventFunc>(gtk_main_do_event), + this, NULL); +} + +void MessagePumpGtk::WillProcessEvent(GdkEvent* event) { + FOR_EACH_OBSERVER(MessagePumpObserver, observers(), WillProcessEvent(event)); +} + +void MessagePumpGtk::DidProcessEvent(GdkEvent* event) { + FOR_EACH_OBSERVER(MessagePumpObserver, observers(), DidProcessEvent(event)); +} + +// static +void MessagePumpGtk::EventDispatcher(GdkEvent* event, gpointer data) { + MessagePumpGtk* message_pump = reinterpret_cast<MessagePumpGtk*>(data); + message_pump->DispatchEvents(event); +} + +} // namespace base
diff --git a/src/base/message_pump_gtk.h b/src/base/message_pump_gtk.h new file mode 100644 index 0000000..ba5cff1 --- /dev/null +++ b/src/base/message_pump_gtk.h
@@ -0,0 +1,76 @@ +// 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_MESSAGE_PUMP_GTK_H_ +#define BASE_MESSAGE_PUMP_GTK_H_ + +#include "base/message_pump_glib.h" + +typedef union _GdkEvent GdkEvent; +typedef struct _XDisplay Display; + +namespace base { + +// The documentation for this class is in message_pump_glib.h +class MessagePumpObserver { + public: + // This method is called before processing a message. + virtual void WillProcessEvent(GdkEvent* event) = 0; + + // This method is called after processing a message. + virtual void DidProcessEvent(GdkEvent* event) = 0; + + protected: + virtual ~MessagePumpObserver() {} +}; + +// The documentation for this class is in message_pump_glib.h +// +// The nested loop is exited by either posting a quit, or returning false +// from Dispatch. +class MessagePumpDispatcher { + public: + // Dispatches the event. If true is returned processing continues as + // normal. If false is returned, the nested loop exits immediately. + virtual bool Dispatch(GdkEvent* event) = 0; + + protected: + virtual ~MessagePumpDispatcher() {} +}; + +// This class implements a message-pump for dispatching GTK events. +class BASE_EXPORT MessagePumpGtk : public MessagePumpGlib { + public: + MessagePumpGtk(); + + // Dispatch an available GdkEvent. Essentially this allows a subclass to do + // some task before/after calling the default handler (EventDispatcher). + void DispatchEvents(GdkEvent* event); + + // Returns default X Display. + static Display* GetDefaultXDisplay(); + + protected: + virtual ~MessagePumpGtk(); + + private: + // Invoked from EventDispatcher. Notifies all observers we're about to + // process an event. + void WillProcessEvent(GdkEvent* event); + + // Invoked from EventDispatcher. Notifies all observers we processed an + // event. + void DidProcessEvent(GdkEvent* event); + + // Callback prior to gdk dispatching an event. + static void EventDispatcher(GdkEvent* event, void* data); + + DISALLOW_COPY_AND_ASSIGN(MessagePumpGtk); +}; + +typedef MessagePumpGtk MessagePumpForUI; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_GTK_H_
diff --git a/src/base/message_pump_io_ios.cc b/src/base/message_pump_io_ios.cc new file mode 100644 index 0000000..ca800ff --- /dev/null +++ b/src/base/message_pump_io_ios.cc
@@ -0,0 +1,209 @@ +// Copyright 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/message_pump_io_ios.h" + +namespace base { + +MessagePumpIOSForIO::FileDescriptorWatcher::FileDescriptorWatcher() + : is_persistent_(false), + fdref_(NULL), + callback_types_(0), + fd_source_(NULL), + pump_(NULL), + watcher_(NULL) { +} + +MessagePumpIOSForIO::FileDescriptorWatcher::~FileDescriptorWatcher() { + StopWatchingFileDescriptor(); +} + +bool MessagePumpIOSForIO::FileDescriptorWatcher::StopWatchingFileDescriptor() { + if (fdref_ == NULL) + return true; + + CFFileDescriptorDisableCallBacks(fdref_, callback_types_); + pump_->RemoveRunLoopSource(fd_source_); + fd_source_.reset(); + fdref_.reset(); + callback_types_ = 0; + pump_ = NULL; + watcher_ = NULL; + return true; +} + +void MessagePumpIOSForIO::FileDescriptorWatcher::Init( + CFFileDescriptorRef fdref, + CFOptionFlags callback_types, + CFRunLoopSourceRef fd_source, + bool is_persistent) { + DCHECK(fdref); + DCHECK(!fdref_); + + is_persistent_ = is_persistent; + fdref_.reset(fdref); + callback_types_ = callback_types; + fd_source_.reset(fd_source); +} + +void MessagePumpIOSForIO::FileDescriptorWatcher::OnFileCanReadWithoutBlocking( + int fd, + MessagePumpIOSForIO* pump) { + DCHECK(callback_types_ & kCFFileDescriptorReadCallBack); + pump->WillProcessIOEvent(); + watcher_->OnFileCanReadWithoutBlocking(fd); + pump->DidProcessIOEvent(); +} + +void MessagePumpIOSForIO::FileDescriptorWatcher::OnFileCanWriteWithoutBlocking( + int fd, + MessagePumpIOSForIO* pump) { + DCHECK(callback_types_ & kCFFileDescriptorWriteCallBack); + pump->WillProcessIOEvent(); + watcher_->OnFileCanWriteWithoutBlocking(fd); + pump->DidProcessIOEvent(); +} + +MessagePumpIOSForIO::MessagePumpIOSForIO() { +} + +MessagePumpIOSForIO::~MessagePumpIOSForIO() { +} + +bool MessagePumpIOSForIO::WatchFileDescriptor( + int fd, + bool persistent, + int mode, + FileDescriptorWatcher *controller, + Watcher *delegate) { + DCHECK_GE(fd, 0); + DCHECK(controller); + DCHECK(delegate); + DCHECK(mode == WATCH_READ || mode == WATCH_WRITE || mode == WATCH_READ_WRITE); + + // WatchFileDescriptor should be called on the pump thread. It is not + // threadsafe, and your watcher may never be registered. + DCHECK(watch_file_descriptor_caller_checker_.CalledOnValidThread()); + + CFFileDescriptorContext source_context = {0}; + source_context.info = controller; + + CFOptionFlags callback_types = 0; + if (mode & WATCH_READ) { + callback_types |= kCFFileDescriptorReadCallBack; + } + if (mode & WATCH_WRITE) { + callback_types |= kCFFileDescriptorWriteCallBack; + } + + CFFileDescriptorRef fdref = controller->fdref_; + if (fdref == NULL) { + base::mac::ScopedCFTypeRef<CFFileDescriptorRef> scoped_fdref( + CFFileDescriptorCreate(kCFAllocatorDefault, fd, false, HandleFdIOEvent, + &source_context)); + if (scoped_fdref == NULL) { + NOTREACHED() << "CFFileDescriptorCreate failed"; + return false; + } + + CFFileDescriptorEnableCallBacks(scoped_fdref, callback_types); + + // TODO(wtc): what should the 'order' argument be? + base::mac::ScopedCFTypeRef<CFRunLoopSourceRef> scoped_fd_source( + CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, + scoped_fdref, + 0)); + if (scoped_fd_source == NULL) { + NOTREACHED() << "CFFileDescriptorCreateRunLoopSource failed"; + return false; + } + CFRunLoopAddSource(run_loop(), scoped_fd_source, kCFRunLoopCommonModes); + + // Transfer ownership of scoped_fdref and fd_source to controller. + controller->Init(scoped_fdref.release(), callback_types, + scoped_fd_source.release(), persistent); + } else { + // It's illegal to use this function to listen on 2 separate fds with the + // same |controller|. + if (CFFileDescriptorGetNativeDescriptor(fdref) != fd) { + NOTREACHED() << "FDs don't match: " + << CFFileDescriptorGetNativeDescriptor(fdref) + << " != " << fd; + return false; + } + if (persistent != controller->is_persistent_) { + NOTREACHED() << "persistent doesn't match"; + return false; + } + + // Combine old/new event masks. + CFFileDescriptorDisableCallBacks(fdref, controller->callback_types_); + controller->callback_types_ |= callback_types; + CFFileDescriptorEnableCallBacks(fdref, controller->callback_types_); + } + + controller->set_watcher(delegate); + controller->set_pump(this); + + return true; +} + +void MessagePumpIOSForIO::RemoveRunLoopSource(CFRunLoopSourceRef source) { + CFRunLoopRemoveSource(run_loop(), source, kCFRunLoopCommonModes); +} + +void MessagePumpIOSForIO::AddIOObserver(IOObserver *obs) { + io_observers_.AddObserver(obs); +} + +void MessagePumpIOSForIO::RemoveIOObserver(IOObserver *obs) { + io_observers_.RemoveObserver(obs); +} + +void MessagePumpIOSForIO::WillProcessIOEvent() { + FOR_EACH_OBSERVER(IOObserver, io_observers_, WillProcessIOEvent()); +} + +void MessagePumpIOSForIO::DidProcessIOEvent() { + FOR_EACH_OBSERVER(IOObserver, io_observers_, DidProcessIOEvent()); +} + +// static +void MessagePumpIOSForIO::HandleFdIOEvent(CFFileDescriptorRef fdref, + CFOptionFlags callback_types, + void* context) { + FileDescriptorWatcher* controller = + static_cast<FileDescriptorWatcher*>(context); + DCHECK_EQ(fdref, controller->fdref_); + + // Ensure that |fdref| will remain live for the duration of this function + // call even if |controller| is deleted or |StopWatchingFileDescriptor()| is + // called, either of which will cause |fdref| to be released. + mac::ScopedCFTypeRef<CFFileDescriptorRef> scoped_fdref( + fdref, base::scoped_policy::RETAIN); + + int fd = CFFileDescriptorGetNativeDescriptor(fdref); + MessagePumpIOSForIO* pump = controller->pump(); + if (callback_types & kCFFileDescriptorWriteCallBack) + controller->OnFileCanWriteWithoutBlocking(fd, pump); + + // Perform the read callback only if the file descriptor has not been + // invalidated in the write callback. As |FileDescriptorWatcher| invalidates + // its file descriptor on destruction, the file descriptor being valid also + // guarantees that |controller| has not been deleted. + if (callback_types & kCFFileDescriptorReadCallBack && + CFFileDescriptorIsValid(fdref)) { + DCHECK_EQ(fdref, controller->fdref_); + controller->OnFileCanReadWithoutBlocking(fd, pump); + } + + // Re-enable callbacks after the read/write if the file descriptor is still + // valid and the controller is persistent. + if (CFFileDescriptorIsValid(fdref) && controller->is_persistent_) { + DCHECK_EQ(fdref, controller->fdref_); + CFFileDescriptorEnableCallBacks(fdref, callback_types); + } +} + +} // namespace base
diff --git a/src/base/message_pump_io_ios.h b/src/base/message_pump_io_ios.h new file mode 100644 index 0000000..81f529d --- /dev/null +++ b/src/base/message_pump_io_ios.h
@@ -0,0 +1,142 @@ +// Copyright 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_MESSAGE_PUMP_IO_IOS_H_ +#define BASE_MESSAGE_PUMP_IO_IOS_H_ + +#include "base/base_export.h" +#include "base/mac/scoped_cffiledescriptorref.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/memory/ref_counted.h" +#include "base/message_pump_mac.h" +#include "base/observer_list.h" + +namespace base { + +// This file introduces a class to monitor sockets and issue callbacks when +// sockets are ready for I/O on iOS. +class BASE_EXPORT MessagePumpIOSForIO : public MessagePumpNSRunLoop { + public: + class IOObserver { + public: + IOObserver() {} + + // An IOObserver is an object that receives IO notifications from the + // MessagePump. + // + // NOTE: An IOObserver implementation should be extremely fast! + virtual void WillProcessIOEvent() = 0; + virtual void DidProcessIOEvent() = 0; + + protected: + virtual ~IOObserver() {} + }; + + // Used with WatchFileDescriptor to asynchronously monitor the I/O readiness + // of a file descriptor. + class Watcher { + public: + // Called from MessageLoop::Run when an FD can be read from/written to + // without blocking + virtual void OnFileCanReadWithoutBlocking(int fd) = 0; + virtual void OnFileCanWriteWithoutBlocking(int fd) = 0; + + protected: + virtual ~Watcher() {} + }; + + // Object returned by WatchFileDescriptor to manage further watching. + class FileDescriptorWatcher { + public: + FileDescriptorWatcher(); + ~FileDescriptorWatcher(); // Implicitly calls StopWatchingFileDescriptor. + + // NOTE: These methods aren't called StartWatching()/StopWatching() to + // avoid confusion with the win32 ObjectWatcher class. + + // Stop watching the FD, always safe to call. No-op if there's nothing + // to do. + bool StopWatchingFileDescriptor(); + + private: + friend class MessagePumpIOSForIO; + friend class MessagePumpIOSForIOTest; + + // Called by MessagePumpIOSForIO, ownership of |fdref| and |fd_source| + // is transferred to this object. + void Init(CFFileDescriptorRef fdref, + CFOptionFlags callback_types, + CFRunLoopSourceRef fd_source, + bool is_persistent); + + void set_pump(MessagePumpIOSForIO* pump) { pump_ = pump; } + MessagePumpIOSForIO* pump() const { return pump_; } + + void set_watcher(Watcher* watcher) { watcher_ = watcher; } + + void OnFileCanReadWithoutBlocking(int fd, MessagePumpIOSForIO* pump); + void OnFileCanWriteWithoutBlocking(int fd, MessagePumpIOSForIO* pump); + + bool is_persistent_; // false if this event is one-shot. + base::mac::ScopedCFFileDescriptorRef fdref_; + CFOptionFlags callback_types_; + base::mac::ScopedCFTypeRef<CFRunLoopSourceRef> fd_source_; + scoped_refptr<MessagePumpIOSForIO> pump_; + Watcher* watcher_; + + DISALLOW_COPY_AND_ASSIGN(FileDescriptorWatcher); + }; + + enum Mode { + WATCH_READ = 1 << 0, + WATCH_WRITE = 1 << 1, + WATCH_READ_WRITE = WATCH_READ | WATCH_WRITE + }; + + MessagePumpIOSForIO(); + + // Have the current thread's message loop watch for a a situation in which + // reading/writing to the FD can be performed without blocking. + // Callers must provide a preallocated FileDescriptorWatcher object which + // can later be used to manage the lifetime of this event. + // If a FileDescriptorWatcher is passed in which is already attached to + // an event, then the effect is cumulative i.e. after the call |controller| + // will watch both the previous event and the new one. + // If an error occurs while calling this method in a cumulative fashion, the + // event previously attached to |controller| is aborted. + // Returns true on success. + // Must be called on the same thread the message_pump is running on. + bool WatchFileDescriptor(int fd, + bool persistent, + int mode, + FileDescriptorWatcher *controller, + Watcher *delegate); + + void RemoveRunLoopSource(CFRunLoopSourceRef source); + + void AddIOObserver(IOObserver* obs); + void RemoveIOObserver(IOObserver* obs); + + protected: + virtual ~MessagePumpIOSForIO(); + + private: + friend class MessagePumpIOSForIOTest; + + void WillProcessIOEvent(); + void DidProcessIOEvent(); + + static void HandleFdIOEvent(CFFileDescriptorRef fdref, + CFOptionFlags callback_types, + void* context); + + ObserverList<IOObserver> io_observers_; + ThreadChecker watch_file_descriptor_caller_checker_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpIOSForIO); +}; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_IO_IOS_H_
diff --git a/src/base/message_pump_io_ios_unittest.cc b/src/base/message_pump_io_ios_unittest.cc new file mode 100644 index 0000000..7a00d11 --- /dev/null +++ b/src/base/message_pump_io_ios_unittest.cc
@@ -0,0 +1,188 @@ +// Copyright 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/message_pump_io_ios.h" + +#include <unistd.h> + +#include "base/message_loop.h" +#include "base/posix/eintr_wrapper.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +class MessagePumpIOSForIOTest : public testing::Test { + protected: + MessagePumpIOSForIOTest() + : ui_loop_(MessageLoop::TYPE_UI), + io_thread_("MessagePumpIOSForIOTestIOThread") {} + virtual ~MessagePumpIOSForIOTest() {} + + virtual void SetUp() OVERRIDE { + Thread::Options options(MessageLoop::TYPE_IO, 0); + ASSERT_TRUE(io_thread_.StartWithOptions(options)); + ASSERT_EQ(MessageLoop::TYPE_IO, io_thread_.message_loop()->type()); + int ret = pipe(pipefds_); + ASSERT_EQ(0, ret); + ret = pipe(alternate_pipefds_); + ASSERT_EQ(0, ret); + } + + virtual void TearDown() OVERRIDE { + if (HANDLE_EINTR(close(pipefds_[0])) < 0) + PLOG(ERROR) << "close"; + if (HANDLE_EINTR(close(pipefds_[1])) < 0) + PLOG(ERROR) << "close"; + } + + MessageLoop* ui_loop() { return &ui_loop_; } + MessageLoopForIO* io_loop() const { + return static_cast<MessageLoopForIO*>(io_thread_.message_loop()); + } + + void HandleFdIOEvent(MessageLoopForIO::FileDescriptorWatcher* watcher) { + MessagePumpIOSForIO::HandleFdIOEvent(watcher->fdref_, + kCFFileDescriptorReadCallBack | kCFFileDescriptorWriteCallBack, + watcher); + } + + int pipefds_[2]; + int alternate_pipefds_[2]; + + private: + MessageLoop ui_loop_; + Thread io_thread_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpIOSForIOTest); +}; + +namespace { + +// Concrete implementation of MessagePumpIOSForIO::Watcher that does +// nothing useful. +class StupidWatcher : public MessagePumpIOSForIO::Watcher { + public: + virtual ~StupidWatcher() {} + + // base:MessagePumpIOSForIO::Watcher interface + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE {} + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {} +}; + +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +// Test to make sure that we catch calling WatchFileDescriptor off of the +// wrong thread. +TEST_F(MessagePumpIOSForIOTest, TestWatchingFromBadThread) { + MessagePumpIOSForIO::FileDescriptorWatcher watcher; + StupidWatcher delegate; + + ASSERT_DEBUG_DEATH(io_loop()->WatchFileDescriptor( + STDOUT_FILENO, false, MessageLoopForIO::WATCH_READ, &watcher, &delegate), + "Check failed: " + "watch_file_descriptor_caller_checker_.CalledOnValidThread()"); +} + +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +class BaseWatcher : public MessagePumpIOSForIO::Watcher { + public: + BaseWatcher(MessagePumpIOSForIO::FileDescriptorWatcher* controller) + : controller_(controller) { + DCHECK(controller_); + } + virtual ~BaseWatcher() {} + + // MessagePumpIOSForIO::Watcher interface + virtual void OnFileCanReadWithoutBlocking(int /* fd */) OVERRIDE { + NOTREACHED(); + } + + virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE { + NOTREACHED(); + } + + protected: + MessagePumpIOSForIO::FileDescriptorWatcher* controller_; +}; + +class DeleteWatcher : public BaseWatcher { + public: + explicit DeleteWatcher( + MessagePumpIOSForIO::FileDescriptorWatcher* controller) + : BaseWatcher(controller) {} + + virtual ~DeleteWatcher() { + DCHECK(!controller_); + } + + virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE { + DCHECK(controller_); + delete controller_; + controller_ = NULL; + } +}; + +TEST_F(MessagePumpIOSForIOTest, DeleteWatcher) { + scoped_refptr<MessagePumpIOSForIO> pump(new MessagePumpIOSForIO); + MessagePumpIOSForIO::FileDescriptorWatcher* watcher = + new MessagePumpIOSForIO::FileDescriptorWatcher; + DeleteWatcher delegate(watcher); + pump->WatchFileDescriptor(pipefds_[1], + false, MessagePumpIOSForIO::WATCH_READ_WRITE, watcher, &delegate); + + // Spoof a callback. + HandleFdIOEvent(watcher); +} + +class StopWatcher : public BaseWatcher { + public: + StopWatcher(MessagePumpIOSForIO::FileDescriptorWatcher* controller, + MessagePumpIOSForIO* pump, + int fd_to_start_watching = -1) + : BaseWatcher(controller), + pump_(pump), + fd_to_start_watching_(fd_to_start_watching) {} + + virtual ~StopWatcher() {} + + virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE { + controller_->StopWatchingFileDescriptor(); + if (fd_to_start_watching_ >= 0) { + pump_->WatchFileDescriptor(fd_to_start_watching_, + false, MessagePumpIOSForIO::WATCH_READ_WRITE, controller_, this); + } + } + + private: + MessagePumpIOSForIO* pump_; + int fd_to_start_watching_; +}; + +TEST_F(MessagePumpIOSForIOTest, StopWatcher) { + scoped_refptr<MessagePumpIOSForIO> pump(new MessagePumpIOSForIO); + MessagePumpIOSForIO::FileDescriptorWatcher watcher; + StopWatcher delegate(&watcher, pump); + pump->WatchFileDescriptor(pipefds_[1], + false, MessagePumpIOSForIO::WATCH_READ_WRITE, &watcher, &delegate); + + // Spoof a callback. + HandleFdIOEvent(&watcher); +} + +TEST_F(MessagePumpIOSForIOTest, StopWatcherAndWatchSomethingElse) { + scoped_refptr<MessagePumpIOSForIO> pump(new MessagePumpIOSForIO); + MessagePumpIOSForIO::FileDescriptorWatcher watcher; + StopWatcher delegate(&watcher, pump, alternate_pipefds_[1]); + pump->WatchFileDescriptor(pipefds_[1], + false, MessagePumpIOSForIO::WATCH_READ_WRITE, &watcher, &delegate); + + // Spoof a callback. + HandleFdIOEvent(&watcher); +} + +} // namespace + +} // namespace base
diff --git a/src/base/message_pump_io_starboard.cc b/src/base/message_pump_io_starboard.cc new file mode 100644 index 0000000..bbd1707 --- /dev/null +++ b/src/base/message_pump_io_starboard.cc
@@ -0,0 +1,289 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/message_pump_io_starboard.h" + +#include "base/auto_reset.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/posix/eintr_wrapper.h" +#include "base/time.h" +#include "starboard/socket.h" +#include "starboard/socket_waiter.h" + +namespace base { + +MessagePumpIOStarboard::SocketWatcher::SocketWatcher() + : socket_(kSbSocketInvalid), + pump_(NULL), + watcher_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {} + +MessagePumpIOStarboard::SocketWatcher::~SocketWatcher() { + if (SbSocketIsValid(socket_)) { + StopWatchingSocket(); + } +} + +bool MessagePumpIOStarboard::SocketWatcher::StopWatchingSocket() { + SbSocket socket = Release(); + bool result = true; + if (SbSocketIsValid(socket)) { + DCHECK(pump_); + result = pump_->StopWatching(socket); + } + pump_ = NULL; + watcher_ = NULL; + return result; +} + +void MessagePumpIOStarboard::SocketWatcher::Init(SbSocket socket, + bool persistent) { + DCHECK(socket); + DCHECK(!socket_); + socket_ = socket; + persistent_ = persistent; +} + +SbSocket MessagePumpIOStarboard::SocketWatcher::Release() { + SbSocket socket = socket_; + socket_ = kSbSocketInvalid; + return socket; +} + +void MessagePumpIOStarboard::SocketWatcher::OnSocketReadyToRead( + SbSocket socket, + MessagePumpIOStarboard* pump) { + if (!watcher_) + return; + pump->WillProcessIOEvent(); + watcher_->OnSocketReadyToRead(socket); + pump->DidProcessIOEvent(); +} + +void MessagePumpIOStarboard::SocketWatcher::OnSocketReadyToWrite( + SbSocket socket, + MessagePumpIOStarboard* pump) { + if (!watcher_) + return; + pump->WillProcessIOEvent(); + watcher_->OnSocketReadyToWrite(socket); + pump->DidProcessIOEvent(); +} + +MessagePumpIOStarboard::MessagePumpIOStarboard() + : keep_running_(true), + in_run_(false), + processed_io_events_(false), + waiter_(SbSocketWaiterCreate()) {} + +MessagePumpIOStarboard::~MessagePumpIOStarboard() { + DCHECK(SbSocketWaiterIsValid(waiter_)); + SbSocketWaiterDestroy(waiter_); +} + +bool MessagePumpIOStarboard::Watch(SbSocket socket, + bool persistent, + int mode, + SocketWatcher* controller, + Watcher* delegate) { + DCHECK(SbSocketIsValid(socket)); + DCHECK(controller); + DCHECK(delegate); + DCHECK(mode == WATCH_READ || mode == WATCH_WRITE || mode == WATCH_READ_WRITE); + // Watch should be called on the pump thread. It is not threadsafe, and your + // watcher may never be registered. + DCHECK(watch_socket_caller_checker_.CalledOnValidThread()); + + int interests = kSbSocketWaiterInterestNone; + if (mode & WATCH_READ) { + interests |= kSbSocketWaiterInterestRead; + } + if (mode & WATCH_WRITE) { + interests |= kSbSocketWaiterInterestWrite; + } + + SbSocket old_socket = controller->Release(); + if (SbSocketIsValid(old_socket)) { + // It's illegal to use this function to listen on 2 separate fds with the + // same |controller|. + if (old_socket != socket) { + NOTREACHED() << "Sockets don't match" << old_socket << "!=" << socket; + return false; + } + + // Make sure we don't pick up any funky internal masks. + int old_interest_mask = + controller->interests() & + (kSbSocketWaiterInterestRead | kSbSocketWaiterInterestWrite); + + // Combine old/new event masks. + interests |= old_interest_mask; + + // Must disarm the event before we can reuse it. + SbSocketWaiterRemove(waiter_, old_socket); + } + + // Set current interest mask and waiter for this event. + bool result = + SbSocketWaiterAdd(waiter_, socket, controller, OnSocketWaiterNotification, + interests, persistent); + DCHECK(result); + + controller->Init(socket, persistent); + controller->set_watcher(delegate); + controller->set_pump(this); + + return true; +} + +bool MessagePumpIOStarboard::StopWatching(SbSocket socket) { + return SbSocketWaiterRemove(waiter_, socket); +} + +void MessagePumpIOStarboard::AddIOObserver(IOObserver* obs) { + io_observers_.AddObserver(obs); +} + +void MessagePumpIOStarboard::RemoveIOObserver(IOObserver* obs) { + io_observers_.RemoveObserver(obs); +} + +// Reentrant! +void MessagePumpIOStarboard::Run(Delegate* delegate) { + DCHECK(keep_running_) << "Quit must have been called outside of Run!"; + AutoReset<bool> auto_reset_in_run(&in_run_, true); + + for (;;) { + bool did_work = delegate->DoWork(); + if (!keep_running_) + break; + + // NOTE: We need to have a wake-up pending any time there is work queued, + // and the MessageLoop only wakes up the pump when the work queue goes from + // 0 tasks to 1 task. If any work is scheduled on this MessageLoop (from + // another thread) anywhere in between the call to DoWork() above and the + // call to SbSocketWaiterWaitTimed() below, SbSocketWaiterWaitTimed() will + // consume a wake-up, but leave the work queued. This will cause the + // blocking wait further below to hang forever, no matter how many more + // items are added to the queue. To resolve this, if this wait consumes a + // wake-up, we set did_work to true so we will jump back to the top of the + // loop and call delegate->DoWork() before we decide to block. + + SbSocketWaiterResult result = SbSocketWaiterWaitTimed(waiter_, 0); + DCHECK_NE(kSbSocketWaiterResultInvalid, result); + did_work |= + (result == kSbSocketWaiterResultWokenUp) || processed_io_events_; + processed_io_events_ = false; + if (!keep_running_) + break; + + // Let's play catchup on all delayed work before we loop. This fixes bug + // #5534709 by processing a large number of short delayed tasks quickly + // before looping back to process non-delayed tasks (like paint). + bool did_delayed_work = false; + do { + did_delayed_work = delegate->DoDelayedWork(&delayed_work_time_); + did_work |= did_delayed_work; + } while (did_delayed_work && keep_running_); + + if (!keep_running_) + break; + + if (did_work) + continue; + + did_work = delegate->DoIdleWork(); + if (!keep_running_) + break; + + if (did_work) + continue; + + if (delayed_work_time_.is_null()) { + SbSocketWaiterWait(waiter_); + } else { + TimeDelta delay = delayed_work_time_ - TimeTicks::Now(); + if (delay > TimeDelta()) { + SbSocketWaiterWaitTimed(waiter_, delay.ToSbTime()); + } else { + // It looks like delayed_work_time_ indicates a time in the past, so we + // need to call DoDelayedWork now. + delayed_work_time_ = TimeTicks(); + } + } + } + + keep_running_ = true; +} + +void MessagePumpIOStarboard::Quit() { + DCHECK(in_run_); + // Tell both the SbObjectWaiter and Run that they should break out of their + // loops. + keep_running_ = false; + ScheduleWork(); +} + +void MessagePumpIOStarboard::ScheduleWork() { + SbSocketWaiterWakeUp(waiter_); +} + +void MessagePumpIOStarboard::ScheduleDelayedWork( + const TimeTicks& delayed_work_time) { + // We know that we can't be blocked on Wait right now since this method can + // only be called on the same thread as Run, so we only need to update our + // record of how long to sleep when we do sleep. + delayed_work_time_ = delayed_work_time; + ScheduleWork(); +} + +void MessagePumpIOStarboard::WillProcessIOEvent() { + FOR_EACH_OBSERVER(IOObserver, io_observers_, WillProcessIOEvent()); +} + +void MessagePumpIOStarboard::DidProcessIOEvent() { + FOR_EACH_OBSERVER(IOObserver, io_observers_, DidProcessIOEvent()); +} + +// static +void MessagePumpIOStarboard::OnSocketWaiterNotification(SbSocketWaiter waiter, + SbSocket socket, + void* context, + int ready_interests) { + base::WeakPtr<SocketWatcher> controller = + static_cast<SocketWatcher*>(context)->weak_factory_.GetWeakPtr(); + DCHECK(controller.get()); + + MessagePumpIOStarboard* pump = controller->pump(); + pump->processed_io_events_ = true; + + // If not persistent, the watch has been released at this point. + if (!controller->persistent()) { + controller->Release(); + } + + if (ready_interests & kSbSocketWaiterInterestWrite) { + controller->OnSocketReadyToWrite(socket, pump); + } + + // Check |controller| in case it's been deleted previously. + if (controller.get() && ready_interests & kSbSocketWaiterInterestRead) { + controller->OnSocketReadyToRead(socket, pump); + } +} + +} // namespace base
diff --git a/src/base/message_pump_io_starboard.h b/src/base/message_pump_io_starboard.h new file mode 100644 index 0000000..c840181 --- /dev/null +++ b/src/base/message_pump_io_starboard.h
@@ -0,0 +1,179 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BASE_MESSAGE_PUMP_IO_STARBOARD_H_ +#define BASE_MESSAGE_PUMP_IO_STARBOARD_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "base/message_pump.h" +#include "base/observer_list.h" +#include "base/threading/thread_checker.h" +#include "base/time.h" +#include "starboard/socket.h" +#include "starboard/socket_waiter.h" + +namespace base { + +// Class to monitor sockets and issue callbacks when sockets are ready for I/O. +class BASE_EXPORT MessagePumpIOStarboard : public MessagePump { + public: + class IOObserver { + public: + IOObserver() {} + + // An IOObserver is an object that receives IO notifications from the + // MessagePump. + // + // NOTE: An IOObserver should not do much work, it should return extremely + // quickly! + virtual void WillProcessIOEvent() = 0; + virtual void DidProcessIOEvent() = 0; + + protected: + virtual ~IOObserver() {} + }; + + // Used with WatchFileDescriptor to asynchronously monitor the I/O readiness + // of a file descriptor. + class Watcher { + public: + // These methods are called from MessageLoop::Run when a socket can be + // interacted with without blocking. + virtual void OnSocketReadyToRead(SbSocket /*socket*/) {} + virtual void OnSocketReadyToWrite(SbSocket /*socket*/) {} + + protected: + virtual ~Watcher() {} + }; + + // Object returned by WatchSocket to manage further watching. + class SocketWatcher { + public: + SocketWatcher(); + ~SocketWatcher(); // Implicitly calls StopWatchingSocket. + + // NOTE: These methods aren't called StartWatching()/StopWatching() to avoid + // confusion with the win32 ObjectWatcher class. + + // Stops watching the socket, always safe to call. No-op if there's nothing + // to do. + bool StopWatchingSocket(); + + bool persistent() const { return persistent_; } + + private: + friend class MessagePumpIOStarboard; + friend class MessagePumpIOStarboardTest; + + // Called by MessagePumpIOStarboard. + void Init(SbSocket socket, bool persistent); + SbSocket Release(); + + int interests() const { return interests_; } + + void set_pump(MessagePumpIOStarboard* pump) { pump_ = pump; } + MessagePumpIOStarboard* pump() const { return pump_; } + + void set_watcher(Watcher* watcher) { watcher_ = watcher; } + + void OnSocketReadyToRead(SbSocket socket, MessagePumpIOStarboard* pump); + void OnSocketReadyToWrite(SbSocket socket, MessagePumpIOStarboard* pump); + + int interests_; + SbSocket socket_; + bool persistent_; + MessagePumpIOStarboard* pump_; + Watcher* watcher_; + base::WeakPtrFactory<SocketWatcher> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(SocketWatcher); + }; + + enum Mode { + WATCH_READ = 1 << 0, + WATCH_WRITE = 1 << 1, + WATCH_READ_WRITE = WATCH_READ | WATCH_WRITE + }; + + MessagePumpIOStarboard(); + + // Have the current thread's message loop watch for a a situation in which + // reading/writing to the socket can be performed without blocking. Callers + // must provide a preallocated SocketWatcher object which can later be used to + // manage the lifetime of this event. If a SocketWatcher is passed in which + // is already attached to a socket, then the effect is cumulative i.e. after + // the call |controller| will watch both the previous event and the new one. + // If an error occurs while calling this method in a cumulative fashion, the + // event previously attached to |controller| is aborted. Returns true on + // success. Must be called on the same thread the message_pump is running on. + bool Watch(SbSocket socket, + bool persistent, + int mode, + SocketWatcher* controller, + Watcher* delegate); + + // Stops watching the socket. + bool StopWatching(SbSocket socket); + + void AddIOObserver(IOObserver* obs); + void RemoveIOObserver(IOObserver* obs); + + // MessagePump methods: + virtual void Run(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + virtual void ScheduleWork() OVERRIDE; + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time) OVERRIDE; + + protected: + virtual ~MessagePumpIOStarboard(); + + private: + friend class MessagePumpIOStarboardTest; + + void WillProcessIOEvent(); + void DidProcessIOEvent(); + + // Called by SbSocketWaiter to tell us a registered socket can be read and/or + // written to. + static void OnSocketWaiterNotification(SbSocketWaiter waiter, + SbSocket socket, + void* context, + int ready_interests); + + // This flag is set to false when Run should return. + bool keep_running_; + + // This flag is set when inside Run. + bool in_run_; + + // This flag is set if the Socket Waiter has processed I/O events. + bool processed_io_events_; + + // The time at which we should call DoDelayedWork. + TimeTicks delayed_work_time_; + + // Starboard socket waiter dispatcher. Waits for all sockets registered with + // it, and sends readiness callbacks when a socket is ready for I/O. + SbSocketWaiter waiter_; + + ObserverList<IOObserver> io_observers_; + ThreadChecker watch_socket_caller_checker_; + DISALLOW_COPY_AND_ASSIGN(MessagePumpIOStarboard); +}; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_IO_STARBOARD_H_
diff --git a/src/base/message_pump_libevent.cc b/src/base/message_pump_libevent.cc new file mode 100644 index 0000000..14a4715 --- /dev/null +++ b/src/base/message_pump_libevent.cc
@@ -0,0 +1,383 @@ +// 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/message_pump_libevent.h" + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include "base/auto_reset.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#if defined(OS_MACOSX) +#include "base/mac/scoped_nsautorelease_pool.h" +#endif +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/posix/eintr_wrapper.h" +#include "base/time.h" +#if defined(USE_SYSTEM_LIBEVENT) +#include <event.h> +#else +#include "third_party/libevent/event.h" +#endif + +#if defined(OS_MACOSX) +#include "base/mac/scoped_nsautorelease_pool.h" +#endif + +// Lifecycle of struct event +// Libevent uses two main data structures: +// struct event_base (of which there is one per message pump), and +// struct event (of which there is roughly one per socket). +// The socket's struct event is created in +// MessagePumpLibevent::WatchFileDescriptor(), +// is owned by the FileDescriptorWatcher, and is destroyed in +// StopWatchingFileDescriptor(). +// It is moved into and out of lists in struct event_base by +// the libevent functions event_add() and event_del(). +// +// TODO(dkegel): +// At the moment bad things happen if a FileDescriptorWatcher +// is active after its MessagePumpLibevent has been destroyed. +// See MessageLoopTest.FileDescriptorWatcherOutlivesMessageLoop +// Not clear yet whether that situation occurs in practice, +// but if it does, we need to fix it. + +namespace base { + +// Return 0 on success +// Too small a function to bother putting in a library? +static int SetNonBlocking(int fd) { + int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) + flags = 0; + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); +} + +MessagePumpLibevent::FileDescriptorWatcher::FileDescriptorWatcher() + : event_(NULL), + pump_(NULL), + watcher_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { +} + +MessagePumpLibevent::FileDescriptorWatcher::~FileDescriptorWatcher() { + if (event_) { + StopWatchingFileDescriptor(); + } +} + +bool MessagePumpLibevent::FileDescriptorWatcher::StopWatchingFileDescriptor() { + event* e = ReleaseEvent(); + if (e == NULL) + return true; + + // event_del() is a no-op if the event isn't active. + int rv = event_del(e); + delete e; + pump_ = NULL; + watcher_ = NULL; + return (rv == 0); +} + +void MessagePumpLibevent::FileDescriptorWatcher::Init(event *e) { + DCHECK(e); + DCHECK(!event_); + + event_ = e; +} + +event *MessagePumpLibevent::FileDescriptorWatcher::ReleaseEvent() { + struct event *e = event_; + event_ = NULL; + return e; +} + +void MessagePumpLibevent::FileDescriptorWatcher::OnFileCanReadWithoutBlocking( + int fd, MessagePumpLibevent* pump) { + // Since OnFileCanWriteWithoutBlocking() gets called first, it can stop + // watching the file descriptor. + if (!watcher_) + return; + pump->WillProcessIOEvent(); + watcher_->OnFileCanReadWithoutBlocking(fd); + pump->DidProcessIOEvent(); +} + +void MessagePumpLibevent::FileDescriptorWatcher::OnFileCanWriteWithoutBlocking( + int fd, MessagePumpLibevent* pump) { + DCHECK(watcher_); + pump->WillProcessIOEvent(); + watcher_->OnFileCanWriteWithoutBlocking(fd); + pump->DidProcessIOEvent(); +} + +MessagePumpLibevent::MessagePumpLibevent() + : keep_running_(true), + in_run_(false), + processed_io_events_(false), + event_base_(event_base_new()), + wakeup_pipe_in_(-1), + wakeup_pipe_out_(-1) { + if (!Init()) + NOTREACHED(); +} + +MessagePumpLibevent::~MessagePumpLibevent() { + DCHECK(wakeup_event_); + DCHECK(event_base_); + event_del(wakeup_event_); + delete wakeup_event_; + if (wakeup_pipe_in_ >= 0) { + if (HANDLE_EINTR(close(wakeup_pipe_in_)) < 0) + DPLOG(ERROR) << "close"; + } + if (wakeup_pipe_out_ >= 0) { + if (HANDLE_EINTR(close(wakeup_pipe_out_)) < 0) + DPLOG(ERROR) << "close"; + } + event_base_free(event_base_); +} + +bool MessagePumpLibevent::WatchFileDescriptor(int fd, + bool persistent, + int mode, + FileDescriptorWatcher *controller, + Watcher *delegate) { + DCHECK_GE(fd, 0); + DCHECK(controller); + DCHECK(delegate); + DCHECK(mode == WATCH_READ || mode == WATCH_WRITE || mode == WATCH_READ_WRITE); + // WatchFileDescriptor should be called on the pump thread. It is not + // threadsafe, and your watcher may never be registered. + DCHECK(watch_file_descriptor_caller_checker_.CalledOnValidThread()); + + int event_mask = persistent ? EV_PERSIST : 0; + if (mode & WATCH_READ) { + event_mask |= EV_READ; + } + if (mode & WATCH_WRITE) { + event_mask |= EV_WRITE; + } + + scoped_ptr<event> evt(controller->ReleaseEvent()); + if (evt.get() == NULL) { + // Ownership is transferred to the controller. + evt.reset(new event); + } else { + // Make sure we don't pick up any funky internal libevent masks. + int old_interest_mask = evt.get()->ev_events & + (EV_READ | EV_WRITE | EV_PERSIST); + + // Combine old/new event masks. + event_mask |= old_interest_mask; + + // Must disarm the event before we can reuse it. + event_del(evt.get()); + + // It's illegal to use this function to listen on 2 separate fds with the + // same |controller|. + if (EVENT_FD(evt.get()) != fd) { + NOTREACHED() << "FDs don't match" << EVENT_FD(evt.get()) << "!=" << fd; + return false; + } + } + + // Set current interest mask and message pump for this event. + event_set(evt.get(), fd, event_mask, OnLibeventNotification, controller); + + // Tell libevent which message pump this socket will belong to when we add it. + if (event_base_set(event_base_, evt.get())) { + return false; + } + + // Add this socket to the list of monitored sockets. + if (event_add(evt.get(), NULL)) { + return false; + } + + // Transfer ownership of evt to controller. + controller->Init(evt.release()); + + controller->set_watcher(delegate); + controller->set_pump(this); + + return true; +} + +void MessagePumpLibevent::AddIOObserver(IOObserver *obs) { + io_observers_.AddObserver(obs); +} + +void MessagePumpLibevent::RemoveIOObserver(IOObserver *obs) { + io_observers_.RemoveObserver(obs); +} + +// Tell libevent to break out of inner loop. +static void timer_callback(int fd, short events, void *context) +{ + event_base_loopbreak((struct event_base *)context); +} + +// Reentrant! +void MessagePumpLibevent::Run(Delegate* delegate) { + DCHECK(keep_running_) << "Quit must have been called outside of Run!"; + base::AutoReset<bool> auto_reset_in_run(&in_run_, true); + + // event_base_loopexit() + EVLOOP_ONCE is leaky, see http://crbug.com/25641. + // Instead, make our own timer and reuse it on each call to event_base_loop(). + scoped_ptr<event> timer_event(new event); + + for (;;) { +#if defined(OS_MACOSX) + mac::ScopedNSAutoreleasePool autorelease_pool; +#endif + + bool did_work = delegate->DoWork(); + if (!keep_running_) + break; + + event_base_loop(event_base_, EVLOOP_NONBLOCK); + did_work |= processed_io_events_; + processed_io_events_ = false; + if (!keep_running_) + break; + + did_work |= delegate->DoDelayedWork(&delayed_work_time_); + if (!keep_running_) + break; + + if (did_work) + continue; + + did_work = delegate->DoIdleWork(); + if (!keep_running_) + break; + + if (did_work) + continue; + + // EVLOOP_ONCE tells libevent to only block once, + // but to service all pending events when it wakes up. + if (delayed_work_time_.is_null()) { + event_base_loop(event_base_, EVLOOP_ONCE); + } else { + TimeDelta delay = delayed_work_time_ - TimeTicks::Now(); + if (delay > TimeDelta()) { + struct timeval poll_tv; + poll_tv.tv_sec = delay.InSeconds(); + poll_tv.tv_usec = delay.InMicroseconds() % Time::kMicrosecondsPerSecond; + event_set(timer_event.get(), -1, 0, timer_callback, event_base_); + event_base_set(event_base_, timer_event.get()); + event_add(timer_event.get(), &poll_tv); + event_base_loop(event_base_, EVLOOP_ONCE); + event_del(timer_event.get()); + } else { + // It looks like delayed_work_time_ indicates a time in the past, so we + // need to call DoDelayedWork now. + delayed_work_time_ = TimeTicks(); + } + } + } + + keep_running_ = true; +} + +void MessagePumpLibevent::Quit() { + DCHECK(in_run_); + // Tell both libevent and Run that they should break out of their loops. + keep_running_ = false; + ScheduleWork(); +} + +void MessagePumpLibevent::ScheduleWork() { + // Tell libevent (in a threadsafe way) that it should break out of its loop. + char buf = 0; + int nwrite = HANDLE_EINTR(write(wakeup_pipe_in_, &buf, 1)); + DCHECK(nwrite == 1 || errno == EAGAIN) + << "[nwrite:" << nwrite << "] [errno:" << errno << "]"; +} + +void MessagePumpLibevent::ScheduleDelayedWork( + const TimeTicks& delayed_work_time) { + // We know that we can't be blocked on Wait right now since this method can + // only be called on the same thread as Run, so we only need to update our + // record of how long to sleep when we do sleep. + delayed_work_time_ = delayed_work_time; +} + +void MessagePumpLibevent::WillProcessIOEvent() { + FOR_EACH_OBSERVER(IOObserver, io_observers_, WillProcessIOEvent()); +} + +void MessagePumpLibevent::DidProcessIOEvent() { + FOR_EACH_OBSERVER(IOObserver, io_observers_, DidProcessIOEvent()); +} + +bool MessagePumpLibevent::Init() { + int fds[2]; + if (pipe(fds)) { + DLOG(ERROR) << "pipe() failed, errno: " << errno; + return false; + } + if (SetNonBlocking(fds[0])) { + DLOG(ERROR) << "SetNonBlocking for pipe fd[0] failed, errno: " << errno; + return false; + } + if (SetNonBlocking(fds[1])) { + DLOG(ERROR) << "SetNonBlocking for pipe fd[1] failed, errno: " << errno; + return false; + } + wakeup_pipe_out_ = fds[0]; + wakeup_pipe_in_ = fds[1]; + + wakeup_event_ = new event; + event_set(wakeup_event_, wakeup_pipe_out_, EV_READ | EV_PERSIST, + OnWakeup, this); + event_base_set(event_base_, wakeup_event_); + + if (event_add(wakeup_event_, 0)) + return false; + return true; +} + +// static +void MessagePumpLibevent::OnLibeventNotification(int fd, short flags, + void* context) { + base::WeakPtr<FileDescriptorWatcher> controller = + static_cast<FileDescriptorWatcher*>(context)->weak_factory_.GetWeakPtr(); + DCHECK(controller.get()); + + MessagePumpLibevent* pump = controller->pump(); + pump->processed_io_events_ = true; + + if (flags & EV_WRITE) { + controller->OnFileCanWriteWithoutBlocking(fd, pump); + } + // Check |controller| in case it's been deleted in + // controller->OnFileCanWriteWithoutBlocking(). + if (controller.get() && flags & EV_READ) { + controller->OnFileCanReadWithoutBlocking(fd, pump); + } +} + +// Called if a byte is received on the wakeup pipe. +// static +void MessagePumpLibevent::OnWakeup(int socket, short flags, void* context) { + base::MessagePumpLibevent* that = + static_cast<base::MessagePumpLibevent*>(context); + DCHECK(that->wakeup_pipe_out_ == socket); + + // Remove and discard the wakeup byte. + char buf; + int nread = HANDLE_EINTR(read(socket, &buf, 1)); + DCHECK_EQ(nread, 1); + that->processed_io_events_ = true; + // Tell libevent to break out of inner loop. + event_base_loopbreak(that->event_base_); +} + +} // namespace base
diff --git a/src/base/message_pump_libevent.h b/src/base/message_pump_libevent.h new file mode 100644 index 0000000..90e8510 --- /dev/null +++ b/src/base/message_pump_libevent.h
@@ -0,0 +1,179 @@ +// 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_MESSAGE_PUMP_LIBEVENT_H_ +#define BASE_MESSAGE_PUMP_LIBEVENT_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "base/message_pump.h" +#include "base/observer_list.h" +#include "base/threading/thread_checker.h" +#include "base/time.h" + +// Declare structs we need from libevent.h rather than including it +struct event_base; +struct event; + +namespace base { + +// Class to monitor sockets and issue callbacks when sockets are ready for I/O +// TODO(dkegel): add support for background file IO somehow +class BASE_EXPORT MessagePumpLibevent : public MessagePump { + public: + class IOObserver { + public: + IOObserver() {} + + // An IOObserver is an object that receives IO notifications from the + // MessagePump. + // + // NOTE: An IOObserver implementation should be extremely fast! + virtual void WillProcessIOEvent() = 0; + virtual void DidProcessIOEvent() = 0; + + protected: + virtual ~IOObserver() {} + }; + + // Used with WatchFileDescriptor to asynchronously monitor the I/O readiness + // of a file descriptor. + class Watcher { + public: + // Called from MessageLoop::Run when an FD can be read from/written to + // without blocking + virtual void OnFileCanReadWithoutBlocking(int fd) = 0; + virtual void OnFileCanWriteWithoutBlocking(int fd) = 0; + + protected: + virtual ~Watcher() {} + }; + + // Object returned by WatchFileDescriptor to manage further watching. + class FileDescriptorWatcher { + public: + FileDescriptorWatcher(); + ~FileDescriptorWatcher(); // Implicitly calls StopWatchingFileDescriptor. + + // NOTE: These methods aren't called StartWatching()/StopWatching() to + // avoid confusion with the win32 ObjectWatcher class. + + // Stop watching the FD, always safe to call. No-op if there's nothing + // to do. + bool StopWatchingFileDescriptor(); + + private: + friend class MessagePumpLibevent; + friend class MessagePumpLibeventTest; + + // Called by MessagePumpLibevent, ownership of |e| is transferred to this + // object. + void Init(event* e); + + // Used by MessagePumpLibevent to take ownership of event_. + event* ReleaseEvent(); + + void set_pump(MessagePumpLibevent* pump) { pump_ = pump; } + MessagePumpLibevent* pump() const { return pump_; } + + void set_watcher(Watcher* watcher) { watcher_ = watcher; } + + void OnFileCanReadWithoutBlocking(int fd, MessagePumpLibevent* pump); + void OnFileCanWriteWithoutBlocking(int fd, MessagePumpLibevent* pump); + + event* event_; + MessagePumpLibevent* pump_; + Watcher* watcher_; + base::WeakPtrFactory<FileDescriptorWatcher> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileDescriptorWatcher); + }; + + enum Mode { + WATCH_READ = 1 << 0, + WATCH_WRITE = 1 << 1, + WATCH_READ_WRITE = WATCH_READ | WATCH_WRITE + }; + + MessagePumpLibevent(); + + // Have the current thread's message loop watch for a a situation in which + // reading/writing to the FD can be performed without blocking. + // Callers must provide a preallocated FileDescriptorWatcher object which + // can later be used to manage the lifetime of this event. + // If a FileDescriptorWatcher is passed in which is already attached to + // an event, then the effect is cumulative i.e. after the call |controller| + // will watch both the previous event and the new one. + // If an error occurs while calling this method in a cumulative fashion, the + // event previously attached to |controller| is aborted. + // Returns true on success. + // Must be called on the same thread the message_pump is running on. + // TODO(dkegel): switch to edge-triggered readiness notification + bool WatchFileDescriptor(int fd, + bool persistent, + int mode, + FileDescriptorWatcher *controller, + Watcher *delegate); + + void AddIOObserver(IOObserver* obs); + void RemoveIOObserver(IOObserver* obs); + + // MessagePump methods: + virtual void Run(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + virtual void ScheduleWork() OVERRIDE; + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time) OVERRIDE; + + protected: + virtual ~MessagePumpLibevent(); + + private: + friend class MessagePumpLibeventTest; + + void WillProcessIOEvent(); + void DidProcessIOEvent(); + + // Risky part of constructor. Returns true on success. + bool Init(); + + // Called by libevent to tell us a registered FD can be read/written to. + static void OnLibeventNotification(int fd, short flags, + void* context); + + // Unix pipe used to implement ScheduleWork() + // ... callback; called by libevent inside Run() when pipe is ready to read + static void OnWakeup(int socket, short flags, void* context); + + // This flag is set to false when Run should return. + bool keep_running_; + + // This flag is set when inside Run. + bool in_run_; + + // This flag is set if libevent has processed I/O events. + bool processed_io_events_; + + // The time at which we should call DoDelayedWork. + TimeTicks delayed_work_time_; + + // Libevent dispatcher. Watches all sockets registered with it, and sends + // readiness callbacks when a socket is ready for I/O. + event_base* event_base_; + + // ... write end; ScheduleWork() writes a single byte to it + int wakeup_pipe_in_; + // ... read end; OnWakeup reads it and then breaks Run() out of its sleep + int wakeup_pipe_out_; + // ... libevent wrapper for read end + event* wakeup_event_; + + ObserverList<IOObserver> io_observers_; + ThreadChecker watch_file_descriptor_caller_checker_; + DISALLOW_COPY_AND_ASSIGN(MessagePumpLibevent); +}; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_LIBEVENT_H_
diff --git a/src/base/message_pump_libevent_unittest.cc b/src/base/message_pump_libevent_unittest.cc new file mode 100644 index 0000000..e48a9c5 --- /dev/null +++ b/src/base/message_pump_libevent_unittest.cc
@@ -0,0 +1,167 @@ +// 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/message_pump_libevent.h" + +#include <unistd.h> + +#include "base/message_loop.h" +#include "base/posix/eintr_wrapper.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(USE_SYSTEM_LIBEVENT) +#include <event.h> +#else +#include "third_party/libevent/event.h" +#endif + +namespace base { + +class MessagePumpLibeventTest : public testing::Test { + protected: + MessagePumpLibeventTest() + : ui_loop_(MessageLoop::TYPE_UI), + io_thread_("MessagePumpLibeventTestIOThread") {} + virtual ~MessagePumpLibeventTest() {} + + virtual void SetUp() OVERRIDE { + Thread::Options options(MessageLoop::TYPE_IO, 0); + ASSERT_TRUE(io_thread_.StartWithOptions(options)); + ASSERT_EQ(MessageLoop::TYPE_IO, io_thread_.message_loop()->type()); + int ret = pipe(pipefds_); + ASSERT_EQ(0, ret); + } + + virtual void TearDown() OVERRIDE { + if (HANDLE_EINTR(close(pipefds_[0])) < 0) + PLOG(ERROR) << "close"; + if (HANDLE_EINTR(close(pipefds_[1])) < 0) + PLOG(ERROR) << "close"; + } + + MessageLoop* ui_loop() { return &ui_loop_; } + MessageLoopForIO* io_loop() const { + return static_cast<MessageLoopForIO*>(io_thread_.message_loop()); + } + + void OnLibeventNotification( + MessagePumpLibevent* pump, + MessagePumpLibevent::FileDescriptorWatcher* controller) { + pump->OnLibeventNotification(0, EV_WRITE | EV_READ, controller); + } + + int pipefds_[2]; + + private: + MessageLoop ui_loop_; + Thread io_thread_; +}; + +namespace { + +// Concrete implementation of MessagePumpLibevent::Watcher that does +// nothing useful. +class StupidWatcher : public MessagePumpLibevent::Watcher { + public: + virtual ~StupidWatcher() {} + + // base:MessagePumpLibevent::Watcher interface + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE {} + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {} +}; + +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +// Test to make sure that we catch calling WatchFileDescriptor off of the +// wrong thread. +TEST_F(MessagePumpLibeventTest, TestWatchingFromBadThread) { + MessagePumpLibevent::FileDescriptorWatcher watcher; + StupidWatcher delegate; + + ASSERT_DEATH(io_loop()->WatchFileDescriptor( + STDOUT_FILENO, false, MessageLoopForIO::WATCH_READ, &watcher, &delegate), + "Check failed: " + "watch_file_descriptor_caller_checker_.CalledOnValidThread()"); +} + +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +class BaseWatcher : public MessagePumpLibevent::Watcher { + public: + BaseWatcher(MessagePumpLibevent::FileDescriptorWatcher* controller) + : controller_(controller) { + DCHECK(controller_); + } + virtual ~BaseWatcher() {} + + // base:MessagePumpLibevent::Watcher interface + virtual void OnFileCanReadWithoutBlocking(int /* fd */) OVERRIDE { + NOTREACHED(); + } + + virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE { + NOTREACHED(); + } + + protected: + MessagePumpLibevent::FileDescriptorWatcher* controller_; +}; + +class DeleteWatcher : public BaseWatcher { + public: + explicit DeleteWatcher( + MessagePumpLibevent::FileDescriptorWatcher* controller) + : BaseWatcher(controller) {} + + virtual ~DeleteWatcher() { + DCHECK(!controller_); + } + + virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE { + DCHECK(controller_); + delete controller_; + controller_ = NULL; + } +}; + +TEST_F(MessagePumpLibeventTest, DeleteWatcher) { + scoped_refptr<MessagePumpLibevent> pump(new MessagePumpLibevent); + MessagePumpLibevent::FileDescriptorWatcher* watcher = + new MessagePumpLibevent::FileDescriptorWatcher; + DeleteWatcher delegate(watcher); + pump->WatchFileDescriptor(pipefds_[1], + false, MessagePumpLibevent::WATCH_READ_WRITE, watcher, &delegate); + + // Spoof a libevent notification. + OnLibeventNotification(pump, watcher); +} + +class StopWatcher : public BaseWatcher { + public: + explicit StopWatcher( + MessagePumpLibevent::FileDescriptorWatcher* controller) + : BaseWatcher(controller) {} + + virtual ~StopWatcher() {} + + virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE { + controller_->StopWatchingFileDescriptor(); + } +}; + +TEST_F(MessagePumpLibeventTest, StopWatcher) { + scoped_refptr<MessagePumpLibevent> pump(new MessagePumpLibevent); + MessagePumpLibevent::FileDescriptorWatcher watcher; + StopWatcher delegate(&watcher); + pump->WatchFileDescriptor(pipefds_[1], + false, MessagePumpLibevent::WATCH_READ_WRITE, &watcher, &delegate); + + // Spoof a libevent notification. + OnLibeventNotification(pump, &watcher); +} + +} // namespace + +} // namespace base
diff --git a/src/base/message_pump_mac.h b/src/base/message_pump_mac.h new file mode 100644 index 0000000..2dd5ecf --- /dev/null +++ b/src/base/message_pump_mac.h
@@ -0,0 +1,337 @@ +// 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. + +// The basis for all native run loops on the Mac is the CFRunLoop. It can be +// used directly, it can be used as the driving force behind the similar +// Foundation NSRunLoop, and it can be used to implement higher-level event +// loops such as the NSApplication event loop. +// +// This file introduces a basic CFRunLoop-based implementation of the +// MessagePump interface called CFRunLoopBase. CFRunLoopBase contains all +// of the machinery necessary to dispatch events to a delegate, but does not +// implement the specific run loop. Concrete subclasses must provide their +// own DoRun and Quit implementations. +// +// A concrete subclass that just runs a CFRunLoop loop is provided in +// MessagePumpCFRunLoop. For an NSRunLoop, the similar MessagePumpNSRunLoop +// is provided. +// +// For the application's event loop, an implementation based on AppKit's +// NSApplication event system is provided in MessagePumpNSApplication. +// +// Typically, MessagePumpNSApplication only makes sense on a Cocoa +// application's main thread. If a CFRunLoop-based message pump is needed on +// any other thread, one of the other concrete subclasses is preferrable. +// MessagePumpMac::Create is defined, which returns a new NSApplication-based +// or NSRunLoop-based MessagePump subclass depending on which thread it is +// called on. + +#ifndef BASE_MESSAGE_PUMP_MAC_H_ +#define BASE_MESSAGE_PUMP_MAC_H_ + +#include "base/message_pump.h" + +#include <CoreFoundation/CoreFoundation.h> + +#if !defined(__OBJC__) +class NSAutoreleasePool; +#else // !defined(__OBJC__) +#if defined(OS_IOS) +#import <Foundation/Foundation.h> +#else +#import <AppKit/AppKit.h> + +// Clients must subclass NSApplication and implement this protocol if they use +// MessagePumpMac. +@protocol CrAppProtocol +// Must return true if -[NSApplication sendEvent:] is currently on the stack. +// See the comment for |CreateAutoreleasePool()| in the cc file for why this is +// necessary. +- (BOOL)isHandlingSendEvent; +@end +#endif // !defined(OS_IOS) +#endif // !defined(__OBJC__) + +namespace base { + +class RunLoop; +class TimeTicks; + +class MessagePumpCFRunLoopBase : public MessagePump { + // Needs access to CreateAutoreleasePool. + friend class MessagePumpScopedAutoreleasePool; + public: + MessagePumpCFRunLoopBase(); + + // Subclasses should implement the work they need to do in MessagePump::Run + // in the DoRun method. MessagePumpCFRunLoopBase::Run calls DoRun directly. + // This arrangement is used because MessagePumpCFRunLoopBase needs to set + // up and tear down things before and after the "meat" of DoRun. + virtual void Run(Delegate* delegate) OVERRIDE; + virtual void DoRun(Delegate* delegate) = 0; + + virtual void ScheduleWork() OVERRIDE; + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time) OVERRIDE; + + protected: + virtual ~MessagePumpCFRunLoopBase(); + + // Accessors for private data members to be used by subclasses. + CFRunLoopRef run_loop() const { return run_loop_; } + int nesting_level() const { return nesting_level_; } + int run_nesting_level() const { return run_nesting_level_; } + + // Sets this pump's delegate. Signals the appropriate sources if + // |delegateless_work_| is true. |delegate| can be NULL. + void SetDelegate(Delegate* delegate); + + // Return an autorelease pool to wrap around any work being performed. + // In some cases, CreateAutoreleasePool may return nil intentionally to + // preventing an autorelease pool from being created, allowing any + // objects autoreleased by work to fall into the current autorelease pool. + virtual NSAutoreleasePool* CreateAutoreleasePool(); + + private: + // Timer callback scheduled by ScheduleDelayedWork. This does not do any + // work, but it signals work_source_ so that delayed work can be performed + // within the appropriate priority constraints. + static void RunDelayedWorkTimer(CFRunLoopTimerRef timer, void* info); + + // Perform highest-priority work. This is associated with work_source_ + // signalled by ScheduleWork or RunDelayedWorkTimer. The static method calls + // the instance method; the instance method returns true if it resignalled + // work_source_ to be called again from the loop. + static void RunWorkSource(void* info); + bool RunWork(); + + // Perform idle-priority work. This is normally called by PreWaitObserver, + // but is also associated with idle_work_source_. When this function + // actually does perform idle work, it will resignal that source. The + // static method calls the instance method; the instance method returns + // true if idle work was done. + static void RunIdleWorkSource(void* info); + bool RunIdleWork(); + + // Perform work that may have been deferred because it was not runnable + // within a nested run loop. This is associated with + // nesting_deferred_work_source_ and is signalled by + // MaybeScheduleNestingDeferredWork when returning from a nested loop, + // so that an outer loop will be able to perform the necessary tasks if it + // permits nestable tasks. + static void RunNestingDeferredWorkSource(void* info); + bool RunNestingDeferredWork(); + + // Schedules possible nesting-deferred work to be processed before the run + // loop goes to sleep, exits, or begins processing sources at the top of its + // loop. If this function detects that a nested loop had run since the + // previous attempt to schedule nesting-deferred work, it will schedule a + // call to RunNestingDeferredWorkSource. + void MaybeScheduleNestingDeferredWork(); + + // Observer callback responsible for performing idle-priority work, before + // the run loop goes to sleep. Associated with idle_work_observer_. + static void PreWaitObserver(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, void* info); + + // Observer callback called before the run loop processes any sources. + // Associated with pre_source_observer_. + static void PreSourceObserver(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, void* info); + + // Observer callback called when the run loop starts and stops, at the + // beginning and end of calls to CFRunLoopRun. This is used to maintain + // nesting_level_. Associated with enter_exit_observer_. + static void EnterExitObserver(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, void* info); + + // Called by EnterExitObserver after performing maintenance on nesting_level_. + // This allows subclasses an opportunity to perform additional processing on + // the basis of run loops starting and stopping. + virtual void EnterExitRunLoop(CFRunLoopActivity activity); + + // The thread's run loop. + CFRunLoopRef run_loop_; + + // The timer, sources, and observers are described above alongside their + // callbacks. + CFRunLoopTimerRef delayed_work_timer_; + CFRunLoopSourceRef work_source_; + CFRunLoopSourceRef idle_work_source_; + CFRunLoopSourceRef nesting_deferred_work_source_; + CFRunLoopObserverRef pre_wait_observer_; + CFRunLoopObserverRef pre_source_observer_; + CFRunLoopObserverRef enter_exit_observer_; + + // (weak) Delegate passed as an argument to the innermost Run call. + Delegate* delegate_; + + // The time that delayed_work_timer_ is scheduled to fire. This is tracked + // independently of CFRunLoopTimerGetNextFireDate(delayed_work_timer_) + // to be able to reset the timer properly after waking from system sleep. + // See PowerStateNotification. + CFAbsoluteTime delayed_work_fire_time_; + + // The recursion depth of the currently-executing CFRunLoopRun loop on the + // run loop's thread. 0 if no run loops are running inside of whatever scope + // the object was created in. + int nesting_level_; + + // The recursion depth (calculated in the same way as nesting_level_) of the + // innermost executing CFRunLoopRun loop started by a call to Run. + int run_nesting_level_; + + // The deepest (numerically highest) recursion depth encountered since the + // most recent attempt to run nesting-deferred work. + int deepest_nesting_level_; + + // "Delegateless" work flags are set when work is ready to be performed but + // must wait until a delegate is available to process it. This can happen + // when a MessagePumpCFRunLoopBase is instantiated and work arrives without + // any call to Run on the stack. The Run method will check for delegateless + // work on entry and redispatch it as needed once a delegate is available. + bool delegateless_work_; + bool delegateless_idle_work_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpCFRunLoopBase); +}; + +class MessagePumpCFRunLoop : public MessagePumpCFRunLoopBase { + public: + MessagePumpCFRunLoop(); + + virtual void DoRun(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + + protected: + virtual ~MessagePumpCFRunLoop(); + + private: + virtual void EnterExitRunLoop(CFRunLoopActivity activity) OVERRIDE; + + // True if Quit is called to stop the innermost MessagePump + // (innermost_quittable_) but some other CFRunLoopRun loop (nesting_level_) + // is running inside the MessagePump's innermost Run call. + bool quit_pending_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpCFRunLoop); +}; + +class MessagePumpNSRunLoop : public MessagePumpCFRunLoopBase { + public: + BASE_EXPORT MessagePumpNSRunLoop(); + + virtual void DoRun(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + + protected: + virtual ~MessagePumpNSRunLoop(); + + private: + // A source that doesn't do anything but provide something signalable + // attached to the run loop. This source will be signalled when Quit + // is called, to cause the loop to wake up so that it can stop. + CFRunLoopSourceRef quit_source_; + + // False after Quit is called. + bool keep_running_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpNSRunLoop); +}; + +#if defined(OS_IOS) +// This is a fake message pump. It attaches sources to the main thread's +// CFRunLoop, so PostTask() will work, but it is unable to drive the loop +// directly, so calling Run() or Quit() are errors. +class MessagePumpUIApplication : public MessagePumpCFRunLoopBase { + public: + MessagePumpUIApplication(); + virtual void DoRun(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + + // This message pump can not spin the main message loop directly. Instead, + // call |Attach()| to set up a delegate. It is an error to call |Run()|. + virtual void Attach(Delegate* delegate); + + protected: + virtual ~MessagePumpUIApplication(); + + private: + base::RunLoop* run_loop_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpUIApplication); +}; + +#else + +class MessagePumpNSApplication : public MessagePumpCFRunLoopBase { + public: + MessagePumpNSApplication(); + + virtual void DoRun(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + + protected: + virtual ~MessagePumpNSApplication(); + + private: + // False after Quit is called. + bool keep_running_; + + // True if DoRun is managing its own run loop as opposed to letting + // -[NSApplication run] handle it. The outermost run loop in the application + // is managed by -[NSApplication run], inner run loops are handled by a loop + // in DoRun. + bool running_own_loop_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpNSApplication); +}; + +class MessagePumpCrApplication : public MessagePumpNSApplication { + public: + MessagePumpCrApplication(); + + protected: + virtual ~MessagePumpCrApplication() {} + + // Returns nil if NSApp is currently in the middle of calling + // -sendEvent. Requires NSApp implementing CrAppProtocol. + virtual NSAutoreleasePool* CreateAutoreleasePool() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(MessagePumpCrApplication); +}; +#endif // !defined(OS_IOS) + +class MessagePumpMac { + public: + // If not on the main thread, returns a new instance of + // MessagePumpNSRunLoop. + // + // On the main thread, if NSApp exists and conforms to + // CrAppProtocol, creates an instances of MessagePumpCrApplication. + // + // Otherwise creates an instance of MessagePumpNSApplication using a + // default NSApplication. + static MessagePump* Create(); + +#if !defined(OS_IOS) + // If a pump is created before the required CrAppProtocol is + // created, the wrong MessagePump subclass could be used. + // UsingCrApp() returns false if the message pump was created before + // NSApp was initialized, or if NSApp does not implement + // CrAppProtocol. NSApp must be initialized before calling. + BASE_EXPORT static bool UsingCrApp(); + + // Wrapper to query -[NSApp isHandlingSendEvent] from C++ code. + // Requires NSApp to implement CrAppProtocol. + BASE_EXPORT static bool IsHandlingSendEvent(); +#endif // !defined(OS_IOS) + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(MessagePumpMac); +}; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_MAC_H_
diff --git a/src/base/message_pump_mac.mm b/src/base/message_pump_mac.mm new file mode 100644 index 0000000..3878eb8 --- /dev/null +++ b/src/base/message_pump_mac.mm
@@ -0,0 +1,699 @@ +// 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. + +#import "base/message_pump_mac.h" + +#import <Foundation/Foundation.h> + +#include <limits> + +#include "base/logging.h" +#include "base/run_loop.h" +#include "base/time.h" + +#if !defined(OS_IOS) +#import <AppKit/AppKit.h> +#endif // !defined(OS_IOS) + +namespace { + +void NoOp(void* info) { +} + +const CFTimeInterval kCFTimeIntervalMax = + std::numeric_limits<CFTimeInterval>::max(); + +// Set to true if MessagePumpMac::Create() is called before NSApp is +// initialized. Only accessed from the main thread. +bool not_using_crapp = false; + +} // namespace + +namespace base { + +// A scoper for autorelease pools created from message pump run loops. +// Avoids dirtying up the ScopedNSAutoreleasePool interface for the rare +// case where an autorelease pool needs to be passed in. +class MessagePumpScopedAutoreleasePool { + public: + explicit MessagePumpScopedAutoreleasePool(MessagePumpCFRunLoopBase* pump) : + pool_(pump->CreateAutoreleasePool()) { + } + ~MessagePumpScopedAutoreleasePool() { + [pool_ drain]; + } + + private: + NSAutoreleasePool* pool_; + DISALLOW_COPY_AND_ASSIGN(MessagePumpScopedAutoreleasePool); +}; + +// Must be called on the run loop thread. +MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase() + : delegate_(NULL), + delayed_work_fire_time_(kCFTimeIntervalMax), + nesting_level_(0), + run_nesting_level_(0), + deepest_nesting_level_(0), + delegateless_work_(false), + delegateless_idle_work_(false) { + run_loop_ = CFRunLoopGetCurrent(); + CFRetain(run_loop_); + + // Set a repeating timer with a preposterous firing time and interval. The + // timer will effectively never fire as-is. The firing time will be adjusted + // as needed when ScheduleDelayedWork is called. + CFRunLoopTimerContext timer_context = CFRunLoopTimerContext(); + timer_context.info = this; + delayed_work_timer_ = CFRunLoopTimerCreate(NULL, // allocator + kCFTimeIntervalMax, // fire time + kCFTimeIntervalMax, // interval + 0, // flags + 0, // priority + RunDelayedWorkTimer, + &timer_context); + CFRunLoopAddTimer(run_loop_, delayed_work_timer_, kCFRunLoopCommonModes); + + CFRunLoopSourceContext source_context = CFRunLoopSourceContext(); + source_context.info = this; + source_context.perform = RunWorkSource; + work_source_ = CFRunLoopSourceCreate(NULL, // allocator + 1, // priority + &source_context); + CFRunLoopAddSource(run_loop_, work_source_, kCFRunLoopCommonModes); + + source_context.perform = RunIdleWorkSource; + idle_work_source_ = CFRunLoopSourceCreate(NULL, // allocator + 2, // priority + &source_context); + CFRunLoopAddSource(run_loop_, idle_work_source_, kCFRunLoopCommonModes); + + source_context.perform = RunNestingDeferredWorkSource; + nesting_deferred_work_source_ = CFRunLoopSourceCreate(NULL, // allocator + 0, // priority + &source_context); + CFRunLoopAddSource(run_loop_, nesting_deferred_work_source_, + kCFRunLoopCommonModes); + + CFRunLoopObserverContext observer_context = CFRunLoopObserverContext(); + observer_context.info = this; + pre_wait_observer_ = CFRunLoopObserverCreate(NULL, // allocator + kCFRunLoopBeforeWaiting, + true, // repeat + 0, // priority + PreWaitObserver, + &observer_context); + CFRunLoopAddObserver(run_loop_, pre_wait_observer_, kCFRunLoopCommonModes); + + pre_source_observer_ = CFRunLoopObserverCreate(NULL, // allocator + kCFRunLoopBeforeSources, + true, // repeat + 0, // priority + PreSourceObserver, + &observer_context); + CFRunLoopAddObserver(run_loop_, pre_source_observer_, kCFRunLoopCommonModes); + + enter_exit_observer_ = CFRunLoopObserverCreate(NULL, // allocator + kCFRunLoopEntry | + kCFRunLoopExit, + true, // repeat + 0, // priority + EnterExitObserver, + &observer_context); + CFRunLoopAddObserver(run_loop_, enter_exit_observer_, kCFRunLoopCommonModes); +} + +// Ideally called on the run loop thread. If other run loops were running +// lower on the run loop thread's stack when this object was created, the +// same number of run loops must be running when this object is destroyed. +MessagePumpCFRunLoopBase::~MessagePumpCFRunLoopBase() { + CFRunLoopRemoveObserver(run_loop_, enter_exit_observer_, + kCFRunLoopCommonModes); + CFRelease(enter_exit_observer_); + + CFRunLoopRemoveObserver(run_loop_, pre_source_observer_, + kCFRunLoopCommonModes); + CFRelease(pre_source_observer_); + + CFRunLoopRemoveObserver(run_loop_, pre_wait_observer_, + kCFRunLoopCommonModes); + CFRelease(pre_wait_observer_); + + CFRunLoopRemoveSource(run_loop_, nesting_deferred_work_source_, + kCFRunLoopCommonModes); + CFRelease(nesting_deferred_work_source_); + + CFRunLoopRemoveSource(run_loop_, idle_work_source_, kCFRunLoopCommonModes); + CFRelease(idle_work_source_); + + CFRunLoopRemoveSource(run_loop_, work_source_, kCFRunLoopCommonModes); + CFRelease(work_source_); + + CFRunLoopRemoveTimer(run_loop_, delayed_work_timer_, kCFRunLoopCommonModes); + CFRelease(delayed_work_timer_); + + CFRelease(run_loop_); +} + +// Must be called on the run loop thread. +void MessagePumpCFRunLoopBase::Run(Delegate* delegate) { + // nesting_level_ will be incremented in EnterExitRunLoop, so set + // run_nesting_level_ accordingly. + int last_run_nesting_level = run_nesting_level_; + run_nesting_level_ = nesting_level_ + 1; + + Delegate* last_delegate = delegate_; + SetDelegate(delegate); + + DoRun(delegate); + + // Restore the previous state of the object. + SetDelegate(last_delegate); + run_nesting_level_ = last_run_nesting_level; +} + +void MessagePumpCFRunLoopBase::SetDelegate(Delegate* delegate) { + delegate_ = delegate; + + if (delegate) { + // If any work showed up but could not be dispatched for want of a + // delegate, set it up for dispatch again now that a delegate is + // available. + if (delegateless_work_) { + CFRunLoopSourceSignal(work_source_); + delegateless_work_ = false; + } + if (delegateless_idle_work_) { + CFRunLoopSourceSignal(idle_work_source_); + delegateless_idle_work_ = false; + } + } +} + +// May be called on any thread. +void MessagePumpCFRunLoopBase::ScheduleWork() { + CFRunLoopSourceSignal(work_source_); + CFRunLoopWakeUp(run_loop_); +} + +// Must be called on the run loop thread. +void MessagePumpCFRunLoopBase::ScheduleDelayedWork( + const TimeTicks& delayed_work_time) { + TimeDelta delta = delayed_work_time - TimeTicks::Now(); + delayed_work_fire_time_ = CFAbsoluteTimeGetCurrent() + delta.InSecondsF(); + CFRunLoopTimerSetNextFireDate(delayed_work_timer_, delayed_work_fire_time_); +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer, + void* info) { + MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); + + // The timer won't fire again until it's reset. + self->delayed_work_fire_time_ = kCFTimeIntervalMax; + + // CFRunLoopTimers fire outside of the priority scheme for CFRunLoopSources. + // In order to establish the proper priority in which work and delayed work + // are processed one for one, the timer used to schedule delayed work must + // signal a CFRunLoopSource used to dispatch both work and delayed work. + CFRunLoopSourceSignal(self->work_source_); +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::RunWorkSource(void* info) { + MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); + self->RunWork(); +} + +// Called by MessagePumpCFRunLoopBase::RunWorkSource. +bool MessagePumpCFRunLoopBase::RunWork() { + if (!delegate_) { + // This point can be reached with a NULL delegate_ if Run is not on the + // stack but foreign code is spinning the CFRunLoop. Arrange to come back + // here when a delegate is available. + delegateless_work_ = true; + return false; + } + + // The NSApplication-based run loop only drains the autorelease pool at each + // UI event (NSEvent). The autorelease pool is not drained for each + // CFRunLoopSource target that's run. Use a local pool for any autoreleased + // objects if the app is not currently handling a UI event to ensure they're + // released promptly even in the absence of UI events. + MessagePumpScopedAutoreleasePool autorelease_pool(this); + + // Call DoWork and DoDelayedWork once, and if something was done, arrange to + // come back here again as long as the loop is still running. + bool did_work = delegate_->DoWork(); + bool resignal_work_source = did_work; + + TimeTicks next_time; + delegate_->DoDelayedWork(&next_time); + if (!did_work) { + // Determine whether there's more delayed work, and if so, if it needs to + // be done at some point in the future or if it's already time to do it. + // Only do these checks if did_work is false. If did_work is true, this + // function, and therefore any additional delayed work, will get another + // chance to run before the loop goes to sleep. + bool more_delayed_work = !next_time.is_null(); + if (more_delayed_work) { + TimeDelta delay = next_time - TimeTicks::Now(); + if (delay > TimeDelta()) { + // There's more delayed work to be done in the future. + ScheduleDelayedWork(next_time); + } else { + // There's more delayed work to be done, and its time is in the past. + // Arrange to come back here directly as long as the loop is still + // running. + resignal_work_source = true; + } + } + } + + if (resignal_work_source) { + CFRunLoopSourceSignal(work_source_); + } + + return resignal_work_source; +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::RunIdleWorkSource(void* info) { + MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); + self->RunIdleWork(); +} + +// Called by MessagePumpCFRunLoopBase::RunIdleWorkSource. +bool MessagePumpCFRunLoopBase::RunIdleWork() { + if (!delegate_) { + // This point can be reached with a NULL delegate_ if Run is not on the + // stack but foreign code is spinning the CFRunLoop. Arrange to come back + // here when a delegate is available. + delegateless_idle_work_ = true; + return false; + } + + // The NSApplication-based run loop only drains the autorelease pool at each + // UI event (NSEvent). The autorelease pool is not drained for each + // CFRunLoopSource target that's run. Use a local pool for any autoreleased + // objects if the app is not currently handling a UI event to ensure they're + // released promptly even in the absence of UI events. + MessagePumpScopedAutoreleasePool autorelease_pool(this); + + // Call DoIdleWork once, and if something was done, arrange to come back here + // again as long as the loop is still running. + bool did_work = delegate_->DoIdleWork(); + if (did_work) { + CFRunLoopSourceSignal(idle_work_source_); + } + + return did_work; +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource(void* info) { + MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); + self->RunNestingDeferredWork(); +} + +// Called by MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource. +bool MessagePumpCFRunLoopBase::RunNestingDeferredWork() { + if (!delegate_) { + // This point can be reached with a NULL delegate_ if Run is not on the + // stack but foreign code is spinning the CFRunLoop. There's no sense in + // attempting to do any work or signalling the work sources because + // without a delegate, work is not possible. + return false; + } + + // Immediately try work in priority order. + if (!RunWork()) { + if (!RunIdleWork()) { + return false; + } + } else { + // Work was done. Arrange for the loop to try non-nestable idle work on + // a subsequent pass. + CFRunLoopSourceSignal(idle_work_source_); + } + + return true; +} + +// Called before the run loop goes to sleep or exits, or processes sources. +void MessagePumpCFRunLoopBase::MaybeScheduleNestingDeferredWork() { + // deepest_nesting_level_ is set as run loops are entered. If the deepest + // level encountered is deeper than the current level, a nested loop + // (relative to the current level) ran since the last time nesting-deferred + // work was scheduled. When that situation is encountered, schedule + // nesting-deferred work in case any work was deferred because nested work + // was disallowed. + if (deepest_nesting_level_ > nesting_level_) { + deepest_nesting_level_ = nesting_level_; + CFRunLoopSourceSignal(nesting_deferred_work_source_); + } +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::PreWaitObserver(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, + void* info) { + MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); + + // Attempt to do some idle work before going to sleep. + self->RunIdleWork(); + + // The run loop is about to go to sleep. If any of the work done since it + // started or woke up resulted in a nested run loop running, + // nesting-deferred work may have accumulated. Schedule it for processing + // if appropriate. + self->MaybeScheduleNestingDeferredWork(); +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::PreSourceObserver(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, + void* info) { + MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); + + // The run loop has reached the top of the loop and is about to begin + // processing sources. If the last iteration of the loop at this nesting + // level did not sleep or exit, nesting-deferred work may have accumulated + // if a nested loop ran. Schedule nesting-deferred work for processing if + // appropriate. + self->MaybeScheduleNestingDeferredWork(); +} + +// Called from the run loop. +// static +void MessagePumpCFRunLoopBase::EnterExitObserver(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, + void* info) { + MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); + + switch (activity) { + case kCFRunLoopEntry: + ++self->nesting_level_; + if (self->nesting_level_ > self->deepest_nesting_level_) { + self->deepest_nesting_level_ = self->nesting_level_; + } + break; + + case kCFRunLoopExit: + // Not all run loops go to sleep. If a run loop is stopped before it + // goes to sleep due to a CFRunLoopStop call, or if the timeout passed + // to CFRunLoopRunInMode expires, the run loop may proceed directly from + // handling sources to exiting without any sleep. This most commonly + // occurs when CFRunLoopRunInMode is passed a timeout of 0, causing it + // to make a single pass through the loop and exit without sleep. Some + // native loops use CFRunLoop in this way. Because PreWaitObserver will + // not be called in these case, MaybeScheduleNestingDeferredWork needs + // to be called here, as the run loop exits. + // + // MaybeScheduleNestingDeferredWork consults self->nesting_level_ + // to determine whether to schedule nesting-deferred work. It expects + // the nesting level to be set to the depth of the loop that is going + // to sleep or exiting. It must be called before decrementing the + // value so that the value still corresponds to the level of the exiting + // loop. + self->MaybeScheduleNestingDeferredWork(); + --self->nesting_level_; + break; + + default: + break; + } + + self->EnterExitRunLoop(activity); +} + +// Called by MessagePumpCFRunLoopBase::EnterExitRunLoop. The default +// implementation is a no-op. +void MessagePumpCFRunLoopBase::EnterExitRunLoop(CFRunLoopActivity activity) { +} + +// Base version returns a standard NSAutoreleasePool. +NSAutoreleasePool* MessagePumpCFRunLoopBase::CreateAutoreleasePool() { + return [[NSAutoreleasePool alloc] init]; +} + +MessagePumpCFRunLoop::MessagePumpCFRunLoop() + : quit_pending_(false) { +} + +MessagePumpCFRunLoop::~MessagePumpCFRunLoop() {} + +// Called by MessagePumpCFRunLoopBase::DoRun. If other CFRunLoopRun loops were +// running lower on the run loop thread's stack when this object was created, +// the same number of CFRunLoopRun loops must be running for the outermost call +// to Run. Run/DoRun are reentrant after that point. +void MessagePumpCFRunLoop::DoRun(Delegate* delegate) { + // This is completely identical to calling CFRunLoopRun(), except autorelease + // pool management is introduced. + int result; + do { + MessagePumpScopedAutoreleasePool autorelease_pool(this); + result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, + kCFTimeIntervalMax, + false); + } while (result != kCFRunLoopRunStopped && result != kCFRunLoopRunFinished); +} + +// Must be called on the run loop thread. +void MessagePumpCFRunLoop::Quit() { + // Stop the innermost run loop managed by this MessagePumpCFRunLoop object. + if (nesting_level() == run_nesting_level()) { + // This object is running the innermost loop, just stop it. + CFRunLoopStop(run_loop()); + } else { + // There's another loop running inside the loop managed by this object. + // In other words, someone else called CFRunLoopRunInMode on the same + // thread, deeper on the stack than the deepest Run call. Don't preempt + // other run loops, just mark this object to quit the innermost Run as + // soon as the other inner loops not managed by Run are done. + quit_pending_ = true; + } +} + +// Called by MessagePumpCFRunLoopBase::EnterExitObserver. +void MessagePumpCFRunLoop::EnterExitRunLoop(CFRunLoopActivity activity) { + if (activity == kCFRunLoopExit && + nesting_level() == run_nesting_level() && + quit_pending_) { + // Quit was called while loops other than those managed by this object + // were running further inside a run loop managed by this object. Now + // that all unmanaged inner run loops are gone, stop the loop running + // just inside Run. + CFRunLoopStop(run_loop()); + quit_pending_ = false; + } +} + +MessagePumpNSRunLoop::MessagePumpNSRunLoop() + : keep_running_(true) { + CFRunLoopSourceContext source_context = CFRunLoopSourceContext(); + source_context.perform = NoOp; + quit_source_ = CFRunLoopSourceCreate(NULL, // allocator + 0, // priority + &source_context); + CFRunLoopAddSource(run_loop(), quit_source_, kCFRunLoopCommonModes); +} + +MessagePumpNSRunLoop::~MessagePumpNSRunLoop() { + CFRunLoopRemoveSource(run_loop(), quit_source_, kCFRunLoopCommonModes); + CFRelease(quit_source_); +} + +void MessagePumpNSRunLoop::DoRun(Delegate* delegate) { + while (keep_running_) { + // NSRunLoop manages autorelease pools itself. + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:[NSDate distantFuture]]; + } + + keep_running_ = true; +} + +void MessagePumpNSRunLoop::Quit() { + keep_running_ = false; + CFRunLoopSourceSignal(quit_source_); + CFRunLoopWakeUp(run_loop()); +} + +#if defined(OS_IOS) +MessagePumpUIApplication::MessagePumpUIApplication() + : run_loop_(NULL) { +} + +MessagePumpUIApplication::~MessagePumpUIApplication() {} + +void MessagePumpUIApplication::DoRun(Delegate* delegate) { + NOTREACHED(); +} + +void MessagePumpUIApplication::Quit() { + NOTREACHED(); +} + +void MessagePumpUIApplication::Attach(Delegate* delegate) { + DCHECK(!run_loop_); + run_loop_ = new base::RunLoop(); + CHECK(run_loop_->BeforeRun()); + SetDelegate(delegate); +} + +#else + +MessagePumpNSApplication::MessagePumpNSApplication() + : keep_running_(true), + running_own_loop_(false) { +} + +MessagePumpNSApplication::~MessagePumpNSApplication() {} + +void MessagePumpNSApplication::DoRun(Delegate* delegate) { + bool last_running_own_loop_ = running_own_loop_; + + // NSApp must be initialized by calling: + // [{some class which implements CrAppProtocol} sharedApplication] + // Most likely candidates are CrApplication or BrowserCrApplication. + // These can be initialized from C++ code by calling + // RegisterCrApp() or RegisterBrowserCrApp(). + CHECK(NSApp); + + if (![NSApp isRunning]) { + running_own_loop_ = false; + // NSApplication manages autorelease pools itself when run this way. + [NSApp run]; + } else { + running_own_loop_ = true; + NSDate* distant_future = [NSDate distantFuture]; + while (keep_running_) { + MessagePumpScopedAutoreleasePool autorelease_pool(this); + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:distant_future + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event) { + [NSApp sendEvent:event]; + } + } + keep_running_ = true; + } + + running_own_loop_ = last_running_own_loop_; +} + +void MessagePumpNSApplication::Quit() { + if (!running_own_loop_) { + [[NSApplication sharedApplication] stop:nil]; + } else { + keep_running_ = false; + } + + // Send a fake event to wake the loop up. + [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined + location:NSMakePoint(0, 0) + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:NULL + subtype:0 + data1:0 + data2:0] + atStart:NO]; +} + +MessagePumpCrApplication::MessagePumpCrApplication() { +} + +// Prevents an autorelease pool from being created if the app is in the midst of +// handling a UI event because various parts of AppKit depend on objects that +// are created while handling a UI event to be autoreleased in the event loop. +// An example of this is NSWindowController. When a window with a window +// controller is closed it goes through a stack like this: +// (Several stack frames elided for clarity) +// +// #0 [NSWindowController autorelease] +// #1 DoAClose +// #2 MessagePumpCFRunLoopBase::DoWork() +// #3 [NSRunLoop run] +// #4 [NSButton performClick:] +// #5 [NSWindow sendEvent:] +// #6 [NSApp sendEvent:] +// #7 [NSApp run] +// +// -performClick: spins a nested run loop. If the pool created in DoWork was a +// standard NSAutoreleasePool, it would release the objects that were +// autoreleased into it once DoWork released it. This would cause the window +// controller, which autoreleased itself in frame #0, to release itself, and +// possibly free itself. Unfortunately this window controller controls the +// window in frame #5. When the stack is unwound to frame #5, the window would +// no longer exists and crashes may occur. Apple gets around this by never +// releasing the pool it creates in frame #4, and letting frame #7 clean it up +// when it cleans up the pool that wraps frame #7. When an autorelease pool is +// released it releases all other pools that were created after it on the +// autorelease pool stack. +// +// CrApplication is responsible for setting handlingSendEvent to true just +// before it sends the event through the event handling mechanism, and +// returning it to its previous value once the event has been sent. +NSAutoreleasePool* MessagePumpCrApplication::CreateAutoreleasePool() { + if (MessagePumpMac::IsHandlingSendEvent()) + return nil; + return MessagePumpNSApplication::CreateAutoreleasePool(); +} + +// static +bool MessagePumpMac::UsingCrApp() { + DCHECK([NSThread isMainThread]); + + // If NSApp is still not initialized, then the subclass used cannot + // be determined. + DCHECK(NSApp); + + // The pump was created using MessagePumpNSApplication. + if (not_using_crapp) + return false; + + return [NSApp conformsToProtocol:@protocol(CrAppProtocol)]; +} + +// static +bool MessagePumpMac::IsHandlingSendEvent() { + DCHECK([NSApp conformsToProtocol:@protocol(CrAppProtocol)]); + NSObject<CrAppProtocol>* app = static_cast<NSObject<CrAppProtocol>*>(NSApp); + return [app isHandlingSendEvent]; +} +#endif // !defined(OS_IOS) + +// static +MessagePump* MessagePumpMac::Create() { + if ([NSThread isMainThread]) { +#if defined(OS_IOS) + return new MessagePumpUIApplication; +#else + if ([NSApp conformsToProtocol:@protocol(CrAppProtocol)]) + return new MessagePumpCrApplication; + + // The main-thread MessagePump implementations REQUIRE an NSApp. + // Executables which have specific requirements for their + // NSApplication subclass should initialize appropriately before + // creating an event loop. + [NSApplication sharedApplication]; + not_using_crapp = true; + return new MessagePumpNSApplication; +#endif + } + + return new MessagePumpNSRunLoop; +} + +} // namespace base
diff --git a/src/base/message_pump_observer.h b/src/base/message_pump_observer.h new file mode 100644 index 0000000..a7d2aa7 --- /dev/null +++ b/src/base/message_pump_observer.h
@@ -0,0 +1,47 @@ +// 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_MESSAGE_PUMP_OBSERVER_H +#define BASE_MESSAGE_PUMP_OBSERVER_H + +#include "base/base_export.h" +#include "base/event_types.h" + +namespace base { + +enum EventStatus { + EVENT_CONTINUE, // The event should be dispatched as normal. +#if defined(USE_X11) + EVENT_HANDLED // The event should not be processed any farther. +#endif +}; + +// A MessagePumpObserver is an object that receives global +// notifications from the UI MessageLoop with MessagePumpWin or +// MessagePumpAuraX11. +// +// NOTE: An Observer implementation should be extremely fast! +// +// For use with MessagePumpAuraX11, please see message_pump_glib.h for more +// info about how this is invoked in this environment. +class BASE_EXPORT MessagePumpObserver { + public: + // This method is called before processing a NativeEvent. If the + // method returns EVENT_HANDLED, it indicates the event has already + // been handled, so the event is not processed any farther. If the + // method returns EVENT_CONTINUE, the event dispatching proceeds as + // normal. + virtual EventStatus WillProcessEvent(const NativeEvent& event) = 0; + + // This method is called after processing a message. This method + // will not be called if WillProcessEvent returns EVENT_HANDLED. + virtual void DidProcessEvent(const NativeEvent& event) = 0; + + protected: + virtual ~MessagePumpObserver() {} +}; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_OBSERVER_VIEWS_H
diff --git a/src/base/message_pump_shell.cc b/src/base/message_pump_shell.cc new file mode 100644 index 0000000..c451f3d --- /dev/null +++ b/src/base/message_pump_shell.cc
@@ -0,0 +1,149 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#if !defined(__LB_ANDROID__) +#include "message_pump_shell.h" +#include "base/logging.h" + +namespace base { + +MessagePumpShell::MessagePumpShell() + : keep_running_(true), + event_(false, false) { +} + +bool MessagePumpShell::DoWork() { + NOTREACHED(); + return false; +} + +bool MessagePumpShell::DoDelayedWork(TimeTicks *next_delayed_work_time) { + NOTREACHED(); + return false; +} + +bool MessagePumpShell::DoIdleWork() { + NOTREACHED(); + return false; +} + +void MessagePumpShell::Run(Delegate * delegate) { + DCHECK(keep_running_) << "Quit must have been called outside of Run!"; + + for (;;) { + bool did_work = delegate->DoWork(); + if (!keep_running_) + break; + + bool did_delayed_work = false; + // Let's play catchup on all delayed work before we loop. + // This fixes bug #5534709 by processing a large number of + // short delayed tasks (like WebKitClientImpl's stuff) + // quickly before looping back to process non-delayed tasks + // (like paint). Otherwise, WebKitClientImpl's stuff + // (including everything in WebCore & JSC) gets starved out + // because the backlog of delayed tasks only gets processed + // at one task per drawn frame. + do { + did_delayed_work = delegate->DoDelayedWork(&delayed_work_time_); + did_work |= did_delayed_work; + } while (did_delayed_work && keep_running_); + + if (!keep_running_) + break; + + if (did_work) + continue; + + did_work = delegate->DoIdleWork(); + if (!keep_running_) + break; + + if (did_work) + continue; + + if (delayed_work_time_.is_null()) { + event_.Wait(); + } else { + TimeDelta delay = delayed_work_time_ - TimeTicks::Now(); + if (delay > TimeDelta()) { + event_.TimedWait(delay); + } else { + // It looks like delayed_work_time_ indicates a time in the past, so we + // need to call DoDelayedWork now. + delayed_work_time_ = TimeTicks(); + } + } + // Since event_ is auto-reset, we don't need to do anything special here + // other than service each delegate method. + } + keep_running_ = true; +} + +void MessagePumpShell::Quit() { + keep_running_ = false; +} + +void MessagePumpShell::ScheduleWork() { + event_.Signal(); +} + +void MessagePumpShell::ScheduleDelayedWork(const TimeTicks& delayed_work_time) { + delayed_work_time_ = delayed_work_time; +} + +void MessagePumpShell::RunWithDispatcher(Delegate* delegate, Dispatcher* dispatcher) { + NOTREACHED(); +} + +void MessagePumpShell::AddObserver(Observer* observer) { + NOTREACHED(); +} + +void MessagePumpShell::RemoveObserver(Observer* observer) { + NOTREACHED(); +} + +void MessagePumpShell::AddIOObserver(IOObserver* observer) { + NOTREACHED(); +} + +void MessagePumpShell::RemoveIOObserver(IOObserver* observer) { + NOTREACHED(); +} + +bool MessagePumpShell::WatchSocket(int s, + bool persistent, + int mode, + FileDescriptorWatcher *controller, + Watcher *del) { + NOTREACHED(); + return false; +} + + +void MessagePumpShell::Watcher::OnFileCanReadWithoutBlocking(int fd) +{ + NOTREACHED(); +} + +void MessagePumpShell::Watcher::OnFileCanWriteWithoutBlocking(int fd) +{ + NOTREACHED(); +} + + +} // namespace base +#endif
diff --git a/src/base/message_pump_shell.h b/src/base/message_pump_shell.h new file mode 100644 index 0000000..32356af --- /dev/null +++ b/src/base/message_pump_shell.h
@@ -0,0 +1,115 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BASE_MESSAGE_PUMP_SHELL_H_ +#define BASE_MESSAGE_PUMP_SHELL_H_ +#pragma once + +#include "base/message_pump.h" +#include "base/time.h" +#include "base/synchronization/waitable_event.h" + +namespace base { + +class BASE_EXPORT MessagePumpShell : public MessagePump { + public: + + MessagePumpShell(); + + class Dispatcher { + }; + + class Observer { + }; + + class IOObserver { + }; + + class Watcher { + public: + Watcher(); + Watcher(Watcher &); + // __LB_SHELL__WRITE_ME__ + + virtual void OnFileCanReadWithoutBlocking(int fd); + virtual void OnFileCanWriteWithoutBlocking(int fd); + }; + + // this one only watches sockets, not all file descriptors + class FileDescriptorWatcher { + public: + FileDescriptorWatcher(); + FileDescriptorWatcher(FileDescriptorWatcher &); + // __LB_SHELL__WRITE_ME__ + bool StopWatchingFileDescriptor(); + }; + + // borrowed from message_pump_libevent.h + enum Mode { + WATCH_READ = 1 << 0, + WATCH_WRITE = 1 << 1, + WATCH_READ_WRITE = WATCH_READ | WATCH_WRITE + }; + + // ------------------------------------------- UI + + // cribbed from message_pump.h + virtual bool DoWork(); + virtual bool DoDelayedWork(TimeTicks *next_delayed_work_time); + virtual bool DoIdleWork(); + + virtual void Run(Delegate * delegate); + virtual void Quit(); + virtual void ScheduleWork(); + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time); + + // cribbed from message_pump_glib.h + virtual void RunWithDispatcher(Delegate* delegate, Dispatcher* dispatcher); + + // Add an Observer, which will start receiving notifications immediately. + void AddObserver(Observer* observer); + + // Remove an Observer. It is safe to call this method while an Observer is + // receiving a notification callback. + void RemoveObserver(Observer* observer); + + // ------------------------------------------- IO + void AddIOObserver(IOObserver* obs); + void RemoveIOObserver(IOObserver* obs); + + bool WatchSocket(int s, + bool persistent, + int mode, + FileDescriptorWatcher *controller, + Watcher *del); + + private: + // set to false when run should return + bool keep_running_; + + WaitableEvent event_; + + TimeTicks delayed_work_time_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpShell); +}; + +typedef MessagePumpShell MessagePumpForUI; +typedef MessagePumpShell MessagePumpForIO; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_SHELL_H_
diff --git a/src/base/message_pump_ui_starboard.cc b/src/base/message_pump_ui_starboard.cc new file mode 100644 index 0000000..74a71f5 --- /dev/null +++ b/src/base/message_pump_ui_starboard.cc
@@ -0,0 +1,196 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/message_pump_ui_starboard.h" + +#include "base/logging.h" +#include "base/run_loop.h" +#include "base/time.h" + +#include "starboard/event.h" +#include "starboard/system.h" + +namespace base { + +namespace { + +void CallMessagePumpImmediate(void* context) { + DCHECK(context); + MessagePumpUIStarboard* pump = + reinterpret_cast<MessagePumpUIStarboard*>(context); + pump->RunOneAndReschedule(false /*delayed*/); +} + +void CallMessagePumpDelayed(void* context) { + DCHECK(context); + MessagePumpUIStarboard* pump = + reinterpret_cast<MessagePumpUIStarboard*>(context); + pump->RunOneAndReschedule(true /*delayed*/); +} + +} // namespace + +MessagePumpUIStarboard::MessagePumpUIStarboard() : delegate_(NULL) {} + +void MessagePumpUIStarboard::RunOneAndReschedule(bool delayed) { + DCHECK(delegate_); + if (delayed) { + CancelDelayed(); + } else { + CancelImmediate(); + } + + TimeTicks delayed_work_time; + for (;;) { + TimeTicks next_time; + if (!RunOne(&next_time)) { + delayed_work_time = next_time; + break; + } + } + + if (!delayed_work_time.is_null()) { + ScheduleDelayedWork(delayed_work_time); + } +} + +void MessagePumpUIStarboard::Run(Delegate* delegate) { + // This should never be called because we are not like a normal message pump + // where we loop until told to quit. We are providing a MessagePump interface + // on top of an externally-owned message pump. We want to exist and be able to + // schedule work, but the actual for(;;) loop is owned by Starboard. + NOTREACHED(); +} + +void MessagePumpUIStarboard::Start(Delegate* delegate) { + run_loop_.reset(new base::RunLoop()); + delegate_ = delegate; + + // Since the RunLoop was just created above, BeforeRun should be guaranteed to + // return true (it only returns false if the RunLoop has been Quit already). + if (!run_loop_->BeforeRun()) + NOTREACHED(); +} + +void MessagePumpUIStarboard::Quit() { + delegate_ = NULL; + CancelAll(); + if (run_loop_) { + run_loop_->AfterRun(); + run_loop_.reset(); + } +} + +void MessagePumpUIStarboard::ScheduleWork() { + base::AutoLock auto_lock(outstanding_events_lock_); + if (!outstanding_events_.empty()) { + // No need, already an outstanding event. + return; + } + + outstanding_events_.insert( + SbEventSchedule(&CallMessagePumpImmediate, this, 0)); +} + +void MessagePumpUIStarboard::ScheduleDelayedWork( + const base::TimeTicks& delayed_work_time) { + base::TimeDelta delay; + if (!delayed_work_time.is_null()) { + delay = delayed_work_time - base::TimeTicks::Now(); + + if (delay <= base::TimeDelta()) { + delay = base::TimeDelta(); + } + } + + { + base::AutoLock auto_lock(outstanding_events_lock_); + // Make sure any outstanding delayed event is canceled. + CancelDelayedLocked(); + outstanding_delayed_events_.insert( + SbEventSchedule(&CallMessagePumpDelayed, this, delay.ToSbTime())); + } +} + +void MessagePumpUIStarboard::CancelAll() { + base::AutoLock auto_lock(outstanding_events_lock_); + CancelImmediateLocked(); + CancelDelayedLocked(); +} + +void MessagePumpUIStarboard::CancelImmediate() { + base::AutoLock auto_lock(outstanding_events_lock_); + CancelImmediateLocked(); +} + +void MessagePumpUIStarboard::CancelDelayed() { + base::AutoLock auto_lock(outstanding_events_lock_); + CancelDelayedLocked(); +} + +void MessagePumpUIStarboard::CancelImmediateLocked() { + outstanding_events_lock_.AssertAcquired(); + for (SbEventIdSet::iterator it = outstanding_events_.begin(); + it != outstanding_events_.end(); ++it) { + SbEventCancel(*it); + } + outstanding_events_.erase(outstanding_events_.begin(), + outstanding_events_.end()); +} + +void MessagePumpUIStarboard::CancelDelayedLocked() { + outstanding_events_lock_.AssertAcquired(); + for (SbEventIdSet::iterator it = outstanding_delayed_events_.begin(); + it != outstanding_delayed_events_.end(); ++it) { + SbEventCancel(*it); + } + outstanding_delayed_events_.erase(outstanding_delayed_events_.begin(), + outstanding_delayed_events_.end()); +} + +bool MessagePumpUIStarboard::RunOne(TimeTicks* out_delayed_work_time) { + DCHECK(out_delayed_work_time); + + // We expect to start with a delegate, so we can DCHECK it, but any task we + // run could call Quit and remove it. + DCHECK(delegate_); + + // Do immediate work. + bool did_work = delegate_->DoWork(); + + // Do all delayed work. Unlike Chromium, we drain all due delayed work before + // going back to the loop. See message_pump_io_starboard.cc for more + // information. + while (delegate_ && delegate_->DoDelayedWork(out_delayed_work_time)) { + did_work = true; + } + + // If we did work, and we still have a delegate, return true, so we will be + // called again. + if (did_work) { + return !!delegate_; + } + + // If the delegate has been removed, Quit() has been called, so no more work. + if (!delegate_) { + return false; + } + + // No work was done, so only call back if there was idle work done, otherwise + // go to sleep. ScheduleWork or ScheduleDelayedWork will be called if new work + // is scheduled. + return delegate_->DoIdleWork(); +} + +} // namespace base
diff --git a/src/base/message_pump_ui_starboard.h b/src/base/message_pump_ui_starboard.h new file mode 100644 index 0000000..08f6be4 --- /dev/null +++ b/src/base/message_pump_ui_starboard.h
@@ -0,0 +1,106 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BASE_MESSAGE_PUMP_UI_STARBOARD_H_ +#define BASE_MESSAGE_PUMP_UI_STARBOARD_H_ + +#include <set> + +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/message_pump.h" +#include "base/run_loop.h" +#include "base/synchronization/lock.h" +#include "base/time.h" +#include "starboard/event.h" + +namespace base { + +// MessagePump that integrates with the Starboard message pump. Starboard has +// its own main loop, so the MessagePumpUIStarboard just piggybacks on that +// rather than implementing its own pump. +// +// To adopt Starboard's message pump, one simply has to create a MessageLoop of +// TYPE_UI from the Starboard message pump thread. A traditional place to do +// this would be in the kSbEventStart event handler. One has to be sure to keep +// the MessageLoop alive for as long as the application wants to use the +// Starboard message pump as a MessageLoop. That would typically be for the +// entire lifetime of the application, and in that case, the MessageLoop would +// traditionally be deleted in the kSbEventStop handler. +class BASE_EXPORT MessagePumpUIStarboard : public MessagePump { + public: + MessagePumpUIStarboard(); + + // Runs one iteration of the run loop, and reschedules another call, if + // necessary. + void RunOneAndReschedule(bool delayed); + + // --- MessagePump Implementation --- + + virtual void Run(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + virtual void ScheduleWork() OVERRIDE; + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time) OVERRIDE; + virtual void Start(Delegate* delegate); + + protected: + virtual ~MessagePumpUIStarboard() {} + + private: + // Cancels all outstanding scheduled callback events, if any. + void CancelAll(); + + // Cancels immediate schedule callback events. + void CancelImmediate(); + + // Cancels delayed schedule callback events. + void CancelDelayed(); + + // Cancel workhorse that assumes |outstanding_events_lock_| is locked. + void CancelImmediateLocked(); + + // Cancel delayed workhorse that assumes |outstanding_events_lock_| is locked. + void CancelDelayedLocked(); + + // Runs one iteration of the run loop, returning whether to schedule another + // iteration or not. Places the delay, if any, in |out_delayed_work_time|. + bool RunOne(base::TimeTicks* out_delayed_work_time); + + // The top-level RunLoop for the MessageLoopForUI. A MessageLoop needs a + // top-level RunLoop to be considered "running." + scoped_ptr<base::RunLoop> run_loop_; + + // The MessagePump::Delegate configured in Start(). + Delegate* delegate_; + + // Lock protecting outstanding scheduled callback events. + base::Lock outstanding_events_lock_; + + // A set of scheduled callback event IDs. + typedef std::set<SbEventId> SbEventIdSet; + + // The set of outstanding scheduled callback events for immediate work. + SbEventIdSet outstanding_events_; + + // The set of outstanding scheduled callback events for delayed work. + SbEventIdSet outstanding_delayed_events_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpUIStarboard); +}; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_UI_STARBOARD_H_
diff --git a/src/base/message_pump_win.cc b/src/base/message_pump_win.cc new file mode 100644 index 0000000..fb962ca --- /dev/null +++ b/src/base/message_pump_win.cc
@@ -0,0 +1,680 @@ +// 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/message_pump_win.h" + +#include <math.h> + +#include "base/debug/trace_event.h" +#include "base/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/process_util.h" +#include "base/win/wrapped_window_proc.h" + +namespace { + +enum MessageLoopProblems { + MESSAGE_POST_ERROR, + COMPLETION_POST_ERROR, + SET_TIMER_ERROR, + MESSAGE_LOOP_PROBLEM_MAX, +}; + +} // namespace + +namespace base { + +static const wchar_t kWndClass[] = L"Chrome_MessagePumpWindow"; + +// Message sent to get an additional time slice for pumping (processing) another +// task (a series of such messages creates a continuous task pump). +static const int kMsgHaveWork = WM_USER + 1; + +//----------------------------------------------------------------------------- +// MessagePumpWin public: + +void MessagePumpWin::AddObserver(MessagePumpObserver* observer) { + observers_.AddObserver(observer); +} + +void MessagePumpWin::RemoveObserver(MessagePumpObserver* observer) { + observers_.RemoveObserver(observer); +} + +void MessagePumpWin::WillProcessMessage(const MSG& msg) { + FOR_EACH_OBSERVER(MessagePumpObserver, observers_, WillProcessEvent(msg)); +} + +void MessagePumpWin::DidProcessMessage(const MSG& msg) { + FOR_EACH_OBSERVER(MessagePumpObserver, observers_, DidProcessEvent(msg)); +} + +void MessagePumpWin::RunWithDispatcher( + Delegate* delegate, MessagePumpDispatcher* dispatcher) { + RunState s; + s.delegate = delegate; + s.dispatcher = dispatcher; + s.should_quit = false; + s.run_depth = state_ ? state_->run_depth + 1 : 1; + + RunState* previous_state = state_; + state_ = &s; + + DoRunLoop(); + + state_ = previous_state; +} + +void MessagePumpWin::Quit() { + DCHECK(state_); + state_->should_quit = true; +} + +//----------------------------------------------------------------------------- +// MessagePumpWin protected: + +int MessagePumpWin::GetCurrentDelay() const { + if (delayed_work_time_.is_null()) + return -1; + + // Be careful here. TimeDelta has a precision of microseconds, but we want a + // value in milliseconds. If there are 5.5ms left, should the delay be 5 or + // 6? It should be 6 to avoid executing delayed work too early. + double timeout = + ceil((delayed_work_time_ - TimeTicks::Now()).InMillisecondsF()); + + // If this value is negative, then we need to run delayed work soon. + int delay = static_cast<int>(timeout); + if (delay < 0) + delay = 0; + + return delay; +} + +//----------------------------------------------------------------------------- +// MessagePumpForUI public: + +MessagePumpForUI::MessagePumpForUI() + : instance_(NULL), + message_filter_(new MessageFilter) { + InitMessageWnd(); +} + +MessagePumpForUI::~MessagePumpForUI() { + DestroyWindow(message_hwnd_); + UnregisterClass(kWndClass, instance_); +} + +void MessagePumpForUI::ScheduleWork() { + if (InterlockedExchange(&have_work_, 1)) + return; // Someone else continued the pumping. + + // Make sure the MessagePump does some work for us. + BOOL ret = PostMessage(message_hwnd_, kMsgHaveWork, + reinterpret_cast<WPARAM>(this), 0); + if (ret) + return; // There was room in the Window Message queue. + + // We have failed to insert a have-work message, so there is a chance that we + // will starve tasks/timers while sitting in a nested message loop. Nested + // loops only look at Windows Message queues, and don't look at *our* task + // queues, etc., so we might not get a time slice in such. :-( + // We could abort here, but the fear is that this failure mode is plausibly + // common (queue is full, of about 2000 messages), so we'll do a near-graceful + // recovery. Nested loops are pretty transient (we think), so this will + // probably be recoverable. + InterlockedExchange(&have_work_, 0); // Clarify that we didn't really insert. + UMA_HISTOGRAM_ENUMERATION("Chrome.MessageLoopProblem", MESSAGE_POST_ERROR, + MESSAGE_LOOP_PROBLEM_MAX); +} + +void MessagePumpForUI::ScheduleDelayedWork(const TimeTicks& delayed_work_time) { + // + // We would *like* to provide high resolution timers. Windows timers using + // SetTimer() have a 10ms granularity. We have to use WM_TIMER as a wakeup + // mechanism because the application can enter modal windows loops where it + // is not running our MessageLoop; the only way to have our timers fire in + // these cases is to post messages there. + // + // To provide sub-10ms timers, we process timers directly from our run loop. + // For the common case, timers will be processed there as the run loop does + // its normal work. However, we *also* set the system timer so that WM_TIMER + // events fire. This mops up the case of timers not being able to work in + // modal message loops. It is possible for the SetTimer to pop and have no + // pending timers, because they could have already been processed by the + // run loop itself. + // + // We use a single SetTimer corresponding to the timer that will expire + // soonest. As new timers are created and destroyed, we update SetTimer. + // Getting a spurrious SetTimer event firing is benign, as we'll just be + // processing an empty timer queue. + // + delayed_work_time_ = delayed_work_time; + + int delay_msec = GetCurrentDelay(); + DCHECK_GE(delay_msec, 0); + if (delay_msec < USER_TIMER_MINIMUM) + delay_msec = USER_TIMER_MINIMUM; + + // Create a WM_TIMER event that will wake us up to check for any pending + // timers (in case we are running within a nested, external sub-pump). + BOOL ret = SetTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this), + delay_msec, NULL); + if (ret) + return; + // If we can't set timers, we are in big trouble... but cross our fingers for + // now. + // TODO(jar): If we don't see this error, use a CHECK() here instead. + UMA_HISTOGRAM_ENUMERATION("Chrome.MessageLoopProblem", SET_TIMER_ERROR, + 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: + +// static +LRESULT CALLBACK MessagePumpForUI::WndProcThunk( + HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { + switch (message) { + case kMsgHaveWork: + reinterpret_cast<MessagePumpForUI*>(wparam)->HandleWorkMessage(); + break; + case WM_TIMER: + reinterpret_cast<MessagePumpForUI*>(wparam)->HandleTimerMessage(); + break; + } + return DefWindowProc(hwnd, message, wparam, lparam); +} + +void MessagePumpForUI::DoRunLoop() { + // IF this was just a simple PeekMessage() loop (servicing all possible work + // queues), then Windows would try to achieve the following order according + // to MSDN documentation about PeekMessage with no filter): + // * Sent messages + // * Posted messages + // * Sent messages (again) + // * WM_PAINT messages + // * WM_TIMER messages + // + // Summary: none of the above classes is starved, and sent messages has twice + // the chance of being processed (i.e., reduced service time). + + for (;;) { + // If we do any work, we may create more messages etc., and more work may + // possibly be waiting in another task group. When we (for example) + // ProcessNextWindowsMessage(), there is a good chance there are still more + // messages waiting. On the other hand, when any of these methods return + // having done no work, then it is pretty unlikely that calling them again + // quickly will find any work to do. Finally, if they all say they had no + // work, then it is a good time to consider sleeping (waiting) for more + // work. + + bool more_work_is_plausible = ProcessNextWindowsMessage(); + if (state_->should_quit) + break; + + more_work_is_plausible |= state_->delegate->DoWork(); + if (state_->should_quit) + break; + + more_work_is_plausible |= + state_->delegate->DoDelayedWork(&delayed_work_time_); + // If we did not process any delayed work, then we can assume that our + // existing WM_TIMER if any will fire when delayed work should run. We + // don't want to disturb that timer if it is already in flight. However, + // if we did do all remaining delayed work, then lets kill the WM_TIMER. + if (more_work_is_plausible && delayed_work_time_.is_null()) + KillTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this)); + if (state_->should_quit) + break; + + if (more_work_is_plausible) + continue; + + more_work_is_plausible = state_->delegate->DoIdleWork(); + if (state_->should_quit) + break; + + if (more_work_is_plausible) + continue; + + WaitForWork(); // Wait (sleep) until we have work to do again. + } +} + +void MessagePumpForUI::InitMessageWnd() { + WNDCLASSEX wc = {0}; + wc.cbSize = sizeof(wc); + wc.lpfnWndProc = base::win::WrappedWindowProc<WndProcThunk>; + wc.hInstance = base::GetModuleFromAddress(wc.lpfnWndProc); + wc.lpszClassName = kWndClass; + instance_ = wc.hInstance; + RegisterClassEx(&wc); + + message_hwnd_ = + CreateWindow(kWndClass, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, instance_, 0); + DCHECK(message_hwnd_); +} + +void MessagePumpForUI::WaitForWork() { + // Wait until a message is available, up to the time needed by the timer + // manager to fire the next set of timers. + int delay = GetCurrentDelay(); + if (delay < 0) // Negative value means no timers waiting. + delay = INFINITE; + + DWORD result; + result = MsgWaitForMultipleObjectsEx(0, NULL, delay, QS_ALLINPUT, + MWMO_INPUTAVAILABLE); + + if (WAIT_OBJECT_0 == result) { + // A WM_* message is available. + // If a parent child relationship exists between windows across threads + // then their thread inputs are implicitly attached. + // This causes the MsgWaitForMultipleObjectsEx API to return indicating + // that messages are ready for processing (Specifically, mouse messages + // intended for the child window may appear if the child window has + // capture). + // The subsequent PeekMessages call may fail to return any messages thus + // causing us to enter a tight loop at times. + // The WaitMessage call below is a workaround to give the child window + // some time to process its input messages. + MSG msg = {0}; + DWORD queue_status = GetQueueStatus(QS_MOUSE); + if (HIWORD(queue_status) & QS_MOUSE && + !PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE)) { + WaitMessage(); + } + return; + } + + DCHECK_NE(WAIT_FAILED, result) << GetLastError(); +} + +void MessagePumpForUI::HandleWorkMessage() { + // If we are being called outside of the context of Run, then don't try to do + // any work. This could correspond to a MessageBox call or something of that + // sort. + if (!state_) { + // Since we handled a kMsgHaveWork message, we must still update this flag. + InterlockedExchange(&have_work_, 0); + return; + } + + // Let whatever would have run had we not been putting messages in the queue + // run now. This is an attempt to make our dummy message not starve other + // messages that may be in the Windows message queue. + ProcessPumpReplacementMessage(); + + // Now give the delegate a chance to do some work. He'll let us know if he + // needs to do more work. + if (state_->delegate->DoWork()) + ScheduleWork(); +} + +void MessagePumpForUI::HandleTimerMessage() { + KillTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this)); + + // If we are being called outside of the context of Run, then don't do + // anything. This could correspond to a MessageBox call or something of + // that sort. + if (!state_) + return; + + state_->delegate->DoDelayedWork(&delayed_work_time_); + if (!delayed_work_time_.is_null()) { + // A bit gratuitous to set delayed_work_time_ again, but oh well. + ScheduleDelayedWork(delayed_work_time_); + } +} + +bool MessagePumpForUI::ProcessNextWindowsMessage() { + // If there are sent messages in the queue then PeekMessage internally + // dispatches the message and returns false. We return true in this + // case to ensure that the message loop peeks again instead of calling + // MsgWaitForMultipleObjectsEx again. + bool sent_messages_in_queue = false; + DWORD queue_status = GetQueueStatus(QS_SENDMESSAGE); + if (HIWORD(queue_status) & QS_SENDMESSAGE) + sent_messages_in_queue = true; + + MSG msg; + if (message_filter_->DoPeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + return ProcessMessageHelper(msg); + + return sent_messages_in_queue; +} + +bool MessagePumpForUI::ProcessMessageHelper(const MSG& msg) { + TRACE_EVENT1("base", "MessagePumpForUI::ProcessMessageHelper", + "message", msg.message); + if (WM_QUIT == msg.message) { + // Repost the QUIT message so that it will be retrieved by the primary + // GetMessage() loop. + state_->should_quit = true; + PostQuitMessage(static_cast<int>(msg.wParam)); + return false; + } + + // While running our main message pump, we discard kMsgHaveWork messages. + if (msg.message == kMsgHaveWork && msg.hwnd == message_hwnd_) + return ProcessPumpReplacementMessage(); + + if (CallMsgFilter(const_cast<MSG*>(&msg), kMessageFilterCode)) + return true; + + WillProcessMessage(msg); + + if (!message_filter_->ProcessMessage(msg)) { + if (state_->dispatcher) { + if (!state_->dispatcher->Dispatch(msg)) + state_->should_quit = true; + } else { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + DidProcessMessage(msg); + return true; +} + +bool MessagePumpForUI::ProcessPumpReplacementMessage() { + // When we encounter a kMsgHaveWork message, this method is called to peek + // and process a replacement message, such as a WM_PAINT or WM_TIMER. The + // goal is to make the kMsgHaveWork as non-intrusive as possible, even though + // a continuous stream of such messages are posted. This method carefully + // peeks a message while there is no chance for a kMsgHaveWork to be pending, + // then resets the have_work_ flag (allowing a replacement kMsgHaveWork to + // possibly be posted), and finally dispatches that peeked replacement. Note + // that the re-post of kMsgHaveWork may be asynchronous to this thread!! + + bool have_message = false; + MSG msg; + // We should not process all window messages if we are in the context of an + // OS modal loop, i.e. in the context of a windows API call like MessageBox. + // This is to ensure that these messages are peeked out by the OS modal loop. + if (MessageLoop::current()->os_modal_loop()) { + // We only peek out WM_PAINT and WM_TIMER here for reasons mentioned above. + have_message = PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_REMOVE) || + PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE); + } else { + have_message = !!message_filter_->DoPeekMessage(&msg, NULL, 0, 0, + PM_REMOVE); + } + + DCHECK(!have_message || kMsgHaveWork != msg.message || + msg.hwnd != message_hwnd_); + + // Since we discarded a kMsgHaveWork message, we must update the flag. + int old_have_work = InterlockedExchange(&have_work_, 0); + DCHECK(old_have_work); + + // We don't need a special time slice if we didn't have_message to process. + if (!have_message) + return false; + + // Guarantee we'll get another time slice in the case where we go into native + // windows code. This ScheduleWork() may hurt performance a tiny bit when + // tasks appear very infrequently, but when the event queue is busy, the + // kMsgHaveWork events get (percentage wise) rarer and rarer. + ScheduleWork(); + return ProcessMessageHelper(msg); +} + +void MessagePumpForUI::SetMessageFilter( + scoped_ptr<MessageFilter> message_filter) { + message_filter_ = message_filter.Pass(); +} + +//----------------------------------------------------------------------------- +// MessagePumpForIO public: + +MessagePumpForIO::MessagePumpForIO() { + port_.Set(CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 1)); + DCHECK(port_.IsValid()); +} + +void MessagePumpForIO::ScheduleWork() { + if (InterlockedExchange(&have_work_, 1)) + return; // Someone else continued the pumping. + + // Make sure the MessagePump does some work for us. + BOOL ret = PostQueuedCompletionStatus(port_, 0, + reinterpret_cast<ULONG_PTR>(this), + reinterpret_cast<OVERLAPPED*>(this)); + if (ret) + return; // Post worked perfectly. + + // See comment in MessagePumpForUI::ScheduleWork() for this error recovery. + InterlockedExchange(&have_work_, 0); // Clarify that we didn't succeed. + UMA_HISTOGRAM_ENUMERATION("Chrome.MessageLoopProblem", COMPLETION_POST_ERROR, + MESSAGE_LOOP_PROBLEM_MAX); +} + +void MessagePumpForIO::ScheduleDelayedWork(const TimeTicks& delayed_work_time) { + // We know that we can't be blocked right now since this method can only be + // called on the same thread as Run, so we only need to update our record of + // how long to sleep when we do sleep. + delayed_work_time_ = delayed_work_time; +} + +void MessagePumpForIO::RegisterIOHandler(HANDLE file_handle, + IOHandler* handler) { + ULONG_PTR key = HandlerToKey(handler, true); + HANDLE port = CreateIoCompletionPort(file_handle, port_, key, 1); + DPCHECK(port); +} + +bool MessagePumpForIO::RegisterJobObject(HANDLE job_handle, + IOHandler* handler) { + // Job object notifications use the OVERLAPPED pointer to carry the message + // data. Mark the completion key correspondingly, so we will not try to + // convert OVERLAPPED* to IOContext*. + ULONG_PTR key = HandlerToKey(handler, false); + JOBOBJECT_ASSOCIATE_COMPLETION_PORT info; + info.CompletionKey = reinterpret_cast<void*>(key); + info.CompletionPort = port_; + return SetInformationJobObject(job_handle, + JobObjectAssociateCompletionPortInformation, + &info, + sizeof(info)) != FALSE; +} + +//----------------------------------------------------------------------------- +// MessagePumpForIO private: + +void MessagePumpForIO::DoRunLoop() { + for (;;) { + // If we do any work, we may create more messages etc., and more work may + // possibly be waiting in another task group. When we (for example) + // WaitForIOCompletion(), there is a good chance there are still more + // messages waiting. On the other hand, when any of these methods return + // having done no work, then it is pretty unlikely that calling them + // again quickly will find any work to do. Finally, if they all say they + // had no work, then it is a good time to consider sleeping (waiting) for + // more work. + + bool more_work_is_plausible = state_->delegate->DoWork(); + if (state_->should_quit) + break; + + more_work_is_plausible |= WaitForIOCompletion(0, NULL); + if (state_->should_quit) + break; + + more_work_is_plausible |= + state_->delegate->DoDelayedWork(&delayed_work_time_); + if (state_->should_quit) + break; + + if (more_work_is_plausible) + continue; + + more_work_is_plausible = state_->delegate->DoIdleWork(); + if (state_->should_quit) + break; + + if (more_work_is_plausible) + continue; + + WaitForWork(); // Wait (sleep) until we have work to do again. + } +} + +// Wait until IO completes, up to the time needed by the timer manager to fire +// the next set of timers. +void MessagePumpForIO::WaitForWork() { + // We do not support nested IO message loops. This is to avoid messy + // recursion problems. + DCHECK_EQ(1, state_->run_depth) << "Cannot nest an IO message loop!"; + + int timeout = GetCurrentDelay(); + if (timeout < 0) // Negative value means no timers waiting. + timeout = INFINITE; + + WaitForIOCompletion(timeout, NULL); +} + +bool MessagePumpForIO::WaitForIOCompletion(DWORD timeout, IOHandler* filter) { + IOItem item; + if (completed_io_.empty() || !MatchCompletedIOItem(filter, &item)) { + // We have to ask the system for another IO completion. + if (!GetIOItem(timeout, &item)) + return false; + + if (ProcessInternalIOItem(item)) + return true; + } + + // If |item.has_valid_io_context| is false then |item.context| does not point + // to a context structure, and so should not be dereferenced, although it may + // still hold valid non-pointer data. + if (!item.has_valid_io_context || item.context->handler) { + if (filter && item.handler != filter) { + // Save this item for later + completed_io_.push_back(item); + } else { + DCHECK(!item.has_valid_io_context || + (item.context->handler == item.handler)); + WillProcessIOEvent(); + item.handler->OnIOCompleted(item.context, item.bytes_transfered, + item.error); + DidProcessIOEvent(); + } + } else { + // The handler must be gone by now, just cleanup the mess. + delete item.context; + } + return true; +} + +// Asks the OS for another IO completion result. +bool MessagePumpForIO::GetIOItem(DWORD timeout, IOItem* item) { + memset(item, 0, sizeof(*item)); + ULONG_PTR key = NULL; + OVERLAPPED* overlapped = NULL; + if (!GetQueuedCompletionStatus(port_.Get(), &item->bytes_transfered, &key, + &overlapped, timeout)) { + if (!overlapped) + return false; // Nothing in the queue. + item->error = GetLastError(); + item->bytes_transfered = 0; + } + + item->handler = KeyToHandler(key, &item->has_valid_io_context); + item->context = reinterpret_cast<IOContext*>(overlapped); + return true; +} + +bool MessagePumpForIO::ProcessInternalIOItem(const IOItem& item) { + if (this == reinterpret_cast<MessagePumpForIO*>(item.context) && + this == reinterpret_cast<MessagePumpForIO*>(item.handler)) { + // This is our internal completion. + DCHECK(!item.bytes_transfered); + InterlockedExchange(&have_work_, 0); + return true; + } + return false; +} + +// Returns a completion item that was previously received. +bool MessagePumpForIO::MatchCompletedIOItem(IOHandler* filter, IOItem* item) { + DCHECK(!completed_io_.empty()); + for (std::list<IOItem>::iterator it = completed_io_.begin(); + it != completed_io_.end(); ++it) { + if (!filter || it->handler == filter) { + *item = *it; + completed_io_.erase(it); + return true; + } + } + return false; +} + +void MessagePumpForIO::AddIOObserver(IOObserver *obs) { + io_observers_.AddObserver(obs); +} + +void MessagePumpForIO::RemoveIOObserver(IOObserver *obs) { + io_observers_.RemoveObserver(obs); +} + +void MessagePumpForIO::WillProcessIOEvent() { + FOR_EACH_OBSERVER(IOObserver, io_observers_, WillProcessIOEvent()); +} + +void MessagePumpForIO::DidProcessIOEvent() { + FOR_EACH_OBSERVER(IOObserver, io_observers_, DidProcessIOEvent()); +} + +// static +ULONG_PTR MessagePumpForIO::HandlerToKey(IOHandler* handler, + bool has_valid_io_context) { + ULONG_PTR key = reinterpret_cast<ULONG_PTR>(handler); + + // |IOHandler| is at least pointer-size aligned, so the lowest two bits are + // always cleared. We use the lowest bit to distinguish completion keys with + // and without the associated |IOContext|. + DCHECK((key & 1) == 0); + + // Mark the completion key as context-less. + if (!has_valid_io_context) + key = key | 1; + return key; +} + +// static +MessagePumpForIO::IOHandler* MessagePumpForIO::KeyToHandler( + ULONG_PTR key, + bool* has_valid_io_context) { + *has_valid_io_context = ((key & 1) == 0); + return reinterpret_cast<IOHandler*>(key & ~static_cast<ULONG_PTR>(1)); +} + +} // namespace base
diff --git a/src/base/message_pump_win.h b/src/base/message_pump_win.h new file mode 100644 index 0000000..5ea195f --- /dev/null +++ b/src/base/message_pump_win.h
@@ -0,0 +1,396 @@ +// 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_MESSAGE_PUMP_WIN_H_ +#define BASE_MESSAGE_PUMP_WIN_H_ + +#include <windows.h> + +#include <list> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_pump.h" +#include "base/message_pump_dispatcher.h" +#include "base/message_pump_observer.h" +#include "base/observer_list.h" +#include "base/time.h" +#include "base/win/scoped_handle.h" + +namespace base { + +// MessagePumpWin serves as the base for specialized versions of the MessagePump +// for Windows. It provides basic functionality like handling of observers and +// controlling the lifetime of the message pump. +class BASE_EXPORT MessagePumpWin : public MessagePump { + public: + MessagePumpWin() : have_work_(0), state_(NULL) {} + virtual ~MessagePumpWin() {} + + // Add an Observer, which will start receiving notifications immediately. + void AddObserver(MessagePumpObserver* observer); + + // Remove an Observer. It is safe to call this method while an Observer is + // receiving a notification callback. + void RemoveObserver(MessagePumpObserver* observer); + + // Give a chance to code processing additional messages to notify the + // message loop observers that another message has been processed. + void WillProcessMessage(const MSG& msg); + void DidProcessMessage(const MSG& msg); + + // Like MessagePump::Run, but MSG objects are routed through dispatcher. + void RunWithDispatcher(Delegate* delegate, MessagePumpDispatcher* dispatcher); + + // MessagePump methods: + virtual void Run(Delegate* delegate) { RunWithDispatcher(delegate, NULL); } + virtual void Quit(); + + protected: + struct RunState { + Delegate* delegate; + MessagePumpDispatcher* dispatcher; + + // Used to flag that the current Run() invocation should return ASAP. + bool should_quit; + + // Used to count how many Run() invocations are on the stack. + int run_depth; + }; + + virtual void DoRunLoop() = 0; + int GetCurrentDelay() const; + + ObserverList<MessagePumpObserver> observers_; + + // The time at which delayed work should run. + TimeTicks delayed_work_time_; + + // A boolean value used to indicate if there is a kMsgDoWork message pending + // in the Windows Message queue. There is at most one such message, and it + // can drive execution of tasks when a native message pump is running. + LONG have_work_; + + // State for the current invocation of Run. + RunState* state_; +}; + +//----------------------------------------------------------------------------- +// MessagePumpForUI extends MessagePumpWin with methods that are particular to a +// MessageLoop instantiated with TYPE_UI. +// +// MessagePumpForUI implements a "traditional" Windows message pump. It contains +// a nearly infinite loop that peeks out messages, and then dispatches them. +// Intermixed with those peeks are callouts to DoWork for pending tasks, and +// DoDelayedWork for pending timers. When there are no events to be serviced, +// this pump goes into a wait state. In most cases, this message pump handles +// all processing. +// +// However, when a task, or windows event, invokes on the stack a native dialog +// box or such, that window typically provides a bare bones (native?) message +// pump. That bare-bones message pump generally supports little more than a +// peek of the Windows message queue, followed by a dispatch of the peeked +// message. MessageLoop extends that bare-bones message pump to also service +// Tasks, at the cost of some complexity. +// +// The basic structure of the extension (refered to as a sub-pump) is that a +// special message, kMsgHaveWork, is repeatedly injected into the Windows +// Message queue. Each time the kMsgHaveWork message is peeked, checks are +// made for an extended set of events, including the availability of Tasks to +// run. +// +// After running a task, the special message kMsgHaveWork is again posted to +// the Windows Message queue, ensuring a future time slice for processing a +// future event. To prevent flooding the Windows Message queue, care is taken +// to be sure that at most one kMsgHaveWork message is EVER pending in the +// Window's Message queue. +// +// There are a few additional complexities in this system where, when there are +// no Tasks to run, this otherwise infinite stream of messages which drives the +// sub-pump is halted. The pump is automatically re-started when Tasks are +// queued. +// +// A second complexity is that the presence of this stream of posted tasks may +// prevent a bare-bones message pump from ever peeking a WM_PAINT or WM_TIMER. +// Such paint and timer events always give priority to a posted message, such as +// kMsgHaveWork messages. As a result, care is taken to do some peeking in +// between the posting of each kMsgHaveWork message (i.e., after kMsgHaveWork +// is peeked, and before a replacement kMsgHaveWork is posted). +// +// NOTE: Although it may seem odd that messages are used to start and stop this +// flow (as opposed to signaling objects, etc.), it should be understood that +// the native message pump will *only* respond to messages. As a result, it is +// an excellent choice. It is also helpful that the starter messages that are +// placed in the queue when new task arrive also awakens DoRunLoop. +// +class BASE_EXPORT MessagePumpForUI : public MessagePumpWin { + public: + // A MessageFilter implements the common Peek/Translate/Dispatch code to deal + // with windows messages. + // This abstraction is used to inject TSF message peeking. See + // TextServicesMessageFilter. + class BASE_EXPORT MessageFilter { + public: + virtual ~MessageFilter() {} + // Implements the functionality exposed by the OS through PeekMessage. + virtual BOOL DoPeekMessage(MSG* msg, + HWND window_handle, + UINT msg_filter_min, + UINT msg_filter_max, + UINT remove_msg) { + return PeekMessage(msg, window_handle, msg_filter_min, msg_filter_max, + remove_msg); + } + // Returns true if |message| was consumed by the filter and no extra + // processing is required. If this method returns false, it is the + // responsibility of the caller to ensure that normal processing takes + // place. + // The priority to consume messages is the following: + // - Native Windows' message filter (CallMsgFilter). + // - MessageFilter::ProcessMessage. + // - MessagePumpDispatcher. + // - TranslateMessage / DispatchMessage. + virtual bool ProcessMessage(const MSG& msg) { return false;} + }; + // The application-defined code passed to the hook procedure. + static const int kMessageFilterCode = 0x5001; + + MessagePumpForUI(); + virtual ~MessagePumpForUI(); + + // Sets a new MessageFilter. MessagePumpForUI takes ownership of + // |message_filter|. When SetMessageFilter is called, old MessageFilter is + // deleted. + void SetMessageFilter(scoped_ptr<MessageFilter> message_filter); + + // MessagePump methods: + 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, + WPARAM wparam, + LPARAM lparam); + virtual void DoRunLoop(); + void InitMessageWnd(); + void WaitForWork(); + void HandleWorkMessage(); + void HandleTimerMessage(); + bool ProcessNextWindowsMessage(); + bool ProcessMessageHelper(const MSG& msg); + bool ProcessPumpReplacementMessage(); + + // Instance of the module containing the window procedure. + HMODULE instance_; + + // A hidden message-only window. + HWND message_hwnd_; + + scoped_ptr<MessageFilter> message_filter_; +}; + +//----------------------------------------------------------------------------- +// MessagePumpForIO extends MessagePumpWin with methods that are particular to a +// MessageLoop instantiated with TYPE_IO. This version of MessagePump does not +// deal with Windows mesagges, and instead has a Run loop based on Completion +// Ports so it is better suited for IO operations. +// +class BASE_EXPORT MessagePumpForIO : public MessagePumpWin { + public: + struct IOContext; + + // Clients interested in receiving OS notifications when asynchronous IO + // operations complete should implement this interface and register themselves + // with the message pump. + // + // Typical use #1: + // // Use only when there are no user's buffers involved on the actual IO, + // // so that all the cleanup can be done by the message pump. + // class MyFile : public IOHandler { + // MyFile() { + // ... + // context_ = new IOContext; + // context_->handler = this; + // message_pump->RegisterIOHandler(file_, this); + // } + // ~MyFile() { + // if (pending_) { + // // By setting the handler to NULL, we're asking for this context + // // to be deleted when received, without calling back to us. + // context_->handler = NULL; + // } else { + // delete context_; + // } + // } + // virtual void OnIOCompleted(IOContext* context, DWORD bytes_transfered, + // DWORD error) { + // pending_ = false; + // } + // void DoSomeIo() { + // ... + // // The only buffer required for this operation is the overlapped + // // structure. + // ConnectNamedPipe(file_, &context_->overlapped); + // pending_ = true; + // } + // bool pending_; + // IOContext* context_; + // HANDLE file_; + // }; + // + // Typical use #2: + // class MyFile : public IOHandler { + // MyFile() { + // ... + // message_pump->RegisterIOHandler(file_, this); + // } + // // Plus some code to make sure that this destructor is not called + // // while there are pending IO operations. + // ~MyFile() { + // } + // virtual void OnIOCompleted(IOContext* context, DWORD bytes_transfered, + // DWORD error) { + // ... + // delete context; + // } + // void DoSomeIo() { + // ... + // IOContext* context = new IOContext; + // // This is not used for anything. It just prevents the context from + // // being considered "abandoned". + // context->handler = this; + // ReadFile(file_, buffer, num_bytes, &read, &context->overlapped); + // } + // HANDLE file_; + // }; + // + // Typical use #3: + // Same as the previous example, except that in order to deal with the + // requirement stated for the destructor, the class calls WaitForIOCompletion + // from the destructor to block until all IO finishes. + // ~MyFile() { + // while(pending_) + // message_pump->WaitForIOCompletion(INFINITE, this); + // } + // + class IOHandler { + public: + virtual ~IOHandler() {} + // This will be called once the pending IO operation associated with + // |context| completes. |error| is the Win32 error code of the IO operation + // (ERROR_SUCCESS if there was no error). |bytes_transfered| will be zero + // on error. + virtual void OnIOCompleted(IOContext* context, DWORD bytes_transfered, + DWORD error) = 0; + }; + + // An IOObserver is an object that receives IO notifications from the + // MessagePump. + // + // NOTE: An IOObserver implementation should be extremely fast! + class IOObserver { + public: + IOObserver() {} + + virtual void WillProcessIOEvent() = 0; + virtual void DidProcessIOEvent() = 0; + + protected: + virtual ~IOObserver() {} + }; + + // The extended context that should be used as the base structure on every + // overlapped IO operation. |handler| must be set to the registered IOHandler + // for the given file when the operation is started, and it can be set to NULL + // before the operation completes to indicate that the handler should not be + // called anymore, and instead, the IOContext should be deleted when the OS + // notifies the completion of this operation. Please remember that any buffers + // involved with an IO operation should be around until the callback is + // received, so this technique can only be used for IO that do not involve + // additional buffers (other than the overlapped structure itself). + struct IOContext { + OVERLAPPED overlapped; + IOHandler* handler; + }; + + MessagePumpForIO(); + virtual ~MessagePumpForIO() {} + + // MessagePump methods: + virtual void ScheduleWork(); + virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time); + + // Register the handler to be used when asynchronous IO for the given file + // completes. The registration persists as long as |file_handle| is valid, so + // |handler| must be valid as long as there is pending IO for the given file. + void RegisterIOHandler(HANDLE file_handle, IOHandler* handler); + + // Register the handler to be used to process job events. The registration + // persists as long as the job object is live, so |handler| must be valid + // until the job object is destroyed. Returns true if the registration + // succeeded, and false otherwise. + bool RegisterJobObject(HANDLE job_handle, IOHandler* handler); + + // Waits for the next IO completion that should be processed by |filter|, for + // up to |timeout| milliseconds. Return true if any IO operation completed, + // regardless of the involved handler, and false if the timeout expired. If + // the completion port received any message and the involved IO handler + // matches |filter|, the callback is called before returning from this code; + // if the handler is not the one that we are looking for, the callback will + // be postponed for another time, so reentrancy problems can be avoided. + // External use of this method should be reserved for the rare case when the + // caller is willing to allow pausing regular task dispatching on this thread. + bool WaitForIOCompletion(DWORD timeout, IOHandler* filter); + + void AddIOObserver(IOObserver* obs); + void RemoveIOObserver(IOObserver* obs); + + private: + struct IOItem { + IOHandler* handler; + IOContext* context; + DWORD bytes_transfered; + DWORD error; + + // In some cases |context| can be a non-pointer value casted to a pointer. + // |has_valid_io_context| is true if |context| is a valid IOContext + // pointer, and false otherwise. + bool has_valid_io_context; + }; + + virtual void DoRunLoop(); + void WaitForWork(); + bool MatchCompletedIOItem(IOHandler* filter, IOItem* item); + bool GetIOItem(DWORD timeout, IOItem* item); + bool ProcessInternalIOItem(const IOItem& item); + void WillProcessIOEvent(); + void DidProcessIOEvent(); + + // Converts an IOHandler pointer to a completion port key. + // |has_valid_io_context| specifies whether completion packets posted to + // |handler| will have valid OVERLAPPED pointers. + static ULONG_PTR HandlerToKey(IOHandler* handler, bool has_valid_io_context); + + // Converts a completion port key to an IOHandler pointer. + static IOHandler* KeyToHandler(ULONG_PTR key, bool* has_valid_io_context); + + // The completion port associated with this thread. + win::ScopedHandle port_; + // This list will be empty almost always. It stores IO completions that have + // not been delivered yet because somebody was doing cleanup. + std::list<IOItem> completed_io_; + + ObserverList<IOObserver> io_observers_; +}; + +} // namespace base + +#endif // BASE_MESSAGE_PUMP_WIN_H_
diff --git a/src/base/metrics/bucket_ranges.cc b/src/base/metrics/bucket_ranges.cc new file mode 100644 index 0000000..01a5569 --- /dev/null +++ b/src/base/metrics/bucket_ranges.cc
@@ -0,0 +1,143 @@ +// 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/metrics/bucket_ranges.h" + +#include <cmath> + +#include "base/logging.h" + +namespace base { + +// Static table of checksums for all possible 8 bit bytes. +const uint32 kCrcTable[256] = { 0x0, 0x77073096L, 0xee0e612cL, +0x990951baL, 0x76dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0xedb8832L, +0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x9b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, +0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, +0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, +0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, +0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, +0xa50ab56bL, 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, +0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, 0xc8d75180L, +0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, 0x2802b89eL, +0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, +0xb6662d3dL, 0x76dc4190L, 0x1db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, +0x6b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0xf00f934L, 0x9609a88eL, +0xe10e9818L, 0x7f6a0dbbL, 0x86d3d2dL, 0x91646c97L, 0xe6635c01L, 0x6b6b51f4L, +0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, +0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, +0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, +0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, +0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, +0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, +0x206f85b3L, 0xb966d409L, 0xce61e49fL, 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, +0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, +0x9abfb3b6L, 0x3b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x4db2615L, +0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0xd6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, +0x9309ff9dL, 0xa00ae27L, 0x7d079eb1L, 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, +0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, +0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, +0x60b08ed5L, 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, +0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, +0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, 0xcb61b38cL, +0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, +0x5505262fL, 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, +0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, +0x26d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x5005713L, 0x95bf4a82L, +0xe2b87a14L, 0x7bb12baeL, 0xcb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, +0xbdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, +0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, +0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, +0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, +0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, +0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, +0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, +0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, +0x2d02ef8dL, +}; + +// We generate the CRC-32 using the low order bits to select whether to XOR in +// the reversed polynomial 0xedb88320L. This is nice and simple, and allows us +// to keep the quotient in a uint32. Since we're not concerned about the nature +// of corruptions (i.e., we don't care about bit sequencing, since we are +// handling memory changes, which are more grotesque) so we don't bother to +// get the CRC correct for big-endian vs little-ending calculations. All we +// need is a nice hash, that tends to depend on all the bits of the sample, with +// very little chance of changes in one place impacting changes in another +// place. +static uint32 Crc32(uint32 sum, HistogramBase::Sample value) { + // TODO(jar): Switch to false and watch stats. + const bool kUseRealCrc = true; + + if (kUseRealCrc) { + union { + HistogramBase::Sample range; + unsigned char bytes[sizeof(HistogramBase::Sample)]; + } converter; + converter.range = value; + for (size_t i = 0; i < sizeof(converter); ++i) +#if defined(ARCH_CPU_LITTLE_ENDIAN) + sum = kCrcTable[(sum & 0xff) ^ converter.bytes[i]] ^ (sum >> 8); +#else + sum = kCrcTable[(sum & 0xff) ^ converter.bytes[sizeof(converter) - i - 1]] ^ (sum >> 8); +#endif + } else { + // Use hash techniques provided in ReallyFastHash, except we don't care + // about "avalanching" (which would worsten the hash, and add collisions), + // and we don't care about edge cases since we have an even number of bytes. + union { + HistogramBase::Sample range; + uint16 ints[sizeof(HistogramBase::Sample) / 2]; + } converter; + DCHECK_EQ(sizeof(HistogramBase::Sample), sizeof(converter)); + converter.range = value; + sum += converter.ints[0]; + sum = (sum << 16) ^ sum ^ (static_cast<uint32>(converter.ints[1]) << 11); + sum += sum >> 11; + } + return sum; +} + +BucketRanges::BucketRanges(size_t num_ranges) + : ranges_(num_ranges, 0), + checksum_(0) {} + +BucketRanges::~BucketRanges() {} + +void BucketRanges::set_range(size_t i, HistogramBase::Sample value) { + DCHECK_LT(i, ranges_.size()); + CHECK_GE(value, 0); + ranges_[i] = value; +} + +uint32 BucketRanges::CalculateChecksum() const { + // Seed checksum. + uint32 checksum = static_cast<uint32>(ranges_.size()); + + for (size_t index = 0; index < ranges_.size(); ++index) + checksum = Crc32(checksum, ranges_[index]); + return checksum; +} + +bool BucketRanges::HasValidChecksum() const { + return CalculateChecksum() == checksum_; +} + +void BucketRanges::ResetChecksum() { + checksum_ = CalculateChecksum(); +} + +bool BucketRanges::Equals(const BucketRanges* other) const { + if (checksum_ != other->checksum_) + return false; + if (ranges_.size() != other->ranges_.size()) + return false; + for (size_t index = 0; index < ranges_.size(); ++index) { + if (ranges_[index] != other->ranges_[index]) + return false; + } + return true; +} + +} // namespace base
diff --git a/src/base/metrics/bucket_ranges.h b/src/base/metrics/bucket_ranges.h new file mode 100644 index 0000000..90b9047 --- /dev/null +++ b/src/base/metrics/bucket_ranges.h
@@ -0,0 +1,73 @@ +// 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. +// +// BucketRanges stores the vector of ranges that delimit what samples are +// tallied in the corresponding buckets of a histogram. Histograms that have +// same ranges for all their corresponding buckets should share the same +// BucketRanges object. +// +// E.g. A 5 buckets LinearHistogram with 1 as minimal value and 4 as maximal +// value will need a BucketRanges with 6 ranges: +// 0, 1, 2, 3, 4, INT_MAX +// +// TODO(kaiwang): Currently we keep all negative values in 0~1 bucket. Consider +// changing 0 to INT_MIN. + +#ifndef BASE_METRICS_BUCKET_RANGES_H_ +#define BASE_METRICS_BUCKET_RANGES_H_ + +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/metrics/histogram_base.h" + +namespace base { + +class BASE_EXPORT BucketRanges { + public: + typedef std::vector<HistogramBase::Sample> Ranges; + + BucketRanges(size_t num_ranges); + ~BucketRanges(); + + size_t size() const { return ranges_.size(); } + HistogramBase::Sample range(size_t i) const { return ranges_[i]; } + void set_range(size_t i, HistogramBase::Sample value); + uint32 checksum() const { return checksum_; } + void set_checksum(uint32 checksum) { checksum_ = checksum; } + + // Checksum methods to verify whether the ranges are corrupted (e.g. bad + // memory access). + uint32 CalculateChecksum() const; + bool HasValidChecksum() const; + void ResetChecksum(); + + // Return true iff |other| object has same ranges_ as |this| object's ranges_. + bool Equals(const BucketRanges* other) const; + + private: + // A monotonically increasing list of values which determine which bucket to + // put a sample into. For each index, show the smallest sample that can be + // added to the corresponding bucket. + Ranges ranges_; + + // Checksum for the conntents of ranges_. Used to detect random over-writes + // of our data, and to quickly see if some other BucketRanges instance is + // possibly Equal() to this instance. + // TODO(kaiwang): Consider change this to uint64. Because we see a lot of + // noise on UMA dashboard. + uint32 checksum_; + + DISALLOW_COPY_AND_ASSIGN(BucketRanges); +}; + +////////////////////////////////////////////////////////////////////////////// +// Expose only for test. +BASE_EXPORT_PRIVATE extern const uint32 kCrcTable[256]; + +} // namespace base + +#endif // BASE_METRICS_BUCKET_RANGES_H_
diff --git a/src/base/metrics/bucket_ranges_unittest.cc b/src/base/metrics/bucket_ranges_unittest.cc new file mode 100644 index 0000000..424ff63 --- /dev/null +++ b/src/base/metrics/bucket_ranges_unittest.cc
@@ -0,0 +1,91 @@ +// 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/metrics/bucket_ranges.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +TEST(BucketRangesTest, NormalSetup) { + BucketRanges ranges(5); + ASSERT_EQ(5u, ranges.size()); + + for (int i = 0; i < 5; i++) { + EXPECT_EQ(0, ranges.range(i)); + } + EXPECT_EQ(0u, ranges.checksum()); + + ranges.set_range(3, 100); + EXPECT_EQ(100, ranges.range(3)); +} + +TEST(BucketRangesTest, Equals) { + // Compare empty ranges. + BucketRanges ranges1(3); + BucketRanges ranges2(3); + BucketRanges ranges3(5); + + EXPECT_TRUE(ranges1.Equals(&ranges2)); + EXPECT_FALSE(ranges1.Equals(&ranges3)); + EXPECT_FALSE(ranges2.Equals(&ranges3)); + + // Compare full filled ranges. + ranges1.set_range(0, 0); + ranges1.set_range(1, 1); + ranges1.set_range(2, 2); + ranges1.set_checksum(100); + ranges2.set_range(0, 0); + ranges2.set_range(1, 1); + ranges2.set_range(2, 2); + ranges2.set_checksum(100); + + EXPECT_TRUE(ranges1.Equals(&ranges2)); + + // Checksum does not match. + ranges1.set_checksum(99); + EXPECT_FALSE(ranges1.Equals(&ranges2)); + ranges1.set_checksum(100); + + // Range does not match. + ranges1.set_range(1, 3); + EXPECT_FALSE(ranges1.Equals(&ranges2)); +} + +TEST(BucketRangesTest, Checksum) { + BucketRanges ranges(3); + ranges.set_range(0, 0); + ranges.set_range(1, 1); + ranges.set_range(2, 2); + + ranges.ResetChecksum(); + EXPECT_EQ(289217253u, ranges.checksum()); + + ranges.set_range(2, 3); + EXPECT_FALSE(ranges.HasValidChecksum()); + + ranges.ResetChecksum(); + EXPECT_EQ(2843835776u, ranges.checksum()); + EXPECT_TRUE(ranges.HasValidChecksum()); +} + +// Table was generated similarly to sample code for CRC-32 given on: +// http://www.w3.org/TR/PNG/#D-CRCAppendix. +TEST(BucketRangesTest, Crc32TableTest) { + for (int i = 0; i < 256; ++i) { + uint32 checksum = i; + for (int j = 0; j < 8; ++j) { + const uint32 kReversedPolynomial = 0xedb88320L; + if (checksum & 1) + checksum = kReversedPolynomial ^ (checksum >> 1); + else + checksum >>= 1; + } + EXPECT_EQ(kCrcTable[i], checksum); + } +} + +} // namespace +} // namespace base
diff --git a/src/base/metrics/field_trial.cc b/src/base/metrics/field_trial.cc new file mode 100644 index 0000000..14e16ea --- /dev/null +++ b/src/base/metrics/field_trial.cc
@@ -0,0 +1,495 @@ +// 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/metrics/field_trial.h" + +#include "base/build_time.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/rand_util.h" +#include "base/sha1.h" +#include "base/stringprintf.h" +#include "base/string_util.h" +#include "base/sys_byteorder.h" +#include "base/utf_string_conversions.h" + +namespace base { + +namespace { + +// Created a time value based on |year|, |month| and |day_of_month| parameters. +Time CreateTimeFromParams(int year, int month, int day_of_month) { + DCHECK_GT(year, 1970); + DCHECK_GT(month, 0); + DCHECK_LT(month, 13); + DCHECK_GT(day_of_month, 0); + DCHECK_LT(day_of_month, 32); + + Time::Exploded exploded; + exploded.year = year; + exploded.month = month; + exploded.day_of_week = 0; // Should be unused. + exploded.day_of_month = day_of_month; + exploded.hour = 0; + exploded.minute = 0; + exploded.second = 0; + exploded.millisecond = 0; + + return Time::FromLocalExploded(exploded); +} + +} // namespace + +static const char kHistogramFieldTrialSeparator('_'); + +// statics +const int FieldTrial::kNotFinalized = -1; +const int FieldTrial::kDefaultGroupNumber = 0; +bool FieldTrial::enable_benchmarking_ = false; + +const char FieldTrialList::kPersistentStringSeparator('/'); +int FieldTrialList::kExpirationYearInFuture = 0; + +//------------------------------------------------------------------------------ +// FieldTrial methods and members. + +FieldTrial::FieldTrial(const std::string& trial_name, + const Probability total_probability, + const std::string& default_group_name) + : trial_name_(trial_name), + divisor_(total_probability), + default_group_name_(default_group_name), + random_(static_cast<Probability>(divisor_ * RandDouble())), + 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() { +} + +void FieldTrial::UseOneTimeRandomization() { + // No need to specify randomization when the group choice was forced. + if (forced_) + return; + DCHECK_EQ(group_, kNotFinalized); + DCHECK_EQ(kDefaultGroupNumber + 1, next_group_number_); + const EntropyProvider* entropy_provider = + FieldTrialList::GetEntropyProviderForOneTimeRandomization(); + if (!entropy_provider) { + NOTREACHED(); + // TODO(stevet): Remove this temporary histogram when logging + // investigations are complete. + UMA_HISTOGRAM_BOOLEAN("Variations.DisabledNoEntropyProvider", true); + Disable(); + return; + } + + random_ = static_cast<Probability>( + divisor_ * entropy_provider->GetEntropyForTrial(trial_name_)); +} + +void FieldTrial::Disable() { + DCHECK(!group_reported_); + enable_field_trial_ = false; + + // In case we are disabled after initialization, we need to switch + // the trial to the default group. + if (group_ != kNotFinalized) { + // Only reset when not already the default group, because in case we were + // forced to the default group, the group number may not be + // kDefaultGroupNumber, so we should keep it as is. + if (group_name_ != default_group_name_) + SetGroupChoice(default_group_name_, kDefaultGroupNumber); + } +} + +int FieldTrial::AppendGroup(const std::string& name, + Probability group_probability) { + // When the group choice was previously forced, we only need to return the + // the id of the chosen group, and anything can be returned for the others. + if (forced_) { + DCHECK(!group_name_.empty()); + if (name == group_name_) { + return group_; + } + DCHECK_NE(next_group_number_, group_); + // We still return different numbers each time, in case some caller need + // them to be different. + return next_group_number_++; + } + + DCHECK_LE(group_probability, divisor_); + DCHECK_GE(group_probability, 0); + + if (enable_benchmarking_ || !enable_field_trial_) + group_probability = 0; + + accumulated_group_probability_ += group_probability; + + DCHECK_LE(accumulated_group_probability_, divisor_); + if (group_ == kNotFinalized && accumulated_group_probability_ > random_) { + // This is the group that crossed the random line, so we do the assignment. + SetGroupChoice(name, next_group_number_); + } + return next_group_number_++; +} + +int FieldTrial::group() { + FinalizeGroupChoice(); + FieldTrialList::NotifyFieldTrialGroupSelection(this); + return group_; +} + +const std::string& FieldTrial::group_name() { + // Call |group()| to ensure group gets assigned and observers are notified. + group(); + DCHECK(!group_name_.empty()); + return group_name_; +} + +// static +std::string FieldTrial::MakeName(const std::string& name_prefix, + const std::string& trial_name) { + std::string big_string(name_prefix); + big_string.append(1, kHistogramFieldTrialSeparator); + return big_string.append(FieldTrialList::FindFullName(trial_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. + if (forced_) + return; + + // And we must finalize the group choice before we mark ourselves as forced. + FinalizeGroupChoice(); + forced_ = true; +} + +FieldTrial::~FieldTrial() {} + +void FieldTrial::SetGroupChoice(const std::string& group_name, int number) { + group_ = number; + if (group_name.empty()) + StringAppendF(&group_name_, "%d", group_); + else + group_name_ = group_name; + DVLOG(1) << "Field trial: " << trial_name_ << " Group choice:" << group_name_; +} + +void FieldTrial::FinalizeGroupChoice() { + if (group_ != kNotFinalized) + return; + accumulated_group_probability_ = divisor_; + // Here it's OK to use |kDefaultGroupNumber| since we can't be forced and not + // finalized. + DCHECK(!forced_); + SetGroupChoice(default_group_name_, kDefaultGroupNumber); +} + +bool FieldTrial::GetActiveGroup(ActiveGroup* active_group) const { + if (!group_reported_ || !enable_field_trial_) { + // TODO(asvitkine): Temporary histogram. Remove this once it is not needed. + if (trial_name_ == "UMA-Uniformity-Trial-1-Percent") { + const int kGroupNotReported = 1; + const int kTrialDisabled = 2; + int value = 0; + if (!group_reported_) + value |= kGroupNotReported; + if (!enable_field_trial_) + value |= kTrialDisabled; + UMA_HISTOGRAM_ENUMERATION("Variations.UniformityTrialGroupNotActive", + value, 4); + } + return false; + } + DCHECK_NE(group_, kNotFinalized); + active_group->trial_name = trial_name_; + active_group->group_name = group_name_; + return true; +} + +//------------------------------------------------------------------------------ +// FieldTrialList methods and members. + +// static +FieldTrialList* FieldTrialList::global_ = NULL; + +// static +bool FieldTrialList::used_without_global_ = false; + +FieldTrialList::Observer::~Observer() { +} + +FieldTrialList::FieldTrialList( + const FieldTrial::EntropyProvider* entropy_provider) + : entropy_provider_(entropy_provider), + observer_list_(new ObserverListThreadSafe<FieldTrialList::Observer>( + ObserverListBase<FieldTrialList::Observer>::NOTIFY_EXISTING_ONLY)) { + DCHECK(!global_); + DCHECK(!used_without_global_); + global_ = this; + + Time::Exploded exploded; + Time two_years_from_now = + Time::NowFromSystemTime() + TimeDelta::FromDays(730); + two_years_from_now.LocalExplode(&exploded); + kExpirationYearInFuture = exploded.year; +} + +FieldTrialList::~FieldTrialList() { + AutoLock auto_lock(lock_); + while (!registered_.empty()) { + RegistrationList::iterator it = registered_.begin(); + it->second->Release(); + registered_.erase(it->first); + } + DCHECK_EQ(this, global_); + global_ = NULL; +} + +// static +FieldTrial* FieldTrialList::FactoryGetFieldTrial( + const std::string& name, + FieldTrial::Probability total_probability, + const std::string& default_group_name, + const int year, + const int month, + const int day_of_month, + int* default_group_number) { + if (default_group_number) + *default_group_number = FieldTrial::kDefaultGroupNumber; + // Check if the field trial has already been created in some other way. + FieldTrial* existing_trial = Find(name); + if (existing_trial) { + CHECK(existing_trial->forced_); + // If the field trial has already been forced, check whether it was forced + // to the default group. Return the chosen group number, in that case.. + if (default_group_number && + default_group_name == existing_trial->default_group_name()) { + *default_group_number = existing_trial->group(); + } + return existing_trial; + } + + FieldTrial* field_trial = + new FieldTrial(name, total_probability, default_group_name); + if (GetBuildTime() > CreateTimeFromParams(year, month, day_of_month)) { + // TODO(asvitkine): Temporary histogram. Remove this once it is not needed. + if (name == "UMA-Uniformity-Trial-1-Percent") + UMA_HISTOGRAM_BOOLEAN("Variations.UniformityTrialExpired", true); + field_trial->Disable(); + } + FieldTrialList::Register(field_trial); + return field_trial; +} + +// static +FieldTrial* FieldTrialList::Find(const std::string& name) { + if (!global_) + return NULL; + AutoLock auto_lock(global_->lock_); + return global_->PreLockedFind(name); +} + +// static +int FieldTrialList::FindValue(const std::string& name) { + FieldTrial* field_trial = Find(name); + if (field_trial) + return field_trial->group(); + return FieldTrial::kNotFinalized; +} + +// static +std::string FieldTrialList::FindFullName(const std::string& name) { + FieldTrial* field_trial = Find(name); + if (field_trial) + return field_trial->group_name(); + return ""; +} + +// static +bool FieldTrialList::TrialExists(const std::string& name) { + return Find(name) != NULL; +} + +// static +void FieldTrialList::StatesToString(std::string* output) { + FieldTrial::ActiveGroups active_groups; + GetActiveFieldTrialGroups(&active_groups); + for (FieldTrial::ActiveGroups::const_iterator it = active_groups.begin(); + it != active_groups.end(); ++it) { + DCHECK_EQ(std::string::npos, + it->trial_name.find(kPersistentStringSeparator)); + DCHECK_EQ(std::string::npos, + it->group_name.find(kPersistentStringSeparator)); + output->append(it->trial_name); + output->append(1, kPersistentStringSeparator); + output->append(it->group_name); + output->append(1, kPersistentStringSeparator); + } +} + +// static +void FieldTrialList::GetActiveFieldTrialGroups( + FieldTrial::ActiveGroups* active_groups) { + DCHECK(active_groups->empty()); + if (!global_) + return; + AutoLock auto_lock(global_->lock_); + + for (RegistrationList::iterator it = global_->registered_.begin(); + it != global_->registered_.end(); ++it) { + FieldTrial::ActiveGroup active_group; + if (it->second->GetActiveGroup(&active_group)) + active_groups->push_back(active_group); + } +} + +// static +bool FieldTrialList::CreateTrialsFromString(const std::string& trials_string) { + DCHECK(global_); + if (trials_string.empty() || !global_) + return true; + + size_t next_item = 0; + while (next_item < trials_string.length()) { + size_t name_end = trials_string.find(kPersistentStringSeparator, next_item); + if (name_end == trials_string.npos || next_item == name_end) + return false; + size_t group_name_end = trials_string.find(kPersistentStringSeparator, + name_end + 1); + if (group_name_end == trials_string.npos || name_end + 1 == group_name_end) + return false; + std::string name(trials_string, next_item, name_end - next_item); + std::string group_name(trials_string, name_end + 1, + group_name_end - name_end - 1); + next_item = group_name_end + 1; + + FieldTrial* trial = CreateFieldTrial(name, group_name); + if (!trial) + return false; + // Call |group()| to mark the trial as "used" and notify observers, if any. + // This is needed to ensure the trial is properly reported in child process + // crash reports. + trial->group(); + } + return true; +} + +// static +FieldTrial* FieldTrialList::CreateFieldTrial( + const std::string& name, + const std::string& group_name) { + DCHECK(global_); + DCHECK_GE(name.size(), 0u); + DCHECK_GE(group_name.size(), 0u); + if (name.empty() || group_name.empty() || !global_) + return NULL; + + FieldTrial* field_trial = FieldTrialList::Find(name); + if (field_trial) { + // In single process mode, or when we force them from the command line, + // we may have already created the field trial. + if (field_trial->group_name_internal() != group_name) + return NULL; + return field_trial; + } + const int kTotalProbability = 100; + field_trial = new FieldTrial(name, kTotalProbability, group_name); + // This is where we may assign a group number different from + // kDefaultGroupNumber to the default group. + field_trial->AppendGroup(group_name, kTotalProbability); + field_trial->forced_ = true; + FieldTrialList::Register(field_trial); + return field_trial; +} + +// static +void FieldTrialList::AddObserver(Observer* observer) { + if (!global_) + return; + global_->observer_list_->AddObserver(observer); +} + +// static +void FieldTrialList::RemoveObserver(Observer* observer) { + if (!global_) + return; + global_->observer_list_->RemoveObserver(observer); +} + +// static +void FieldTrialList::NotifyFieldTrialGroupSelection(FieldTrial* field_trial) { + if (!global_) + return; + + { + AutoLock auto_lock(global_->lock_); + if (field_trial->group_reported_) + return; + field_trial->group_reported_ = true; + } + + if (!field_trial->enable_field_trial_) + return; + + global_->observer_list_->Notify( + &FieldTrialList::Observer::OnFieldTrialGroupFinalized, + field_trial->trial_name(), + field_trial->group_name_internal()); +} + +// static +size_t FieldTrialList::GetFieldTrialCount() { + if (!global_) + return 0; + AutoLock auto_lock(global_->lock_); + return global_->registered_.size(); +} + +// static +const FieldTrial::EntropyProvider* + FieldTrialList::GetEntropyProviderForOneTimeRandomization() { + if (!global_) { + used_without_global_ = true; + return NULL; + } + + return global_->entropy_provider_.get(); +} + +FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { + RegistrationList::iterator it = registered_.find(name); + if (registered_.end() == it) + return NULL; + return it->second; +} + +// static +void FieldTrialList::Register(FieldTrial* trial) { + if (!global_) { + used_without_global_ = true; + return; + } + AutoLock auto_lock(global_->lock_); + DCHECK(!global_->PreLockedFind(trial->trial_name())); + trial->AddRef(); + global_->registered_[trial->trial_name()] = trial; +} + +} // namespace base
diff --git a/src/base/metrics/field_trial.h b/src/base/metrics/field_trial.h new file mode 100644 index 0000000..007c1da --- /dev/null +++ b/src/base/metrics/field_trial.h
@@ -0,0 +1,444 @@ +// 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. + +// FieldTrial is a class for handling details of statistical experiments +// performed by actual users in the field (i.e., in a shipped or beta product). +// All code is called exclusively on the UI thread currently. +// +// The simplest example is an experiment to see whether one of two options +// produces "better" results across our user population. In that scenario, UMA +// data is uploaded to aggregate the test results, and this FieldTrial class +// manages the state of each such experiment (state == which option was +// pseudo-randomly selected). +// +// States are typically generated randomly, either based on a one time +// randomization (which will yield the same results, in terms of selecting +// the client for a field trial or not, for every run of the program on a +// given machine), or by a startup randomization (generated each time the +// application starts up, but held constant during the duration of the +// process), or by continuous randomization across a run (where the state +// can be recalculated again and again, many times during a process). +// Continuous randomization is not yet implemented. + +//------------------------------------------------------------------------------ +// Example: Suppose we have an experiment involving memory, such as determining +// the impact of some pruning algorithm. +// We assume that we already have a histogram of memory usage, such as: + +// HISTOGRAM_COUNTS("Memory.RendererTotal", count); + +// Somewhere in main thread initialization code, we'd probably define an +// instance of a FieldTrial, with code such as: + +// // FieldTrials are reference counted, and persist automagically until +// // process teardown, courtesy of their automatic registration in +// // FieldTrialList. +// // Note: This field trial will run in Chrome instances compiled through +// // 8 July, 2015, and after that all instances will be in "StandardMem". +// scoped_refptr<base::FieldTrial> trial( +// base::FieldTrialList::FactoryGetFieldTrial("MemoryExperiment", 1000, +// "StandardMem", 2015, 7, 8, +// NULL)); +// const int high_mem_group = +// trial->AppendGroup("HighMem", 20); // 2% in HighMem group. +// const int low_mem_group = +// trial->AppendGroup("LowMem", 20); // 2% in LowMem group. +// // Take action depending of which group we randomly land in. +// if (trial->group() == high_mem_group) +// SetPruningAlgorithm(kType1); // Sample setting of browser state. +// else if (trial->group() == low_mem_group) +// SetPruningAlgorithm(kType2); // Sample alternate setting. + +// We then, in addition to our original histogram, output histograms which have +// slightly different names depending on what group the trial instance happened +// to randomly be assigned: + +// HISTOGRAM_COUNTS("Memory.RendererTotal", count); // The original histogram. +// static const bool memory_renderer_total_trial_exists = +// FieldTrialList::TrialExists("MemoryExperiment"); +// if (memory_renderer_total_trial_exists) { +// HISTOGRAM_COUNTS(FieldTrial::MakeName("Memory.RendererTotal", +// "MemoryExperiment"), count); +// } + +// The above code will create four distinct histograms, with each run of the +// application being assigned to of of the three groups, and for each group, the +// correspondingly named histogram will be populated: + +// Memory.RendererTotal // 100% of users still fill this histogram. +// Memory.RendererTotal_HighMem // 2% of users will fill this histogram. +// Memory.RendererTotal_LowMem // 2% of users will fill this histogram. +// Memory.RendererTotal_StandardMem // 96% of users will fill this histogram. + +//------------------------------------------------------------------------------ + +#ifndef BASE_METRICS_FIELD_TRIAL_H_ +#define BASE_METRICS_FIELD_TRIAL_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list_threadsafe.h" +#include "base/synchronization/lock.h" +#include "base/time.h" + +namespace base { + +class FieldTrialList; + +class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { + public: + typedef int Probability; // Probability type for being selected in a trial. + + // EntropyProvider is an interface for providing entropy for one-time + // randomized (persistent) field trials. + class BASE_EXPORT EntropyProvider { + public: + virtual ~EntropyProvider(); + + // Returns a double in the range of [0, 1) based on |trial_name| that will + // be used for the dice roll for the specified field trial. A given instance + // should always return the same value given the same input |trial_name|. + virtual double GetEntropyForTrial(const std::string& trial_name) const = 0; + }; + + // A pair representing a Field Trial and its selected group. + struct ActiveGroup { + std::string trial_name; + std::string group_name; + }; + + typedef std::vector<ActiveGroup> ActiveGroups; + + // A return value to indicate that a given instance has not yet had a group + // assignment (and hence is not yet participating in the trial). + static const int kNotFinalized; + + // Changes the field trial to use one-time randomization, i.e. produce the + // same result for the current trial on every run of this client. Must be + // called right after construction. + void UseOneTimeRandomization(); + + // Disables this trial, meaning it always determines the default group + // has been selected. May be called immediately after construction, or + // at any time after initialization (should not be interleaved with + // AppendGroup calls). Once disabled, there is no way to re-enable a + // trial. + // TODO(mad): http://code.google.com/p/chromium/issues/detail?id=121446 + // This doesn't properly reset to Default when a group was forced. + void Disable(); + + // Establish the name and probability of the next group in this trial. + // Sometimes, based on construction randomization, this call may cause the + // provided group to be *THE* group selected for use in this instance. + // The return value is the group number of the new group. + int AppendGroup(const std::string& name, Probability group_probability); + + // Return the name of the FieldTrial (excluding the group name). + const std::string& trial_name() const { return trial_name_; } + + // Return the randomly selected group number that was assigned, and notify + // any/all observers that this finalized group number has presumably been used + // (queried), and will never change. Note that this will force an instance to + // participate, and make it illegal to attempt to probabilistically add any + // other groups to the trial. + int group(); + + // If the group's name is empty, a string version containing the group number + // is used as the group name. This causes a winner to be chosen if none was. + const std::string& group_name(); + + // Helper function for the most common use: as an argument to specify the + // name of a HISTOGRAM. Use the original histogram name as the name_prefix. + static std::string MakeName(const std::string& name_prefix, + const std::string& trial_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 + // still succeed even though the field trial has already been registered. + // This must be called after appending all the groups, since we will make + // the group choice here. Note that this is a NOOP for already forced trials. + // And, as the rest of the FieldTrial code, this is not thread safe and must + // be done from the UI thread. + void SetForced(); + + private: + // Allow tests to access our innards for testing purposes. + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, Registration); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, AbsoluteProbabilities); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, RemainingProbability); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, FiftyFiftyProbability); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, MiddleProbabilities); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, OneWinner); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, DisableProbability); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, ActiveGroups); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, ActiveGroupsNotFinalized); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, Save); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, DuplicateRestore); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, MakeName); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, HashClientId); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, HashClientIdIsUniform); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, NameGroupIds); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, UseOneTimeRandomization); + + friend class base::FieldTrialList; + + friend class RefCounted<FieldTrial>; + + // This is the group number of the 'default' group when a choice wasn't forced + // by a call to FieldTrialList::CreateFieldTrial. It is kept private so that + // consumers don't use it by mistake in cases where the group was forced. + static const int kDefaultGroupNumber; + + FieldTrial(const std::string& name, + Probability total_probability, + const std::string& default_group_name); + virtual ~FieldTrial(); + + // Return the default group name of the FieldTrial. + std::string default_group_name() const { return default_group_name_; } + + // Sets the chosen group name and number. + void SetGroupChoice(const std::string& group_name, int number); + + // Ensures that a group is chosen, if it hasn't yet been. The field trial + // might yet be disabled, so this call will *not* notify observers of the + // status. + void FinalizeGroupChoice(); + + // Returns the trial name and selected group name for this field trial via + // the output parameter |active_group|, but only if the group has already + // been chosen and has been externally observed via |group()| and the trial + // has not been disabled. In that case, true is returned and |active_group| + // is filled in; otherwise, the result is false and |active_group| is left + // untouched. + bool GetActiveGroup(ActiveGroup* active_group) const; + + // Returns the group_name. A winner need not have been chosen. + std::string group_name_internal() const { return group_name_; } + + // The name of the field trial, as can be found via the FieldTrialList. + const std::string trial_name_; + + // The maximum sum of all probabilities supplied, which corresponds to 100%. + // This is the scaling factor used to adjust supplied probabilities. + const Probability divisor_; + + // The name of the default group. + const std::string default_group_name_; + + // The randomly selected probability that is used to select a group (or have + // the instance not participate). It is the product of divisor_ and a random + // number between [0, 1). + Probability random_; + + // Sum of the probabilities of all appended groups. + Probability accumulated_group_probability_; + + int next_group_number_; + + // The pseudo-randomly assigned group number. + // This is kNotFinalized if no group has been assigned. + int group_; + + // A textual name for the randomly selected group. Valid after |group()| + // has been called. + std::string group_name_; + + // When enable_field_trial_ is false, field trial reverts to the 'default' + // group. + bool enable_field_trial_; + + // When forced_ is true, we return the chosen group from AppendGroup when + // appropriate. + bool forced_; + + // Specifies whether the group choice has been reported to observers. + bool group_reported_; + + // When benchmarking is enabled, field trials all revert to the 'default' + // group. + static bool enable_benchmarking_; + + DISALLOW_COPY_AND_ASSIGN(FieldTrial); +}; + +//------------------------------------------------------------------------------ +// Class with a list of all active field trials. A trial is active if it has +// been registered, which includes evaluating its state based on its probaility. +// Only one instance of this class exists. +class BASE_EXPORT FieldTrialList { + public: + // Define a separator character to use when creating a persistent form of an + // instance. This is intended for use as a command line argument, passed to a + // second process to mimic our state (i.e., provide the same group name). + static const char kPersistentStringSeparator; // Currently a slash. + + // Define expiration year in future. It is initialized to two years from Now. + static int kExpirationYearInFuture; + + // Observer is notified when a FieldTrial's group is selected. + class BASE_EXPORT Observer { + public: + // Notify observers when FieldTrials's group is selected. + virtual void OnFieldTrialGroupFinalized(const std::string& trial_name, + const std::string& group_name) = 0; + + protected: + virtual ~Observer(); + }; + + // This singleton holds the global list of registered FieldTrials. + // + // To support one-time randomized field trials, specify a non-NULL + // |entropy_provider| which should be a source of uniformly distributed + // entropy values. Takes ownership of |entropy_provider|. If one time + // randomization is not desired, pass in NULL for |entropy_provider|. + explicit FieldTrialList(const FieldTrial::EntropyProvider* entropy_provider); + + // Destructor Release()'s references to all registered FieldTrial instances. + ~FieldTrialList(); + + // Get a FieldTrial instance from the factory. + // + // |name| is used to register the instance with the FieldTrialList class, + // and can be used to find the trial (only one trial can be present for each + // name). |default_group_name| is the name of the default group which will + // be chosen if none of the subsequent appended groups get to be chosen. + // |default_group_number| can receive the group number of the default group as + // AppendGroup returns the number of the subsequence groups. |trial_name| and + // |default_group_name| may not be empty but |default_group_number| can be + // NULL if the value is not needed. + // + // Group probabilities that are later supplied must sum to less than or equal + // to the |total_probability|. Arguments |year|, |month| and |day_of_month| + // specify the expiration time. If the build time is after the expiration time + // then the field trial reverts to the 'default' group. + // + // Use this static method to get a startup-randomized FieldTrial or a + // previously created forced FieldTrial. If you want a one-time randomized + // trial, call UseOneTimeRandomization() right after creation. + static FieldTrial* FactoryGetFieldTrial( + const std::string& trial_name, + FieldTrial::Probability total_probability, + const std::string& default_group_name, + const int year, + const int month, + const int day_of_month, + int* default_group_number); + + // The Find() method can be used to test to see if a named Trial was already + // registered, or to retrieve a pointer to it from the global map. + static FieldTrial* Find(const std::string& name); + + // Returns the group number chosen for the named trial, or + // FieldTrial::kNotFinalized if the trial does not exist. + static int FindValue(const std::string& name); + + // Returns the group name chosen for the named trial, or the + // empty string if the trial does not exist. + static std::string FindFullName(const std::string& name); + + // Returns true if the named trial has been registered. + static bool TrialExists(const std::string& name); + + // Creates a persistent representation of active FieldTrial instances for + // resurrection in another process. This allows randomization to be done in + // one process, and secondary processes can be synchronized on the result. + // The resulting string contains the name and group name pairs of all + // registered FieldTrials for which the group has been chosen and externally + // observed (via |group()|) and which have not been disabled, with "/" used + // to separate all names and to terminate the string. This string is parsed + // by |CreateTrialsFromString()|. + static void StatesToString(std::string* output); + + // Fills in the supplied vector |active_groups| (which must be empty when + // called) with a snapshot of all registered FieldTrials for which the group + // has been chosen and externally observed (via |group()|) and which have + // not been disabled. + static void GetActiveFieldTrialGroups( + FieldTrial::ActiveGroups* active_groups); + + // Use a state string (re: StatesToString()) to augment the current list of + // field trials to include the supplied trials, and using a 100% probability + // for each trial, force them to have the same group string. This is commonly + // used in a non-browser process, to carry randomly selected state in a + // browser process into this non-browser process, but could also be invoked + // through a command line argument to the browser process. The created field + // trials are marked as "used" for the purposes of active trial reporting. + static bool CreateTrialsFromString(const std::string& prior_trials); + + // Create a FieldTrial with the given |name| and using 100% probability for + // the FieldTrial, force FieldTrial to have the same group string as + // |group_name|. This is commonly used in a non-browser process, to carry + // randomly selected state in a browser process into this non-browser process. + // It returns NULL if there is a FieldTrial that is already registered with + // the same |name| but has different finalized group string (|group_name|). + static FieldTrial* CreateFieldTrial(const std::string& name, + const std::string& group_name); + + // Add an observer to be notified when a field trial is irrevocably committed + // to being part of some specific field_group (and hence the group_name is + // also finalized for that field_trial). + static void AddObserver(Observer* observer); + + // Remove an observer. + static void RemoveObserver(Observer* observer); + + // Notify all observers that a group has been finalized for |field_trial|. + static void NotifyFieldTrialGroupSelection(FieldTrial* field_trial); + + // Return the number of active field trials. + static size_t GetFieldTrialCount(); + + // If one-time randomization is enabled, returns a weak pointer to the + // corresponding EntropyProvider. Otherwise, returns NULL. + static const FieldTrial::EntropyProvider* + GetEntropyProviderForOneTimeRandomization(); + + private: + // A map from FieldTrial names to the actual instances. + typedef std::map<std::string, FieldTrial*> RegistrationList; + + // Helper function should be called only while holding lock_. + FieldTrial* PreLockedFind(const std::string& name); + + // Register() stores a pointer to the given trial in a global map. + // This method also AddRef's the indicated trial. + // This should always be called after creating a new FieldTrial instance. + static void Register(FieldTrial* trial); + + static FieldTrialList* global_; // The singleton of this class. + + // This will tell us if there is an attempt to register a field + // trial or check if one-time randomization is enabled without + // creating the FieldTrialList. This is not an error, unless a + // FieldTrialList is created after that. + static bool used_without_global_; + + // Lock for access to registered_. + base::Lock lock_; + RegistrationList registered_; + + // Entropy provider to be used for one-time randomized field trials. If NULL, + // one-time randomization is not supported. + scoped_ptr<const FieldTrial::EntropyProvider> entropy_provider_; + + // List of observers to be notified when a group is selected for a FieldTrial. + scoped_refptr<ObserverListThreadSafe<Observer> > observer_list_; + + DISALLOW_COPY_AND_ASSIGN(FieldTrialList); +}; + +} // namespace base + +#endif // BASE_METRICS_FIELD_TRIAL_H_
diff --git a/src/base/metrics/field_trial_unittest.cc b/src/base/metrics/field_trial_unittest.cc new file mode 100644 index 0000000..eed5f78 --- /dev/null +++ b/src/base/metrics/field_trial_unittest.cc
@@ -0,0 +1,721 @@ +// 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/message_loop.h" +#include "base/metrics/field_trial.h" +#include "base/rand_util.h" +#include "base/stringprintf.h" +#include "base/string_number_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +// Default group name used by several tests. +const char kDefaultGroupName[] = "DefaultGroup"; + +// FieldTrialList::Observer implementation for testing. +class TestFieldTrialObserver : public FieldTrialList::Observer { + public: + TestFieldTrialObserver() { + FieldTrialList::AddObserver(this); + } + + virtual ~TestFieldTrialObserver() { + FieldTrialList::RemoveObserver(this); + } + + virtual void OnFieldTrialGroupFinalized(const std::string& trial, + const std::string& group) OVERRIDE { + trial_name_ = trial; + group_name_ = group; + } + + const std::string& trial_name() const { return trial_name_; } + const std::string& group_name() const { return group_name_; } + + private: + std::string trial_name_; + std::string group_name_; + + DISALLOW_COPY_AND_ASSIGN(TestFieldTrialObserver); +}; + +} // namespace + +class FieldTrialTest : public testing::Test { + public: + FieldTrialTest() : trial_list_(NULL) { + Time now = Time::NowFromSystemTime(); + TimeDelta one_year = TimeDelta::FromDays(365); + Time::Exploded exploded; + + Time next_year_time = now + one_year; + next_year_time.LocalExplode(&exploded); + next_year_ = exploded.year; + + Time last_year_time = now - one_year; + last_year_time.LocalExplode(&exploded); + last_year_ = exploded.year; + } + + protected: + int next_year_; + int last_year_; + MessageLoop message_loop_; + + private: + FieldTrialList trial_list_; +}; + +// Test registration, and also check that destructors are called for trials +// (and that Valgrind doesn't catch us leaking). +TEST_F(FieldTrialTest, Registration) { + const char* name1 = "name 1 test"; + const char* name2 = "name 2 test"; + EXPECT_FALSE(FieldTrialList::Find(name1)); + EXPECT_FALSE(FieldTrialList::Find(name2)); + + FieldTrial* trial1 = FieldTrialList::FactoryGetFieldTrial( + name1, 10, "default name 1 test", next_year_, 12, 31, NULL); + EXPECT_EQ(FieldTrial::kNotFinalized, trial1->group_); + EXPECT_EQ(name1, trial1->trial_name()); + EXPECT_EQ("", trial1->group_name_internal()); + + trial1->AppendGroup("", 7); + + EXPECT_EQ(trial1, FieldTrialList::Find(name1)); + EXPECT_FALSE(FieldTrialList::Find(name2)); + + FieldTrial* trial2 = FieldTrialList::FactoryGetFieldTrial( + name2, 10, "default name 2 test", next_year_, 12, 31, NULL); + EXPECT_EQ(FieldTrial::kNotFinalized, trial2->group_); + EXPECT_EQ(name2, trial2->trial_name()); + EXPECT_EQ("", trial2->group_name_internal()); + + trial2->AppendGroup("a first group", 7); + + EXPECT_EQ(trial1, FieldTrialList::Find(name1)); + EXPECT_EQ(trial2, FieldTrialList::Find(name2)); + // Note: FieldTrialList should delete the objects at shutdown. +} + +TEST_F(FieldTrialTest, AbsoluteProbabilities) { + char always_true[] = " always true"; + char default_always_true[] = " default always true"; + char always_false[] = " always false"; + char default_always_false[] = " default always false"; + for (int i = 1; i < 250; ++i) { + // Try lots of names, by changing the first character of the name. + always_true[0] = i; + default_always_true[0] = i; + always_false[0] = i; + default_always_false[0] = i; + + FieldTrial* trial_true = FieldTrialList::FactoryGetFieldTrial( + always_true, 10, default_always_true, next_year_, 12, 31, NULL); + const std::string winner = "TheWinner"; + int winner_group = trial_true->AppendGroup(winner, 10); + + EXPECT_EQ(winner_group, trial_true->group()); + EXPECT_EQ(winner, trial_true->group_name()); + + FieldTrial* trial_false = FieldTrialList::FactoryGetFieldTrial( + always_false, 10, default_always_false, next_year_, 12, 31, NULL); + int loser_group = trial_false->AppendGroup("ALoser", 0); + + EXPECT_NE(loser_group, trial_false->group()); + } +} + +TEST_F(FieldTrialTest, RemainingProbability) { + // First create a test that hasn't had a winner yet. + const std::string winner = "Winner"; + const std::string loser = "Loser"; + scoped_refptr<FieldTrial> trial; + int counter = 0; + int default_group_number = -1; + do { + std::string name = StringPrintf("trial%d", ++counter); + trial = FieldTrialList::FactoryGetFieldTrial( + name, 10, winner, next_year_, 12, 31, &default_group_number); + trial->AppendGroup(loser, 5); // 50% chance of not being chosen. + // If a group is not assigned, group_ will be kNotFinalized. + } while (trial->group_ != FieldTrial::kNotFinalized); + + // And that 'default' group (winner) should always win. + EXPECT_EQ(default_group_number, trial->group()); + + // And that winner should ALWAYS win. + EXPECT_EQ(winner, trial->group_name()); +} + +TEST_F(FieldTrialTest, FiftyFiftyProbability) { + // Check that even with small divisors, we have the proper probabilities, and + // all outcomes are possible. Since this is a 50-50 test, it should get both + // outcomes in a few tries, but we'll try no more than 100 times (and be flaky + // with probability around 1 in 2^99). + bool first_winner = false; + bool second_winner = false; + int counter = 0; + do { + std::string name = base::StringPrintf("FiftyFifty%d", ++counter); + std::string default_group_name = base::StringPrintf("Default FiftyFifty%d", + ++counter); + scoped_refptr<FieldTrial> trial(FieldTrialList::FactoryGetFieldTrial( + name, 2, default_group_name, next_year_, 12, 31, NULL)); + trial->AppendGroup("first", 1); // 50% chance of being chosen. + // If group_ is kNotFinalized, then a group assignement hasn't been done. + if (trial->group_ != FieldTrial::kNotFinalized) { + first_winner = true; + continue; + } + trial->AppendGroup("second", 1); // Always chosen at this point. + EXPECT_NE(FieldTrial::kNotFinalized, trial->group()); + second_winner = true; + } while ((!second_winner || !first_winner) && counter < 100); + EXPECT_TRUE(second_winner); + EXPECT_TRUE(first_winner); +} + +TEST_F(FieldTrialTest, MiddleProbabilities) { + char name[] = " same name"; + char default_group_name[] = " default same name"; + bool false_event_seen = false; + bool true_event_seen = false; + for (int i = 1; i < 250; ++i) { + name[0] = i; + default_group_name[0] = i; + FieldTrial* trial = FieldTrialList::FactoryGetFieldTrial( + name, 10, default_group_name, next_year_, 12, 31, NULL); + int might_win = trial->AppendGroup("MightWin", 5); + + if (trial->group() == might_win) { + true_event_seen = true; + } else { + false_event_seen = true; + } + if (false_event_seen && true_event_seen) + return; // Successful test!!! + } + // Very surprising to get here. Probability should be around 1 in 2 ** 250. + // One of the following will fail. + EXPECT_TRUE(false_event_seen); + EXPECT_TRUE(true_event_seen); +} + +TEST_F(FieldTrialTest, OneWinner) { + char name[] = "Some name"; + char default_group_name[] = "Default some name"; + int group_count(10); + + int default_group_number = -1; + FieldTrial* trial = FieldTrialList::FactoryGetFieldTrial( + name, group_count, default_group_name, next_year_, 12, 31, + &default_group_number); + int winner_index(-2); + std::string winner_name; + + for (int i = 1; i <= group_count; ++i) { + int might_win = trial->AppendGroup("", 1); + + // Because we keep appending groups, we want to see if the last group that + // was added has been assigned or not. + if (trial->group_ == might_win) { + EXPECT_EQ(-2, winner_index); + winner_index = might_win; + StringAppendF(&winner_name, "%d", might_win); + EXPECT_EQ(winner_name, trial->group_name()); + } + } + EXPECT_GE(winner_index, 0); + // Since all groups cover the total probability, we should not have + // chosen the default group. + EXPECT_NE(trial->group(), default_group_number); + EXPECT_EQ(trial->group(), winner_index); + EXPECT_EQ(trial->group_name(), winner_name); +} + +TEST_F(FieldTrialTest, DisableProbability) { + const std::string default_group_name = "Default group"; + const std::string loser = "Loser"; + const std::string name = "Trial"; + + // Create a field trail that has expired. + int default_group_number = -1; + scoped_refptr<FieldTrial> trial; + trial = FieldTrialList::FactoryGetFieldTrial( + name, 1000000000, default_group_name, last_year_, 1, 1, + &default_group_number); + trial->AppendGroup(loser, 999999999); // 99.9999999% chance of being chosen. + + // Because trial has expired, we should always be in the default group. + EXPECT_EQ(default_group_number, trial->group()); + + // And that default_group_name should ALWAYS win. + EXPECT_EQ(default_group_name, trial->group_name()); +} + +TEST_F(FieldTrialTest, ActiveGroups) { + std::string no_group("No Group"); + scoped_refptr<FieldTrial> trial(FieldTrialList::FactoryGetFieldTrial( + no_group, 10, "Default", next_year_, 12, 31, NULL)); + + // There is no winner yet, so no NameGroupId should be returned. + FieldTrial::ActiveGroup active_group; + EXPECT_FALSE(trial->GetActiveGroup(&active_group)); + + // Create a single winning group. + std::string one_winner("One Winner"); + trial = FieldTrialList::FactoryGetFieldTrial( + one_winner, 10, "Default", next_year_, 12, 31, NULL); + std::string winner("Winner"); + trial->AppendGroup(winner, 10); + EXPECT_FALSE(trial->GetActiveGroup(&active_group)); + // Finalize the group selection by accessing the selected group. + trial->group(); + EXPECT_TRUE(trial->GetActiveGroup(&active_group)); + EXPECT_EQ(one_winner, active_group.trial_name); + EXPECT_EQ(winner, active_group.group_name); + + std::string multi_group("MultiGroup"); + scoped_refptr<FieldTrial> multi_group_trial = + FieldTrialList::FactoryGetFieldTrial(multi_group, 9, "Default", + next_year_, 12, 31, NULL); + + multi_group_trial->AppendGroup("Me", 3); + multi_group_trial->AppendGroup("You", 3); + multi_group_trial->AppendGroup("Them", 3); + EXPECT_FALSE(multi_group_trial->GetActiveGroup(&active_group)); + // Finalize the group selection by accessing the selected group. + multi_group_trial->group(); + EXPECT_TRUE(multi_group_trial->GetActiveGroup(&active_group)); + EXPECT_EQ(multi_group, active_group.trial_name); + EXPECT_EQ(multi_group_trial->group_name(), active_group.group_name); + + // Now check if the list is built properly... + FieldTrial::ActiveGroups active_groups; + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + EXPECT_EQ(2U, active_groups.size()); + for (size_t i = 0; i < active_groups.size(); ++i) { + // Order is not guaranteed, so check all values. + EXPECT_NE(no_group, active_groups[i].trial_name); + EXPECT_TRUE(one_winner != active_groups[i].trial_name || + winner == active_groups[i].group_name); + EXPECT_TRUE(multi_group != active_groups[i].trial_name || + multi_group_trial->group_name() == active_groups[i].group_name); + } +} + +TEST_F(FieldTrialTest, ActiveGroupsNotFinalized) { + const char kTrialName[] = "TestTrial"; + const char kSecondaryGroupName[] = "SecondaryGroup"; + + int default_group = -1; + FieldTrial* trial = + FieldTrialList::FactoryGetFieldTrial(kTrialName, 100, kDefaultGroupName, + next_year_, 12, 31, &default_group); + const int secondary_group = trial->AppendGroup(kSecondaryGroupName, 50); + + // Before |group()| is called, |GetActiveGroup()| should return false. + FieldTrial::ActiveGroup active_group; + EXPECT_FALSE(trial->GetActiveGroup(&active_group)); + + // |GetActiveFieldTrialGroups()| should also not include the trial. + FieldTrial::ActiveGroups active_groups; + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + EXPECT_TRUE(active_groups.empty()); + + // After |group()| has been called, both APIs should succeed. + const int chosen_group = trial->group(); + EXPECT_TRUE(chosen_group == default_group || chosen_group == secondary_group); + + EXPECT_TRUE(trial->GetActiveGroup(&active_group)); + EXPECT_EQ(kTrialName, active_group.trial_name); + if (chosen_group == default_group) + EXPECT_EQ(kDefaultGroupName, active_group.group_name); + else + EXPECT_EQ(kSecondaryGroupName, active_group.group_name); + + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + ASSERT_EQ(1U, active_groups.size()); + EXPECT_EQ(kTrialName, active_groups[0].trial_name); + EXPECT_EQ(active_group.group_name, active_groups[0].group_name); +} + +TEST_F(FieldTrialTest, Save) { + std::string save_string; + + FieldTrial* trial = FieldTrialList::FactoryGetFieldTrial( + "Some name", 10, "Default some name", next_year_, 12, 31, NULL); + // There is no winner yet, so no textual group name is associated with trial. + // In this case, the trial should not be included. + EXPECT_EQ("", trial->group_name_internal()); + FieldTrialList::StatesToString(&save_string); + EXPECT_EQ("", save_string); + save_string.clear(); + + // Create a winning group. + trial->AppendGroup("Winner", 10); + // Finalize the group selection by accessing the selected group. + trial->group(); + FieldTrialList::StatesToString(&save_string); + EXPECT_EQ("Some name/Winner/", save_string); + save_string.clear(); + + // Create a second trial and winning group. + FieldTrial* trial2 = FieldTrialList::FactoryGetFieldTrial( + "xxx", 10, "Default xxx", next_year_, 12, 31, NULL); + trial2->AppendGroup("yyyy", 10); + // Finalize the group selection by accessing the selected group. + trial2->group(); + + FieldTrialList::StatesToString(&save_string); + // We assume names are alphabetized... though this is not critical. + EXPECT_EQ("Some name/Winner/xxx/yyyy/", save_string); + save_string.clear(); + + // Create a third trial with only the default group. + FieldTrial* trial3 = FieldTrialList::FactoryGetFieldTrial( + "zzz", 10, "default", next_year_, 12, 31, NULL); + // Finalize the group selection by accessing the selected group. + trial3->group(); + + FieldTrialList::StatesToString(&save_string); + EXPECT_EQ("Some name/Winner/xxx/yyyy/zzz/default/", save_string); +} + +TEST_F(FieldTrialTest, Restore) { + ASSERT_FALSE(FieldTrialList::TrialExists("Some_name")); + ASSERT_FALSE(FieldTrialList::TrialExists("xxx")); + + FieldTrialList::CreateTrialsFromString("Some_name/Winner/xxx/yyyy/"); + + FieldTrial* trial = FieldTrialList::Find("Some_name"); + ASSERT_NE(static_cast<FieldTrial*>(NULL), trial); + EXPECT_EQ("Winner", trial->group_name()); + EXPECT_EQ("Some_name", trial->trial_name()); + + trial = FieldTrialList::Find("xxx"); + ASSERT_NE(static_cast<FieldTrial*>(NULL), trial); + EXPECT_EQ("yyyy", trial->group_name()); + EXPECT_EQ("xxx", trial->trial_name()); +} + +TEST_F(FieldTrialTest, BogusRestore) { + EXPECT_FALSE(FieldTrialList::CreateTrialsFromString("MissingSlash")); + EXPECT_FALSE(FieldTrialList::CreateTrialsFromString("MissingGroupName/")); + EXPECT_FALSE(FieldTrialList::CreateTrialsFromString( + "MissingFinalSlash/gname")); + EXPECT_FALSE(FieldTrialList::CreateTrialsFromString( + "noname, only group/")); +} + +TEST_F(FieldTrialTest, DuplicateRestore) { + FieldTrial* trial = FieldTrialList::FactoryGetFieldTrial( + "Some name", 10, "Default some name", next_year_, 12, 31, NULL); + trial->AppendGroup("Winner", 10); + // Finalize the group selection by accessing the selected group. + trial->group(); + std::string save_string; + FieldTrialList::StatesToString(&save_string); + EXPECT_EQ("Some name/Winner/", save_string); + + // It is OK if we redundantly specify a winner. + EXPECT_TRUE(FieldTrialList::CreateTrialsFromString(save_string)); + + // But it is an error to try to change to a different winner. + EXPECT_FALSE(FieldTrialList::CreateTrialsFromString("Some name/Loser/")); +} + +TEST_F(FieldTrialTest, CreateTrialsFromStringAreActive) { + ASSERT_FALSE(FieldTrialList::TrialExists("Abc")); + ASSERT_FALSE(FieldTrialList::TrialExists("Xyz")); + ASSERT_TRUE(FieldTrialList::CreateTrialsFromString("Abc/def/Xyz/zyx/")); + + FieldTrial::ActiveGroups active_groups; + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + ASSERT_EQ(2U, active_groups.size()); + EXPECT_EQ("Abc", active_groups[0].trial_name); + EXPECT_EQ("def", active_groups[0].group_name); + EXPECT_EQ("Xyz", active_groups[1].trial_name); + EXPECT_EQ("zyx", active_groups[1].group_name); +} + +TEST_F(FieldTrialTest, CreateTrialsFromStringObserver) { + ASSERT_FALSE(FieldTrialList::TrialExists("Abc")); + + TestFieldTrialObserver observer; + ASSERT_TRUE(FieldTrialList::CreateTrialsFromString("Abc/def/")); + + message_loop_.RunUntilIdle(); + EXPECT_EQ("Abc", observer.trial_name()); + EXPECT_EQ("def", observer.group_name()); +} + +TEST_F(FieldTrialTest, CreateFieldTrial) { + ASSERT_FALSE(FieldTrialList::TrialExists("Some_name")); + + FieldTrialList::CreateFieldTrial("Some_name", "Winner"); + + FieldTrial* trial = FieldTrialList::Find("Some_name"); + ASSERT_NE(static_cast<FieldTrial*>(NULL), trial); + EXPECT_EQ("Winner", trial->group_name()); + EXPECT_EQ("Some_name", trial->trial_name()); +} + +TEST_F(FieldTrialTest, CreateFieldTrialIsNotActive) { + const char kTrialName[] = "CreateFieldTrialIsActiveTrial"; + const char kWinnerGroup[] = "Winner"; + ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName)); + FieldTrialList::CreateFieldTrial(kTrialName, kWinnerGroup); + + FieldTrial::ActiveGroups active_groups; + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + EXPECT_TRUE(active_groups.empty()); +} + +TEST_F(FieldTrialTest, DuplicateFieldTrial) { + FieldTrial* trial = FieldTrialList::FactoryGetFieldTrial( + "Some_name", 10, "Default some name", next_year_, 12, 31, NULL); + trial->AppendGroup("Winner", 10); + + // It is OK if we redundantly specify a winner. + FieldTrial* trial1 = FieldTrialList::CreateFieldTrial("Some_name", "Winner"); + EXPECT_TRUE(trial1 != NULL); + + // But it is an error to try to change to a different winner. + FieldTrial* trial2 = FieldTrialList::CreateFieldTrial("Some_name", "Loser"); + EXPECT_TRUE(trial2 == NULL); +} + +TEST_F(FieldTrialTest, MakeName) { + FieldTrial* trial = FieldTrialList::FactoryGetFieldTrial( + "Field Trial", 10, "Winner", next_year_, 12, 31, NULL); + trial->group(); + EXPECT_EQ("Histogram_Winner", + FieldTrial::MakeName("Histogram", "Field Trial")); +} + +TEST_F(FieldTrialTest, DisableImmediately) { + int default_group_number = -1; + FieldTrial* trial = FieldTrialList::FactoryGetFieldTrial( + "trial", 100, "default", next_year_, 12, 31, &default_group_number); + trial->Disable(); + ASSERT_EQ("default", trial->group_name()); + ASSERT_EQ(default_group_number, trial->group()); +} + +TEST_F(FieldTrialTest, DisableAfterInitialization) { + FieldTrial* trial = + FieldTrialList::FactoryGetFieldTrial("trial", 100, "default", + next_year_, 12, 31, NULL); + trial->AppendGroup("non_default", 100); + trial->Disable(); + ASSERT_EQ("default", trial->group_name()); +} + +TEST_F(FieldTrialTest, ForcedFieldTrials) { + // Validate we keep the forced choice. + FieldTrial* forced_trial = FieldTrialList::CreateFieldTrial("Use the", + "Force"); + EXPECT_STREQ("Force", forced_trial->group_name().c_str()); + + int default_group_number = -1; + FieldTrial* factory_trial = FieldTrialList::FactoryGetFieldTrial( + "Use the", 1000, "default", next_year_, 12, 31, &default_group_number); + EXPECT_EQ(factory_trial, forced_trial); + + int chosen_group = factory_trial->AppendGroup("Force", 100); + EXPECT_EQ(chosen_group, factory_trial->group()); + int not_chosen_group = factory_trial->AppendGroup("Dark Side", 100); + EXPECT_NE(chosen_group, not_chosen_group); + + // Since we didn't force the default group, we should not be returned the + // chosen group as the default group. + EXPECT_NE(default_group_number, chosen_group); + int new_group = factory_trial->AppendGroup("Duck Tape", 800); + EXPECT_NE(chosen_group, new_group); + // The new group should not be the default group either. + EXPECT_NE(default_group_number, new_group); + + // Forcing the default should use the proper group ID. + forced_trial = FieldTrialList::CreateFieldTrial("Trial Name", "Default"); + factory_trial = FieldTrialList::FactoryGetFieldTrial( + "Trial Name", 1000, "Default", next_year_, 12, 31, &default_group_number); + EXPECT_EQ(forced_trial, factory_trial); + + int other_group = factory_trial->AppendGroup("Not Default", 100); + EXPECT_STREQ("Default", factory_trial->group_name().c_str()); + EXPECT_EQ(default_group_number, factory_trial->group()); + EXPECT_NE(other_group, factory_trial->group()); + + int new_other_group = factory_trial->AppendGroup("Not Default Either", 800); + EXPECT_NE(new_other_group, factory_trial->group()); +} + +TEST_F(FieldTrialTest, SetForced) { + // Start by setting a trial for which we ensure a winner... + int default_group_number = -1; + FieldTrial* forced_trial = FieldTrialList::FactoryGetFieldTrial( + "Use the", 1, "default", next_year_, 12, 31, &default_group_number); + EXPECT_EQ(forced_trial, forced_trial); + + int forced_group = forced_trial->AppendGroup("Force", 1); + EXPECT_EQ(forced_group, forced_trial->group()); + + // Now force it. + forced_trial->SetForced(); + + // Now try to set it up differently as a hard coded registration would. + FieldTrial* hard_coded_trial = FieldTrialList::FactoryGetFieldTrial( + "Use the", 1, "default", next_year_, 12, 31, &default_group_number); + EXPECT_EQ(hard_coded_trial, forced_trial); + + int would_lose_group = hard_coded_trial->AppendGroup("Force", 0); + EXPECT_EQ(forced_group, hard_coded_trial->group()); + EXPECT_EQ(forced_group, would_lose_group); + + // Same thing if we would have done it to win again. + FieldTrial* other_hard_coded_trial = FieldTrialList::FactoryGetFieldTrial( + "Use the", 1, "default", next_year_, 12, 31, &default_group_number); + EXPECT_EQ(other_hard_coded_trial, forced_trial); + + int would_win_group = other_hard_coded_trial->AppendGroup("Force", 1); + EXPECT_EQ(forced_group, other_hard_coded_trial->group()); + EXPECT_EQ(forced_group, would_win_group); +} + +TEST_F(FieldTrialTest, SetForcedDefaultOnly) { + const char kTrialName[] = "SetForcedDefaultOnly"; + ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName)); + + int default_group = -1; + FieldTrial* trial = + FieldTrialList::FactoryGetFieldTrial(kTrialName, 100, kDefaultGroupName, + next_year_, 12, 31, &default_group); + trial->SetForced(); + + trial = FieldTrialList::FactoryGetFieldTrial(kTrialName, 100, + kDefaultGroupName, next_year_, + 12, 31, NULL); + EXPECT_EQ(default_group, trial->group()); + EXPECT_EQ(kDefaultGroupName, trial->group_name()); +} + +TEST_F(FieldTrialTest, SetForcedDefaultWithExtraGroup) { + const char kTrialName[] = "SetForcedDefaultWithExtraGroup"; + ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName)); + + int default_group = -1; + FieldTrial* trial = + FieldTrialList::FactoryGetFieldTrial(kTrialName, 100, kDefaultGroupName, + next_year_, 12, 31, &default_group); + trial->SetForced(); + + trial = FieldTrialList::FactoryGetFieldTrial(kTrialName, 100, + kDefaultGroupName, next_year_, + 12, 31, NULL); + const int extra_group = trial->AppendGroup("Extra", 100); + EXPECT_EQ(default_group, trial->group()); + EXPECT_NE(extra_group, trial->group()); + EXPECT_EQ(kDefaultGroupName, trial->group_name()); +} + +TEST_F(FieldTrialTest, Observe) { + const char kTrialName[] = "TrialToObserve1"; + const char kSecondaryGroupName[] = "SecondaryGroup"; + + TestFieldTrialObserver observer; + int default_group = -1; + FieldTrial* trial = + FieldTrialList::FactoryGetFieldTrial(kTrialName, 100, kDefaultGroupName, + next_year_, 12, 31, &default_group); + const int secondary_group = trial->AppendGroup(kSecondaryGroupName, 50); + const int chosen_group = trial->group(); + EXPECT_TRUE(chosen_group == default_group || chosen_group == secondary_group); + + message_loop_.RunUntilIdle(); + EXPECT_EQ(kTrialName, observer.trial_name()); + if (chosen_group == default_group) + EXPECT_EQ(kDefaultGroupName, observer.group_name()); + else + EXPECT_EQ(kSecondaryGroupName, observer.group_name()); +} + +TEST_F(FieldTrialTest, ObserveDisabled) { + const char kTrialName[] = "TrialToObserve2"; + + TestFieldTrialObserver observer; + int default_group = -1; + FieldTrial* trial = + FieldTrialList::FactoryGetFieldTrial(kTrialName, 100, kDefaultGroupName, + next_year_, 12, 31, &default_group); + trial->AppendGroup("A", 25); + trial->AppendGroup("B", 25); + trial->AppendGroup("C", 25); + trial->Disable(); + + // Observer shouldn't be notified of a disabled trial. + message_loop_.RunUntilIdle(); + EXPECT_TRUE(observer.trial_name().empty()); + EXPECT_TRUE(observer.group_name().empty()); + + // Observer shouldn't be notified even after a |group()| call. + EXPECT_EQ(default_group, trial->group()); + message_loop_.RunUntilIdle(); + EXPECT_TRUE(observer.trial_name().empty()); + EXPECT_TRUE(observer.group_name().empty()); +} + +TEST_F(FieldTrialTest, ObserveForcedDisabled) { + const char kTrialName[] = "TrialToObserve3"; + + TestFieldTrialObserver observer; + int default_group = -1; + FieldTrial* trial = + FieldTrialList::FactoryGetFieldTrial(kTrialName, 100, kDefaultGroupName, + next_year_, 12, 31, &default_group); + trial->AppendGroup("A", 25); + trial->AppendGroup("B", 25); + trial->AppendGroup("C", 25); + trial->SetForced(); + trial->Disable(); + + // Observer shouldn't be notified of a disabled trial, even when forced. + message_loop_.RunUntilIdle(); + EXPECT_TRUE(observer.trial_name().empty()); + EXPECT_TRUE(observer.group_name().empty()); + + // Observer shouldn't be notified even after a |group()| call. + EXPECT_EQ(default_group, trial->group()); + message_loop_.RunUntilIdle(); + EXPECT_TRUE(observer.trial_name().empty()); + EXPECT_TRUE(observer.group_name().empty()); +} + +TEST_F(FieldTrialTest, DisabledTrialNotActive) { + const char kTrialName[] = "DisabledTrial"; + ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName)); + + FieldTrial* trial = + FieldTrialList::FactoryGetFieldTrial(kTrialName, 100, kDefaultGroupName, + next_year_, 12, 31, NULL); + trial->AppendGroup("X", 50); + trial->Disable(); + + // Ensure the trial is not listed as active. + FieldTrial::ActiveGroups active_groups; + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + EXPECT_TRUE(active_groups.empty()); + + // Ensure the trial is not listed in the |StatesToString()| result. + std::string states; + FieldTrialList::StatesToString(&states); + EXPECT_TRUE(states.empty()); +} + + +} // namespace base
diff --git a/src/base/metrics/histogram.cc b/src/base/metrics/histogram.cc new file mode 100644 index 0000000..795ea3a --- /dev/null +++ b/src/base/metrics/histogram.cc
@@ -0,0 +1,866 @@ +// 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. + +// Histogram is an object that aggregates statistics, and can summarize them in +// various forms, including ASCII graphical, HTML, and numerically (as a +// vector of numbers corresponding to each of the aggregating buckets). +// See header file for details and examples. + +#include "base/metrics/histogram.h" + +#include <math.h> + +#include <algorithm> +#include <string> + +#include "base/compiler_specific.h" +#include "base/debug/alias.h" +#include "base/logging.h" +#include "base/metrics/sample_vector.h" +#include "base/metrics/statistics_recorder.h" +#include "base/pickle.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/synchronization/lock.h" +#include "base/values.h" + +using std::string; +using std::vector; + +namespace base { + +typedef HistogramBase::Count Count; +typedef HistogramBase::Sample Sample; + +// static +const size_t Histogram::kBucketCount_MAX = 16384u; + +// TODO(rtenneti): delete this code after debugging. +void CheckCorruption(const Histogram& histogram, bool new_histogram) { + const std::string& histogram_name = histogram.histogram_name(); + char histogram_name_buf[128]; + base::strlcpy(histogram_name_buf, + histogram_name.c_str(), + arraysize(histogram_name_buf)); + base::debug::Alias(histogram_name_buf); + + bool debug_new_histogram[1]; + debug_new_histogram[0] = new_histogram; + base::debug::Alias(debug_new_histogram); + + Sample previous_range = -1; // Bottom range is always 0. + for (size_t index = 0; index < histogram.bucket_count(); ++index) { + int new_range = histogram.ranges(index); + CHECK_LT(previous_range, new_range); + previous_range = new_range; + } + + CHECK(histogram.bucket_ranges()->HasValidChecksum()); +} + +Histogram* Histogram::FactoryGet(const string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags) { + bool valid_arguments = + InspectConstructionArguments(name, &minimum, &maximum, &bucket_count); + DCHECK(valid_arguments); + + Histogram* histogram = StatisticsRecorder::FindHistogram(name); + if (!histogram) { + // To avoid racy destruction at shutdown, the following will be leaked. + BucketRanges* ranges = new BucketRanges(bucket_count + 1); + InitializeBucketRanges(minimum, maximum, bucket_count, ranges); + const BucketRanges* registered_ranges = + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges); + + Histogram* tentative_histogram = + new Histogram(name, minimum, maximum, bucket_count, registered_ranges); + CheckCorruption(*tentative_histogram, true); + + tentative_histogram->SetFlags(flags); + histogram = + StatisticsRecorder::RegisterOrDeleteDuplicate(tentative_histogram); + } + // TODO(rtenneti): delete this code after debugging. + CheckCorruption(*histogram, false); + + CHECK_EQ(HISTOGRAM, histogram->GetHistogramType()); + CHECK(histogram->HasConstructionArguments(minimum, maximum, bucket_count)); + return histogram; +} + +Histogram* Histogram::FactoryTimeGet(const string& name, + TimeDelta minimum, + TimeDelta maximum, + size_t bucket_count, + int32 flags) { + return FactoryGet(name, minimum.InMilliseconds(), maximum.InMilliseconds(), + bucket_count, flags); +} + +TimeTicks Histogram::DebugNow() { +#ifndef NDEBUG + return TimeTicks::Now(); +#else + return TimeTicks(); +#endif +} + +// Calculate what range of values are held in each bucket. +// We have to be careful that we don't pick a ratio between starting points in +// consecutive buckets that is sooo small, that the integer bounds are the same +// (effectively making one bucket get no values). We need to avoid: +// ranges(i) == ranges(i + 1) +// To avoid that, we just do a fine-grained bucket width as far as we need to +// until we get a ratio that moves us along at least 2 units at a time. From +// that bucket onward we do use the exponential growth of buckets. +// +// static +void Histogram::InitializeBucketRanges(Sample minimum, + Sample maximum, + size_t bucket_count, + BucketRanges* ranges) { + DCHECK_EQ(ranges->size(), bucket_count + 1); + double log_max = log(static_cast<double>(maximum)); + double log_ratio; + double log_next; + size_t bucket_index = 1; + Sample current = minimum; + ranges->set_range(bucket_index, current); + while (bucket_count > ++bucket_index) { + double log_current; + log_current = log(static_cast<double>(current)); + // Calculate the count'th root of the range. + log_ratio = (log_max - log_current) / (bucket_count - bucket_index); + // See where the next bucket would start. + log_next = log_current + log_ratio; + Sample next; + next = static_cast<int>(floor(exp(log_next) + 0.5)); + if (next > current) + current = next; + else + ++current; // Just do a narrow bucket, and keep trying. + ranges->set_range(bucket_index, current); + } + ranges->set_range(ranges->size() - 1, HistogramBase::kSampleType_MAX); + ranges->ResetChecksum(); +} + +void Histogram::AddBoolean(bool value) { + DCHECK(false); +} + +void Histogram::AddSamples(const HistogramSamples& samples) { + samples_->Add(samples); +} + +bool Histogram::AddSamplesFromPickle(PickleIterator* iter) { + return samples_->AddFromPickle(iter); +} + +// static +string Histogram::SerializeHistogramInfo(const Histogram& histogram, + const HistogramSamples& snapshot) { + DCHECK(histogram.bucket_ranges()->HasValidChecksum()); + + Pickle pickle; + pickle.WriteString(histogram.histogram_name()); + pickle.WriteInt(histogram.declared_min()); + pickle.WriteInt(histogram.declared_max()); + pickle.WriteUInt64(histogram.bucket_count()); + pickle.WriteUInt32(histogram.bucket_ranges()->checksum()); + pickle.WriteInt(histogram.GetHistogramType()); + pickle.WriteInt(histogram.flags()); + + histogram.SerializeRanges(&pickle); + + snapshot.Serialize(&pickle); + + return string(static_cast<const char*>(pickle.data()), pickle.size()); +} + +// static +bool Histogram::DeserializeHistogramInfo(const string& histogram_info) { + if (histogram_info.empty()) { + return false; + } + + Pickle pickle(histogram_info.data(), + static_cast<int>(histogram_info.size())); + string histogram_name; + int declared_min; + int declared_max; + uint64 bucket_count; + uint32 range_checksum; + int histogram_type; + int pickle_flags; + + PickleIterator iter(pickle); + if (!iter.ReadString(&histogram_name) || + !iter.ReadInt(&declared_min) || + !iter.ReadInt(&declared_max) || + !iter.ReadUInt64(&bucket_count) || + !iter.ReadUInt32(&range_checksum) || + !iter.ReadInt(&histogram_type) || + !iter.ReadInt(&pickle_flags)) { + DLOG(ERROR) << "Pickle error decoding Histogram: " << histogram_name; + return false; + } + + DCHECK(pickle_flags & kIPCSerializationSourceFlag); + // Since these fields may have come from an untrusted renderer, do additional + // checks above and beyond those in Histogram::Initialize() + if (declared_max <= 0 || declared_min <= 0 || declared_max < declared_min || + INT_MAX / sizeof(Count) <= bucket_count || bucket_count < 2) { + DLOG(ERROR) << "Values error decoding Histogram: " << histogram_name; + return false; + } + + Flags flags = static_cast<Flags>(pickle_flags & ~kIPCSerializationSourceFlag); + + Histogram* render_histogram(NULL); + + if (histogram_type == HISTOGRAM) { + render_histogram = Histogram::FactoryGet( + histogram_name, declared_min, declared_max, bucket_count, flags); + } else if (histogram_type == LINEAR_HISTOGRAM) { + render_histogram = LinearHistogram::FactoryGet( + histogram_name, declared_min, declared_max, bucket_count, flags); + } else if (histogram_type == BOOLEAN_HISTOGRAM) { + render_histogram = BooleanHistogram::FactoryGet(histogram_name, flags); + } else if (histogram_type == CUSTOM_HISTOGRAM) { + vector<Sample> sample_ranges(bucket_count); + if (!CustomHistogram::DeserializeRanges(&iter, &sample_ranges)) { + DLOG(ERROR) << "Pickle error decoding ranges: " << histogram_name; + return false; + } + render_histogram = + CustomHistogram::FactoryGet(histogram_name, sample_ranges, flags); + } else { + DLOG(ERROR) << "Error Deserializing Histogram Unknown histogram_type: " + << histogram_type; + return false; + } + + DCHECK_EQ(render_histogram->declared_min(), declared_min); + DCHECK_EQ(render_histogram->declared_max(), declared_max); + DCHECK_EQ(render_histogram->bucket_count(), bucket_count); + DCHECK_EQ(render_histogram->GetHistogramType(), histogram_type); + + if (render_histogram->bucket_ranges()->checksum() != range_checksum) { + return false; + } + + if (render_histogram->flags() & kIPCSerializationSourceFlag) { + DVLOG(1) << "Single process mode, histogram observed and not copied: " + << histogram_name; + return true; + } + + DCHECK_EQ(flags & render_histogram->flags(), flags); + return render_histogram->AddSamplesFromPickle(&iter); +} + +// static +const int Histogram::kCommonRaceBasedCountMismatch = 5; + +Histogram::Inconsistencies Histogram::FindCorruption( + const HistogramSamples& samples) const { + int inconsistencies = NO_INCONSISTENCIES; + Sample previous_range = -1; // Bottom range is always 0. + for (size_t index = 0; index < bucket_count(); ++index) { + int new_range = ranges(index); + if (previous_range >= new_range) + inconsistencies |= BUCKET_ORDER_ERROR; + previous_range = new_range; + } + + if (!bucket_ranges()->HasValidChecksum()) + inconsistencies |= RANGE_CHECKSUM_ERROR; + + int64 delta64 = samples.redundant_count() - samples.TotalCount(); + if (delta64 != 0) { + int delta = static_cast<int>(delta64); + if (delta != delta64) + delta = INT_MAX; // Flag all giant errors as INT_MAX. + if (delta > 0) { + UMA_HISTOGRAM_COUNTS("Histogram.InconsistentCountHigh", delta); + if (delta > kCommonRaceBasedCountMismatch) + inconsistencies |= COUNT_HIGH_ERROR; + } else { + DCHECK_GT(0, delta); + UMA_HISTOGRAM_COUNTS("Histogram.InconsistentCountLow", -delta); + if (-delta > kCommonRaceBasedCountMismatch) + inconsistencies |= COUNT_LOW_ERROR; + } + } + return static_cast<Inconsistencies>(inconsistencies); +} + +Sample Histogram::ranges(size_t i) const { + return bucket_ranges_->range(i); +} + +size_t Histogram::bucket_count() const { + return bucket_count_; +} + +// static +bool Histogram::InspectConstructionArguments(const string& name, + Sample* minimum, + Sample* maximum, + size_t* bucket_count) { + // Defensive code for backward compatibility. + if (*minimum < 1) { + DVLOG(1) << "Histogram: " << name << " has bad minimum: " << *minimum; + *minimum = 1; + } + if (*maximum >= kSampleType_MAX) { + DVLOG(1) << "Histogram: " << name << " has bad maximum: " << *maximum; + *maximum = kSampleType_MAX - 1; + } + if (*bucket_count >= kBucketCount_MAX) { + DVLOG(1) << "Histogram: " << name << " has bad bucket_count: " + << *bucket_count; + *bucket_count = kBucketCount_MAX - 1; + } + + if (*minimum >= *maximum) + return false; + if (*bucket_count < 3) + return false; + if (*bucket_count > static_cast<size_t>(*maximum - *minimum + 2)) + return false; + return true; +} + +HistogramType Histogram::GetHistogramType() const { + return HISTOGRAM; +} + +bool Histogram::HasConstructionArguments(Sample minimum, + Sample maximum, + size_t bucket_count) const { + return ((minimum == declared_min_) && (maximum == declared_max_) && + (bucket_count == bucket_count_)); +} + +void Histogram::Add(int value) { + DCHECK_EQ(0, ranges(0)); + DCHECK_EQ(kSampleType_MAX, ranges(bucket_count_)); + + if (value > kSampleType_MAX - 1) + value = kSampleType_MAX - 1; + if (value < 0) + value = 0; + samples_->Accumulate(value, 1); +} + +scoped_ptr<HistogramSamples> Histogram::SnapshotSamples() const { + return SnapshotSampleVector().PassAs<HistogramSamples>(); +} + +// The following methods provide a graphical histogram display. +void Histogram::WriteHTMLGraph(string* output) const { + // TBD(jar) Write a nice HTML bar chart, with divs an mouse-overs etc. + output->append("<PRE>"); + WriteAsciiImpl(true, "<br>", output); + output->append("</PRE>"); +} + +void Histogram::WriteAscii(string* output) const { + WriteAsciiImpl(true, "\n", output); +} + +Histogram::Histogram(const string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + const BucketRanges* ranges) + : HistogramBase(name), + bucket_ranges_(ranges), + declared_min_(minimum), + declared_max_(maximum), + bucket_count_(bucket_count) { + if (ranges) + samples_.reset(new SampleVector(ranges)); +} + +Histogram::~Histogram() { + if (StatisticsRecorder::dump_on_exit()) { + string output; + WriteAsciiImpl(true, "\n", &output); + DLOG(INFO) << output; + } +} + +bool Histogram::SerializeRanges(Pickle* pickle) const { + return true; +} + +bool Histogram::PrintEmptyBucket(size_t index) const { + return true; +} + +// Use the actual bucket widths (like a linear histogram) until the widths get +// over some transition value, and then use that transition width. Exponentials +// get so big so fast (and we don't expect to see a lot of entries in the large +// buckets), so we need this to make it possible to see what is going on and +// not have 0-graphical-height buckets. +double Histogram::GetBucketSize(Count current, size_t i) const { + DCHECK_GT(ranges(i + 1), ranges(i)); + static const double kTransitionWidth = 5; + double denominator = ranges(i + 1) - ranges(i); + if (denominator > kTransitionWidth) + denominator = kTransitionWidth; // Stop trying to normalize. + return current/denominator; +} + +const string Histogram::GetAsciiBucketRange(size_t i) const { + string result; + if (kHexRangePrintingFlag & flags()) + StringAppendF(&result, "%#x", ranges(i)); + else + StringAppendF(&result, "%d", ranges(i)); + return result; +} + +//------------------------------------------------------------------------------ +// Private methods + +scoped_ptr<SampleVector> Histogram::SnapshotSampleVector() const { + scoped_ptr<SampleVector> samples(new SampleVector(bucket_ranges())); + samples->Add(*samples_); + return samples.Pass(); +} + +void Histogram::WriteAsciiImpl(bool graph_it, + const string& newline, + string* output) const { + // Get local (stack) copies of all effectively volatile class data so that we + // are consistent across our output activities. + scoped_ptr<SampleVector> snapshot = SnapshotSampleVector(); + Count sample_count = snapshot->TotalCount(); + + WriteAsciiHeader(*snapshot, sample_count, output); + output->append(newline); + + // Prepare to normalize graphical rendering of bucket contents. + double max_size = 0; + if (graph_it) + max_size = GetPeakBucketSize(*snapshot); + + // Calculate space needed to print bucket range numbers. Leave room to print + // nearly the largest bucket range without sliding over the histogram. + size_t largest_non_empty_bucket = bucket_count() - 1; + while (0 == snapshot->GetCountAtIndex(largest_non_empty_bucket)) { + if (0 == largest_non_empty_bucket) + break; // All buckets are empty. + --largest_non_empty_bucket; + } + + // Calculate largest print width needed for any of our bucket range displays. + size_t print_width = 1; + for (size_t i = 0; i < bucket_count(); ++i) { + if (snapshot->GetCountAtIndex(i)) { + size_t width = GetAsciiBucketRange(i).size() + 1; + if (width > print_width) + print_width = width; + } + } + + int64 remaining = sample_count; + int64 past = 0; + // Output the actual histogram graph. + for (size_t i = 0; i < bucket_count(); ++i) { + Count current = snapshot->GetCountAtIndex(i); + if (!current && !PrintEmptyBucket(i)) + continue; + remaining -= current; + string range = GetAsciiBucketRange(i); + output->append(range); + for (size_t j = 0; range.size() + j < print_width + 1; ++j) + output->push_back(' '); + if (0 == current && i < bucket_count() - 1 && + 0 == snapshot->GetCountAtIndex(i + 1)) { + while (i < bucket_count() - 1 && + 0 == snapshot->GetCountAtIndex(i + 1)) { + ++i; + } + output->append("... "); + output->append(newline); + continue; // No reason to plot emptiness. + } + double current_size = GetBucketSize(current, i); + if (graph_it) + WriteAsciiBucketGraph(current_size, max_size, output); + WriteAsciiBucketContext(past, current, remaining, i, output); + output->append(newline); + past += current; + } + DCHECK_EQ(sample_count, past); +} + +double Histogram::GetPeakBucketSize(const SampleVector& samples) const { + double max = 0; + for (size_t i = 0; i < bucket_count() ; ++i) { + double current_size = GetBucketSize(samples.GetCountAtIndex(i), i); + if (current_size > max) + max = current_size; + } + return max; +} + +void Histogram::WriteAsciiHeader(const SampleVector& samples, + Count sample_count, + string* output) const { + StringAppendF(output, + "Histogram: %s recorded %d samples", + histogram_name().c_str(), + sample_count); + if (0 == sample_count) { + DCHECK_EQ(samples.sum(), 0); + } else { + double average = static_cast<float>(samples.sum()) / sample_count; + + StringAppendF(output, ", average = %.1f", average); + } + if (flags() & ~kHexRangePrintingFlag) + StringAppendF(output, " (flags = 0x%x)", flags() & ~kHexRangePrintingFlag); +} + +void Histogram::WriteAsciiBucketContext(const int64 past, + const Count current, + const int64 remaining, + const size_t i, + string* output) const { + double scaled_sum = (past + current + remaining) / 100.0; + WriteAsciiBucketValue(current, scaled_sum, output); + if (0 < i) { + double percentage = past / scaled_sum; + StringAppendF(output, " {%3.1f%%}", percentage); + } +} + +void Histogram::WriteAsciiBucketValue(Count current, + double scaled_sum, + string* output) const { + StringAppendF(output, " (%d = %3.1f%%)", current, current/scaled_sum); +} + +void Histogram::WriteAsciiBucketGraph(double current_size, + double max_size, + string* output) const { + const int k_line_length = 72; // Maximal horizontal width of graph. + int x_count = static_cast<int>(k_line_length * (current_size / max_size) + + 0.5); + int x_remainder = k_line_length - x_count; + + while (0 < x_count--) + output->append("-"); + output->append("O"); + while (0 < x_remainder--) + output->append(" "); +} + +void Histogram::GetParameters(DictionaryValue* params) const { + params->SetString("type", HistogramTypeToString(GetHistogramType())); + params->SetInteger("min", declared_min()); + params->SetInteger("max", declared_max()); + params->SetInteger("bucket_count", static_cast<int>(bucket_count())); +} + +void Histogram::GetCountAndBucketData(Count* count, ListValue* buckets) const { + scoped_ptr<SampleVector> snapshot = SnapshotSampleVector(); + *count = snapshot->TotalCount(); + size_t index = 0; + for (size_t i = 0; i < bucket_count(); ++i) { + Sample count = snapshot->GetCountAtIndex(i); + if (count > 0) { + scoped_ptr<DictionaryValue> bucket_value(new DictionaryValue()); + bucket_value->SetInteger("low", ranges(i)); + if (i != bucket_count() - 1) + bucket_value->SetInteger("high", ranges(i + 1)); + bucket_value->SetInteger("count", count); + buckets->Set(index, bucket_value.release()); + ++index; + } + } +} + +//------------------------------------------------------------------------------ +// LinearHistogram: This histogram uses a traditional set of evenly spaced +// buckets. +//------------------------------------------------------------------------------ + +LinearHistogram::~LinearHistogram() {} + +Histogram* LinearHistogram::FactoryGet(const string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags) { + return FactoryGetWithRangeDescription( + name, minimum, maximum, bucket_count, flags, NULL); +} + +Histogram* LinearHistogram::FactoryTimeGet(const string& name, + TimeDelta minimum, + TimeDelta maximum, + size_t bucket_count, + int32 flags) { + return FactoryGet(name, minimum.InMilliseconds(), maximum.InMilliseconds(), + bucket_count, flags); +} + +Histogram* LinearHistogram::FactoryGetWithRangeDescription( + const std::string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags, + const DescriptionPair descriptions[]) { + bool valid_arguments = Histogram::InspectConstructionArguments( + name, &minimum, &maximum, &bucket_count); + DCHECK(valid_arguments); + + Histogram* histogram = StatisticsRecorder::FindHistogram(name); + if (!histogram) { + // To avoid racy destruction at shutdown, the following will be leaked. + BucketRanges* ranges = new BucketRanges(bucket_count + 1); + InitializeBucketRanges(minimum, maximum, bucket_count, ranges); + const BucketRanges* registered_ranges = + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges); + + LinearHistogram* tentative_histogram = + new LinearHistogram(name, minimum, maximum, bucket_count, + registered_ranges); + CheckCorruption(*tentative_histogram, true); + + // Set range descriptions. + if (descriptions) { + for (int i = 0; descriptions[i].description; ++i) { + tentative_histogram->bucket_description_[descriptions[i].sample] = + descriptions[i].description; + } + } + + tentative_histogram->SetFlags(flags); + histogram = + StatisticsRecorder::RegisterOrDeleteDuplicate(tentative_histogram); + } + // TODO(rtenneti): delete this code after debugging. + CheckCorruption(*histogram, false); + + CHECK_EQ(LINEAR_HISTOGRAM, histogram->GetHistogramType()); + CHECK(histogram->HasConstructionArguments(minimum, maximum, bucket_count)); + return histogram; +} + +HistogramType LinearHistogram::GetHistogramType() const { + return LINEAR_HISTOGRAM; +} + +LinearHistogram::LinearHistogram(const string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + const BucketRanges* ranges) + : Histogram(name, minimum, maximum, bucket_count, ranges) { +} + +double LinearHistogram::GetBucketSize(Count current, size_t i) const { + DCHECK_GT(ranges(i + 1), ranges(i)); + // Adjacent buckets with different widths would have "surprisingly" many (few) + // samples in a histogram if we didn't normalize this way. + double denominator = ranges(i + 1) - ranges(i); + return current/denominator; +} + +const string LinearHistogram::GetAsciiBucketRange(size_t i) const { + int range = ranges(i); + BucketDescriptionMap::const_iterator it = bucket_description_.find(range); + if (it == bucket_description_.end()) + return Histogram::GetAsciiBucketRange(i); + return it->second; +} + +bool LinearHistogram::PrintEmptyBucket(size_t index) const { + return bucket_description_.find(ranges(index)) == bucket_description_.end(); +} + +// static +void LinearHistogram::InitializeBucketRanges(Sample minimum, + Sample maximum, + size_t bucket_count, + BucketRanges* ranges) { + DCHECK_EQ(ranges->size(), bucket_count + 1); + double min = minimum; + double max = maximum; + size_t i; + for (i = 1; i < bucket_count; ++i) { + double linear_range = + (min * (bucket_count -1 - i) + max * (i - 1)) / (bucket_count - 2); + ranges->set_range(i, static_cast<Sample>(linear_range + 0.5)); + } + ranges->set_range(ranges->size() - 1, HistogramBase::kSampleType_MAX); + ranges->ResetChecksum(); +} + +//------------------------------------------------------------------------------ +// This section provides implementation for BooleanHistogram. +//------------------------------------------------------------------------------ + +Histogram* BooleanHistogram::FactoryGet(const string& name, int32 flags) { + Histogram* histogram = StatisticsRecorder::FindHistogram(name); + if (!histogram) { + // To avoid racy destruction at shutdown, the following will be leaked. + BucketRanges* ranges = new BucketRanges(4); + LinearHistogram::InitializeBucketRanges(1, 2, 3, ranges); + const BucketRanges* registered_ranges = + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges); + + BooleanHistogram* tentative_histogram = + new BooleanHistogram(name, registered_ranges); + CheckCorruption(*tentative_histogram, true); + + tentative_histogram->SetFlags(flags); + histogram = + StatisticsRecorder::RegisterOrDeleteDuplicate(tentative_histogram); + } + // TODO(rtenneti): delete this code after debugging. + CheckCorruption(*histogram, false); + + CHECK_EQ(BOOLEAN_HISTOGRAM, histogram->GetHistogramType()); + return histogram; +} + +HistogramType BooleanHistogram::GetHistogramType() const { + return BOOLEAN_HISTOGRAM; +} + +void BooleanHistogram::AddBoolean(bool value) { + Add(value ? 1 : 0); +} + +BooleanHistogram::BooleanHistogram(const string& name, + const BucketRanges* ranges) + : LinearHistogram(name, 1, 2, 3, ranges) {} + +//------------------------------------------------------------------------------ +// CustomHistogram: +//------------------------------------------------------------------------------ + +Histogram* CustomHistogram::FactoryGet(const string& name, + const vector<Sample>& custom_ranges, + int32 flags) { + CHECK(ValidateCustomRanges(custom_ranges)); + + Histogram* histogram = StatisticsRecorder::FindHistogram(name); + if (!histogram) { + BucketRanges* ranges = CreateBucketRangesFromCustomRanges(custom_ranges); + const BucketRanges* registered_ranges = + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges); + + // To avoid racy destruction at shutdown, the following will be leaked. + CustomHistogram* tentative_histogram = + new CustomHistogram(name, registered_ranges); + CheckCorruption(*tentative_histogram, true); + + tentative_histogram->SetFlags(flags); + + histogram = + StatisticsRecorder::RegisterOrDeleteDuplicate(tentative_histogram); + } + // TODO(rtenneti): delete this code after debugging. + CheckCorruption(*histogram, false); + + CHECK_EQ(histogram->GetHistogramType(), CUSTOM_HISTOGRAM); + return histogram; +} + +HistogramType CustomHistogram::GetHistogramType() const { + return CUSTOM_HISTOGRAM; +} + +// static +vector<Sample> CustomHistogram::ArrayToCustomRanges( + const Sample* values, size_t num_values) { + vector<Sample> all_values; + for (size_t i = 0; i < num_values; ++i) { + Sample value = values[i]; + all_values.push_back(value); + + // Ensure that a guard bucket is added. If we end up with duplicate + // values, FactoryGet will take care of removing them. + all_values.push_back(value + 1); + } + return all_values; +} + +CustomHistogram::CustomHistogram(const string& name, + const BucketRanges* ranges) + : Histogram(name, + ranges->range(1), + ranges->range(ranges->size() - 2), + ranges->size() - 1, + ranges) {} + +bool CustomHistogram::SerializeRanges(Pickle* pickle) const { + for (size_t i = 0; i < bucket_ranges()->size(); ++i) { + if (!pickle->WriteInt(bucket_ranges()->range(i))) + return false; + } + return true; +} + +// static +bool CustomHistogram::DeserializeRanges( + PickleIterator* iter, vector<Sample>* ranges) { + for (size_t i = 0; i < ranges->size(); ++i) { + if (!iter->ReadInt(&(*ranges)[i])) + return false; + } + return true; +} + +double CustomHistogram::GetBucketSize(Count current, size_t i) const { + return 1; +} + +// static +bool CustomHistogram::ValidateCustomRanges( + const vector<Sample>& custom_ranges) { + bool has_valid_range = false; + for (size_t i = 0; i < custom_ranges.size(); i++) { + Sample sample = custom_ranges[i]; + if (sample < 0 || sample > HistogramBase::kSampleType_MAX - 1) + return false; + if (sample != 0) + has_valid_range = true; + } + return has_valid_range; +} + +// static +BucketRanges* CustomHistogram::CreateBucketRangesFromCustomRanges( + const vector<Sample>& custom_ranges) { + // Remove the duplicates in the custom ranges array. + vector<int> ranges = custom_ranges; + ranges.push_back(0); // Ensure we have a zero value. + ranges.push_back(HistogramBase::kSampleType_MAX); + std::sort(ranges.begin(), ranges.end()); + ranges.erase(std::unique(ranges.begin(), ranges.end()), ranges.end()); + + BucketRanges* bucket_ranges = new BucketRanges(ranges.size()); + for (size_t i = 0; i < ranges.size(); i++) { + bucket_ranges->set_range(i, ranges[i]); + } + bucket_ranges->ResetChecksum(); + return bucket_ranges; +} + +} // namespace base
diff --git a/src/base/metrics/histogram.h b/src/base/metrics/histogram.h new file mode 100644 index 0000000..74474f3 --- /dev/null +++ b/src/base/metrics/histogram.h
@@ -0,0 +1,737 @@ +// 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. + +// Histogram is an object that aggregates statistics, and can summarize them in +// various forms, including ASCII graphical, HTML, and numerically (as a +// vector of numbers corresponding to each of the aggregating buckets). + +// It supports calls to accumulate either time intervals (which are processed +// as integral number of milliseconds), or arbitrary integral units. + +// For Histogram(exponential histogram), LinearHistogram and CustomHistogram, +// the minimum for a declared range is 1 (instead of 0), while the maximum is +// (HistogramBase::kSampleType_MAX - 1). Currently you can declare histograms +// with ranges exceeding those limits (e.g. 0 as minimal or +// HistogramBase::kSampleType_MAX as maximal), but those excesses will be +// silently clamped to those limits (for backwards compatibility with existing +// code). Best practice is to not exceed the limits. + +// Each use of a histogram with the same name will reference the same underlying +// data, so it is safe to record to the same histogram from multiple locations +// in the code. It is a runtime error if all uses of the same histogram do not +// 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 +// 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 +// range). +// For these 3 kinds of histograms, the max bucket count is always +// (Histogram::kBucketCount_MAX - 1). + +// The buckets layout of class Histogram is exponential. For example, buckets +// might contain (sequentially) the count of values in the following intervals: +// [0,1), [1,2), [2,4), [4,8), [8,16), [16,32), [32,64), [64,infinity) +// That bucket allocation would actually result from construction of a histogram +// for values between 1 and 64, with 8 buckets, such as: +// Histogram count("some name", 1, 64, 8); +// Note that the underflow bucket [0,1) and the overflow bucket [64,infinity) +// are also counted by the constructor in the user supplied "bucket_count" +// argument. +// The above example has an exponential ratio of 2 (doubling the bucket width +// in each consecutive bucket. The Histogram class automatically calculates +// the smallest ratio that it can use to construct the number of buckets +// selected in the constructor. An another example, if you had 50 buckets, +// and millisecond time values from 1 to 10000, then the ratio between +// consecutive bucket widths will be approximately somewhere around the 50th +// root of 10000. This approach provides very fine grain (narrow) buckets +// at the low end of the histogram scale, but allows the histogram to cover a +// gigantic range with the addition of very few buckets. + +// Usually we use macros to define and use a histogram. These macros use a +// pattern involving a function static variable, that is a pointer to a +// histogram. This static is explicitly initialized on any thread +// that detects a uninitialized (NULL) pointer. The potentially racy +// initialization is not a problem as it is always set to point to the same +// value (i.e., the FactoryGet always returns the same value). FactoryGet +// is also completely thread safe, which results in a completely thread safe, +// and relatively fast, set of counters. To avoid races at shutdown, the static +// pointer is NOT deleted, and we leak the histograms at process termination. + +#ifndef BASE_METRICS_HISTOGRAM_H_ +#define BASE_METRICS_HISTOGRAM_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/atomicops.h" +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/bucket_ranges.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_samples.h" +#include "base/time.h" + +class Pickle; +class PickleIterator; + +namespace base { + +class Lock; +//------------------------------------------------------------------------------ +// Histograms are often put in areas where they are called many many times, and +// performance is critical. As a result, they are designed to have a very low +// recurring cost of executing (adding additional samples). Toward that end, +// the macros declare a static pointer to the histogram in question, and only +// take a "slow path" to construct (or find) the histogram on the first run +// through the macro. We leak the histograms at shutdown time so that we don't +// have to validate using the pointers at any time during the running of the +// process. + +// The following code is generally what a thread-safe static pointer +// initializaion looks like for a histogram (after a macro is expanded). This +// sample is an expansion (with comments) of the code for +// HISTOGRAM_CUSTOM_COUNTS(). + +/* + do { + // The pointer's presence indicates the initialization is complete. + // Initialization is idempotent, so it can safely be atomically repeated. + static base::subtle::AtomicWord atomic_histogram_pointer = 0; + + // Acquire_Load() ensures that we acquire visibility to the pointed-to data + // in the histogrom. + base::Histogram* histogram_pointer(reinterpret_cast<base::Histogram*>( + base::subtle::Acquire_Load(&atomic_histogram_pointer))); + + if (!histogram_pointer) { + // This is the slow path, which will construct OR find the matching + // histogram. FactoryGet includes locks on a global histogram name map + // and is completely thread safe. + histogram_pointer = base::Histogram::FactoryGet( + name, min, max, bucket_count, base::HistogramBase::kNoFlags); + + // Use Release_Store to ensure that the histogram data is made available + // globally before we make the pointer visible. + // Several threads may perform this store, but the same value will be + // stored in all cases (for a given named/spec'ed histogram). + // We could do this without any barrier, since FactoryGet entered and + // exited a lock after construction, but this barrier makes things clear. + base::subtle::Release_Store(&atomic_histogram_pointer, + reinterpret_cast<base::subtle::AtomicWord>(histogram_pointer)); + } + + // Ensure calling contract is upheld, and the name does NOT vary. + DCHECK(histogram_pointer->histogram_name() == constant_histogram_name); + + histogram_pointer->Add(sample); + } while (0); +*/ + +// The above pattern is repeated in several macros. The only elements that +// vary are the invocation of the Add(sample) vs AddTime(sample), and the choice +// of which FactoryGet method to use. The different FactoryGet methods have +// various argument lists, so the function with its argument list is provided as +// a macro argument here. The name is only used in a DCHECK, to assure that +// callers don't try to vary the name of the histogram (which would tend to be +// ignored by the one-time initialization of the histogtram_pointer). +#define STATIC_HISTOGRAM_POINTER_BLOCK(constant_histogram_name, \ + histogram_add_method_invocation, \ + histogram_factory_get_invocation) \ + do { \ + static base::subtle::AtomicWord atomic_histogram_pointer = 0; \ + base::Histogram* histogram_pointer(reinterpret_cast<base::Histogram*>( \ + base::subtle::Acquire_Load(&atomic_histogram_pointer))); \ + if (!histogram_pointer) { \ + histogram_pointer = histogram_factory_get_invocation; \ + base::subtle::Release_Store(&atomic_histogram_pointer, \ + reinterpret_cast<base::subtle::AtomicWord>(histogram_pointer)); \ + } \ + DCHECK_EQ(histogram_pointer->histogram_name(), \ + std::string(constant_histogram_name)); \ + histogram_pointer->histogram_add_method_invocation; \ + } while (0) + + +//------------------------------------------------------------------------------ +// Provide easy general purpose histogram in a macro, just like stats counters. +// The first four macros use 50 buckets. + +#define HISTOGRAM_TIMES(name, sample) HISTOGRAM_CUSTOM_TIMES( \ + name, sample, base::TimeDelta::FromMilliseconds(1), \ + base::TimeDelta::FromSeconds(10), 50) + +#define HISTOGRAM_COUNTS(name, sample) HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1, 1000000, 50) + +#define HISTOGRAM_COUNTS_100(name, sample) HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1, 100, 50) + +#define HISTOGRAM_COUNTS_10000(name, sample) HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1, 10000, 50) + +#define HISTOGRAM_CUSTOM_COUNTS(name, sample, min, max, bucket_count) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, Add(sample), \ + base::Histogram::FactoryGet(name, min, max, bucket_count, \ + base::HistogramBase::kNoFlags)) + +#define HISTOGRAM_PERCENTAGE(name, under_one_hundred) \ + HISTOGRAM_ENUMERATION(name, under_one_hundred, 101) + +// For folks that need real specific times, use this to select a precise range +// of times you want plotted, and the number of buckets you want used. +#define HISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, AddTime(sample), \ + base::Histogram::FactoryTimeGet(name, min, max, bucket_count, \ + base::HistogramBase::kNoFlags)) + +// Support histograming of an enumerated value. The samples should always be +// strictly less than |boundary_value| -- this prevents you from running into +// problems down the line if you add additional buckets to the histogram. Note +// also that, despite explicitly setting the minimum bucket value to |1| below, +// it is fine for enumerated histograms to be 0-indexed -- this is because +// enumerated histograms should never have underflow. +#define HISTOGRAM_ENUMERATION(name, sample, boundary_value) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, Add(sample), \ + base::LinearHistogram::FactoryGet(name, 1, boundary_value, \ + boundary_value + 1, base::HistogramBase::kNoFlags)) + +// Support histograming of an enumerated value. Samples should be one of the +// std::vector<int> list provided via |custom_ranges|. See comments above +// CustomRanges::FactoryGet about the requirement of |custom_ranges|. +// You can use the helper function CustomHistogram::ArrayToCustomRanges to +// transform a C-style array of valid sample values to a std::vector<int>. +#define HISTOGRAM_CUSTOM_ENUMERATION(name, sample, custom_ranges) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, Add(sample), \ + base::CustomHistogram::FactoryGet(name, custom_ranges, \ + base::HistogramBase::kNoFlags)) + +//------------------------------------------------------------------------------ +// Define Debug vs non-debug flavors of macros. +#ifndef NDEBUG + +#define DHISTOGRAM_TIMES(name, sample) HISTOGRAM_TIMES(name, sample) +#define DHISTOGRAM_COUNTS(name, sample) HISTOGRAM_COUNTS(name, sample) +#define DHISTOGRAM_PERCENTAGE(name, under_one_hundred) HISTOGRAM_PERCENTAGE(\ + name, under_one_hundred) +#define DHISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \ + HISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) +#define DHISTOGRAM_CLIPPED_TIMES(name, sample, min, max, bucket_count) \ + HISTOGRAM_CLIPPED_TIMES(name, sample, min, max, bucket_count) +#define DHISTOGRAM_CUSTOM_COUNTS(name, sample, min, max, bucket_count) \ + HISTOGRAM_CUSTOM_COUNTS(name, sample, min, max, bucket_count) +#define DHISTOGRAM_ENUMERATION(name, sample, boundary_value) \ + HISTOGRAM_ENUMERATION(name, sample, boundary_value) +#define DHISTOGRAM_CUSTOM_ENUMERATION(name, sample, custom_ranges) \ + HISTOGRAM_CUSTOM_ENUMERATION(name, sample, custom_ranges) + +#else // NDEBUG +// Keep a mention of passed variables to avoid unused variable warnings in +// release build if these variables are only used in macros. +#define DISCARD_2_ARGUMENTS(a, b) \ + do { \ + static_cast<void>(a); \ + static_cast<void>(b); \ + } while(false) +#define DISCARD_3_ARGUMENTS(a, b, c) \ + do { \ + static_cast<void>(a); \ + static_cast<void>(b); \ + static_cast<void>(c); \ + } while(false) +#define DISCARD_5_ARGUMENTS(a, b, c, d, e) \ + do { \ + static_cast<void>(a); \ + static_cast<void>(b); \ + static_cast<void>(c); \ + static_cast<void>(d); \ + static_cast<void>(e); \ + } while(false) +#define DHISTOGRAM_TIMES(name, sample) \ + DISCARD_2_ARGUMENTS(name, sample) + +#define DHISTOGRAM_COUNTS(name, sample) \ + DISCARD_2_ARGUMENTS(name, sample) + +#define DHISTOGRAM_PERCENTAGE(name, under_one_hundred) \ + DISCARD_2_ARGUMENTS(name, under_one_hundred) + +#define DHISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \ + DISCARD_5_ARGUMENTS(name, sample, min, max, bucket_count) + +#define DHISTOGRAM_CLIPPED_TIMES(name, sample, min, max, bucket_count) \ + DISCARD_5_ARGUMENTS(name, sample, min, max, bucket_count) + +#define DHISTOGRAM_CUSTOM_COUNTS(name, sample, min, max, bucket_count) \ + DISCARD_5_ARGUMENTS(name, sample, min, max, bucket_count) + +#define DHISTOGRAM_ENUMERATION(name, sample, boundary_value) \ + DISCARD_3_ARGUMENTS(name, sample, boundary_value) + +#define DHISTOGRAM_CUSTOM_ENUMERATION(name, sample, custom_ranges) \ + DISCARD_3_ARGUMENTS(name, sample, custom_ranges) + +#endif // NDEBUG + +//------------------------------------------------------------------------------ +// The following macros provide typical usage scenarios for callers that wish +// to record histogram data, and have the data submitted/uploaded via UMA. +// Not all systems support such UMA, but if they do, the following macros +// should work with the service. +#if defined(__LB_SHELL__FOR_RELEASE__) + +#define UMA_HISTOGRAM_TIMES(name, sample) DISCARD_2_ARGUMENTS(name, sample) +#define UMA_HISTOGRAM_MEDIUM_TIMES(name, sample) \ + DISCARD_2_ARGUMENTS(name, sample) + +// Use this macro when times can routinely be much longer than 10 seconds. +#define UMA_HISTOGRAM_LONG_TIMES(name, sample) \ + DISCARD_2_ARGUMENTS(name, sample) +#define UMA_HISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \ + DISCARD_5_ARGUMENTS(name, sample, min, max, bucket_count) +#define UMA_HISTOGRAM_COUNTS(name, sample) DISCARD_2_ARGUMENTS(name, sample) +#define UMA_HISTOGRAM_COUNTS_100(name, sample) \ + DISCARD_2_ARGUMENTS(name, sample) +#define UMA_HISTOGRAM_COUNTS_10000(name, sample) \ + DISCARD_2_ARGUMENTS(name, sample) +#define UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample, min, max, bucket_count) \ + DISCARD_5_ARGUMENTS(name, sample, min, max, bucket_count) +#define UMA_HISTOGRAM_MEMORY_KB(name, sample) DISCARD_2_ARGUMENTS(name, sample) +#define UMA_HISTOGRAM_MEMORY_MB(name, sample) DISCARD_2_ARGUMENTS(name, sample) +#define UMA_HISTOGRAM_PERCENTAGE(name, under_one_hundred) \ + DISCARD_2_ARGUMENTS(name, under_one_hundred) +#define UMA_HISTOGRAM_BOOLEAN(name, sample) DISCARD_2_ARGUMENTS(name, sample) +#define UMA_HISTOGRAM_ENUMERATION(name, sample, boundary_value) \ + DISCARD_3_ARGUMENTS(name, sample, boundary_value) +#define UMA_HISTOGRAM_CUSTOM_ENUMERATION(name, sample, custom_ranges) \ + DISCARD_3_ARGUMENTS(name, sample, custom_ranges) + +#else + +#define UMA_HISTOGRAM_TIMES(name, sample) UMA_HISTOGRAM_CUSTOM_TIMES( \ + name, sample, base::TimeDelta::FromMilliseconds(1), \ + base::TimeDelta::FromSeconds(10), 50) + +#define UMA_HISTOGRAM_MEDIUM_TIMES(name, sample) UMA_HISTOGRAM_CUSTOM_TIMES( \ + name, sample, base::TimeDelta::FromMilliseconds(10), \ + base::TimeDelta::FromMinutes(3), 50) + +// Use this macro when times can routinely be much longer than 10 seconds. +#define UMA_HISTOGRAM_LONG_TIMES(name, sample) UMA_HISTOGRAM_CUSTOM_TIMES( \ + name, sample, base::TimeDelta::FromMilliseconds(1), \ + base::TimeDelta::FromHours(1), 50) + +#define UMA_HISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, AddTime(sample), \ + base::Histogram::FactoryTimeGet(name, min, max, bucket_count, \ + base::Histogram::kUmaTargetedHistogramFlag)) + +#define UMA_HISTOGRAM_COUNTS(name, sample) UMA_HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1, 1000000, 50) + +#define UMA_HISTOGRAM_COUNTS_100(name, sample) UMA_HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1, 100, 50) + +#define UMA_HISTOGRAM_COUNTS_10000(name, sample) UMA_HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1, 10000, 50) + +#define UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample, min, max, bucket_count) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, Add(sample), \ + base::Histogram::FactoryGet(name, min, max, bucket_count, \ + base::Histogram::kUmaTargetedHistogramFlag)) + +#define UMA_HISTOGRAM_MEMORY_KB(name, sample) UMA_HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1000, 500000, 50) + +#define UMA_HISTOGRAM_MEMORY_MB(name, sample) UMA_HISTOGRAM_CUSTOM_COUNTS( \ + name, sample, 1, 1000, 50) + +#define UMA_HISTOGRAM_PERCENTAGE(name, under_one_hundred) \ + UMA_HISTOGRAM_ENUMERATION(name, under_one_hundred, 101) + +#define UMA_HISTOGRAM_BOOLEAN(name, sample) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, AddBoolean(sample), \ + base::BooleanHistogram::FactoryGet(name, \ + base::Histogram::kUmaTargetedHistogramFlag)) + +// The samples should always be strictly less than |boundary_value|. For more +// details, see the comment for the |HISTOGRAM_ENUMERATION| macro, above. +#define UMA_HISTOGRAM_ENUMERATION(name, sample, boundary_value) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, Add(sample), \ + base::LinearHistogram::FactoryGet(name, 1, boundary_value, \ + boundary_value + 1, base::Histogram::kUmaTargetedHistogramFlag)) + +#define UMA_HISTOGRAM_CUSTOM_ENUMERATION(name, sample, custom_ranges) \ + STATIC_HISTOGRAM_POINTER_BLOCK(name, Add(sample), \ + base::CustomHistogram::FactoryGet(name, custom_ranges, \ + base::Histogram::kUmaTargetedHistogramFlag)) +#endif // defined(__LB_SHELL__FOR_RELEASE__) + +//------------------------------------------------------------------------------ + +class BucketRanges; +class SampleVector; + +class BooleanHistogram; +class CustomHistogram; +class Histogram; +class LinearHistogram; + +class BASE_EXPORT Histogram : public HistogramBase { + public: + // Initialize maximum number of buckets in histograms as 16,384. + static const size_t kBucketCount_MAX; + + typedef std::vector<Count> Counts; + + enum Inconsistencies { + NO_INCONSISTENCIES = 0x0, + RANGE_CHECKSUM_ERROR = 0x1, + BUCKET_ORDER_ERROR = 0x2, + COUNT_HIGH_ERROR = 0x4, + COUNT_LOW_ERROR = 0x8, + + NEVER_EXCEEDED_VALUE = 0x10 + }; + + struct DescriptionPair { + Sample sample; + const char* description; // Null means end of a list of pairs. + }; + + //---------------------------------------------------------------------------- + // For a valid histogram, input should follow these restrictions: + // minimum > 0 (if a minimum below 1 is specified, it will implicitly be + // normalized up to 1) + // maximum > minimum + // buckets > 2 [minimum buckets needed: underflow, overflow and the range] + // Additionally, + // buckets <= (maximum - minimum + 2) - this is to ensure that we don't have + // more buckets than the range of numbers; having more buckets than 1 per + // value in the range would be nonsensical. + static Histogram* FactoryGet(const std::string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags); + static Histogram* FactoryTimeGet(const std::string& name, + base::TimeDelta minimum, + base::TimeDelta maximum, + size_t bucket_count, + int32 flags); + + // Time call for use with DHISTOGRAM*. + // Returns TimeTicks::Now() in debug and TimeTicks() in release build. + static TimeTicks DebugNow(); + + static void InitializeBucketRanges(Sample minimum, + Sample maximum, + size_t bucket_count, + BucketRanges* ranges); + + // This method is an interface, used only by BooleanHistogram. + virtual void AddBoolean(bool value); + + // Accept a TimeDelta to increment. + void AddTime(TimeDelta time) { + Add(static_cast<int>(time.InMilliseconds())); + } + + void AddSamples(const HistogramSamples& samples); + bool AddSamplesFromPickle(PickleIterator* iter); + + // Convenience methods for serializing/deserializing the histograms. + // Histograms from Renderer process are serialized and sent to the browser. + // Browser process reconstructs the histogram from the pickled version + // accumulates the browser-side shadow copy of histograms (that mirror + // histograms created in the renderer). + + // Serialize the given snapshot of a Histogram into a String. Uses + // Pickle class to flatten the object. + static std::string SerializeHistogramInfo(const Histogram& histogram, + const HistogramSamples& snapshot); + + // The following method accepts a list of pickled histograms and + // builds a histogram and updates shadow copy of histogram data in the + // browser process. + static bool DeserializeHistogramInfo(const std::string& histogram_info); + + // This constant if for FindCorruption. Since snapshots of histograms are + // taken asynchronously relative to sampling, and our counting code currently + // does not prevent race conditions, it is pretty likely that we'll catch a + // redundant count that doesn't match the sample count. We allow for a + // certain amount of slop before flagging this as an inconsistency. Even with + // an inconsistency, we'll snapshot it again (for UMA in about a half hour), + // so we'll eventually get the data, if it was not the result of a corruption. + static const int kCommonRaceBasedCountMismatch; + + // Check to see if bucket ranges, counts and tallies in the snapshot are + // consistent with the bucket ranges and checksums in our histogram. This can + // produce a false-alarm if a race occurred in the reading of the data during + // a SnapShot process, but should otherwise be false at all times (unless we + // have memory over-writes, or DRAM failures). + virtual Inconsistencies FindCorruption(const HistogramSamples& samples) const; + + //---------------------------------------------------------------------------- + // Accessors for factory constuction, serialization and testing. + //---------------------------------------------------------------------------- + Sample declared_min() const { return declared_min_; } + Sample declared_max() const { return declared_max_; } + virtual Sample ranges(size_t i) const; + virtual size_t bucket_count() const; + const BucketRanges* bucket_ranges() const { return bucket_ranges_; } + + // This function validates histogram construction arguments. It returns false + // if some of the arguments are totally bad. + // Note. Currently it allow some bad input, e.g. 0 as minimum, but silently + // converts it to good input: 1. + // TODO(kaiwang): Be more restrict and return false for any bad input, and + // make this a readonly validating function. + static bool InspectConstructionArguments(const std::string& name, + Sample* minimum, + Sample* maximum, + size_t* bucket_count); + + // HistogramBase implementation: + virtual HistogramType GetHistogramType() const OVERRIDE; + virtual bool HasConstructionArguments(Sample minimum, + Sample maximum, + size_t bucket_count) const OVERRIDE; + virtual void Add(Sample value) OVERRIDE; + virtual scoped_ptr<HistogramSamples> SnapshotSamples() const OVERRIDE; + virtual void WriteHTMLGraph(std::string* output) const OVERRIDE; + virtual void WriteAscii(std::string* output) const OVERRIDE; + + protected: + // |bucket_count| and |ranges| should contain the underflow and overflow + // buckets. See top comments for example. + Histogram(const std::string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + const BucketRanges* ranges); + + virtual ~Histogram(); + + // Serialize the histogram's ranges to |*pickle|, returning true on success. + // Most subclasses can leave this no-op implementation, but some will want to + // override it, especially if the ranges cannot be re-derived from other + // serialized parameters. + virtual bool SerializeRanges(Pickle* pickle) const; + + // Method to override to skip the display of the i'th bucket if it's empty. + virtual bool PrintEmptyBucket(size_t index) const; + + // Get normalized size, relative to the ranges(i). + virtual double GetBucketSize(Count current, size_t i) const; + + // Return a string description of what goes in a given bucket. + // Most commonly this is the numeric value, but in derived classes it may + // be a name (or string description) given to the bucket. + virtual const std::string GetAsciiBucketRange(size_t it) const; + + private: + // Allow tests to corrupt our innards for testing purposes. + FRIEND_TEST_ALL_PREFIXES(HistogramTest, BoundsTest); + FRIEND_TEST_ALL_PREFIXES(HistogramTest, BucketPlacementTest); + FRIEND_TEST_ALL_PREFIXES(HistogramTest, CorruptBucketBounds); + FRIEND_TEST_ALL_PREFIXES(HistogramTest, CorruptSampleCounts); + FRIEND_TEST_ALL_PREFIXES(HistogramTest, NameMatchTest); + + friend class StatisticsRecorder; // To allow it to delete duplicates. + friend class StatisticsRecorderTest; + + // Implementation of SnapshotSamples function. + scoped_ptr<SampleVector> SnapshotSampleVector() const; + + //---------------------------------------------------------------------------- + // Helpers for emitting Ascii graphic. Each method appends data to output. + + void WriteAsciiImpl(bool graph_it, + const std::string& newline, + std::string* output) const; + + // Find out how large (graphically) the largest bucket will appear to be. + double GetPeakBucketSize(const SampleVector& samples) const; + + // Write a common header message describing this histogram. + void WriteAsciiHeader(const SampleVector& samples, + Count sample_count, + std::string* output) const; + + // Write information about previous, current, and next buckets. + // Information such as cumulative percentage, etc. + void WriteAsciiBucketContext(const int64 past, const Count current, + const int64 remaining, const size_t i, + std::string* output) const; + + // Write textual description of the bucket contents (relative to histogram). + // Output is the count in the buckets, as well as the percentage. + void WriteAsciiBucketValue(Count current, double scaled_sum, + std::string* output) const; + + // Produce actual graph (set of blank vs non blank char's) for a bucket. + void WriteAsciiBucketGraph(double current_size, double max_size, + std::string* output) const; + + // WriteJSON calls these. + virtual void GetParameters(DictionaryValue* params) const OVERRIDE; + + virtual void GetCountAndBucketData(Count* count, + ListValue* buckets) const OVERRIDE; + + // Does not own this object. Should get from StatisticsRecorder. + const BucketRanges* bucket_ranges_; + + Sample declared_min_; // Less than this goes into counts_[0] + Sample declared_max_; // Over this goes into counts_[bucket_count_ - 1]. + size_t bucket_count_; // Dimension of counts_[]. + + // Finally, provide the state that changes with the addition of each new + // sample. + scoped_ptr<SampleVector> samples_; + + DISALLOW_COPY_AND_ASSIGN(Histogram); +}; + +//------------------------------------------------------------------------------ + +// LinearHistogram is a more traditional histogram, with evenly spaced +// buckets. +class BASE_EXPORT LinearHistogram : public Histogram { + public: + virtual ~LinearHistogram(); + + /* minimum should start from 1. 0 is as minimum is invalid. 0 is an implicit + default underflow bucket. */ + static Histogram* FactoryGet(const std::string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags); + static Histogram* FactoryTimeGet(const std::string& name, + TimeDelta minimum, + TimeDelta maximum, + size_t bucket_count, + int32 flags); + + // Create a LinearHistogram and store a list of number/text values for use in + // writing the histogram graph. + // |descriptions| can be NULL, which means no special descriptions to set. If + // it's not NULL, the last element in the array must has a NULL in its + // "description" field. + static Histogram* FactoryGetWithRangeDescription( + const std::string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + int32 flags, + const DescriptionPair descriptions[]); + + static void InitializeBucketRanges(Sample minimum, + Sample maximum, + size_t bucket_count, + BucketRanges* ranges); + + // Overridden from Histogram: + virtual HistogramType GetHistogramType() const OVERRIDE; + + protected: + LinearHistogram(const std::string& name, + Sample minimum, + Sample maximum, + size_t bucket_count, + const BucketRanges* ranges); + + virtual double GetBucketSize(Count current, size_t i) const OVERRIDE; + + // If we have a description for a bucket, then return that. Otherwise + // let parent class provide a (numeric) description. + virtual const std::string GetAsciiBucketRange(size_t i) const OVERRIDE; + + // Skip printing of name for numeric range if we have a name (and if this is + // an empty bucket). + virtual bool PrintEmptyBucket(size_t index) const OVERRIDE; + + private: + // For some ranges, we store a printable description of a bucket range. + // If there is no desciption, then GetAsciiBucketRange() uses parent class + // to provide a description. + typedef std::map<Sample, std::string> BucketDescriptionMap; + BucketDescriptionMap bucket_description_; + + DISALLOW_COPY_AND_ASSIGN(LinearHistogram); +}; + +//------------------------------------------------------------------------------ + +// BooleanHistogram is a histogram for booleans. +class BASE_EXPORT BooleanHistogram : public LinearHistogram { + public: + static Histogram* FactoryGet(const std::string& name, int32 flags); + + virtual HistogramType GetHistogramType() const OVERRIDE; + + virtual void AddBoolean(bool value) OVERRIDE; + + private: + BooleanHistogram(const std::string& name, const BucketRanges* ranges); + + DISALLOW_COPY_AND_ASSIGN(BooleanHistogram); +}; + +//------------------------------------------------------------------------------ + +// CustomHistogram is a histogram for a set of custom integers. +class BASE_EXPORT CustomHistogram : public Histogram { + public: + // |custom_ranges| contains a vector of limits on ranges. Each limit should be + // > 0 and < kSampleType_MAX. (Currently 0 is still accepted for backward + // compatibility). The limits can be unordered or contain duplication, but + // client should not depend on this. + static Histogram* FactoryGet(const std::string& name, + const std::vector<Sample>& custom_ranges, + int32 flags); + + // Overridden from Histogram: + virtual HistogramType GetHistogramType() const OVERRIDE; + + // Helper method for transforming an array of valid enumeration values + // to the std::vector<int> expected by HISTOGRAM_CUSTOM_ENUMERATION. + // This function ensures that a guard bucket exists right after any + // valid sample value (unless the next higher sample is also a valid value), + // so that invalid samples never fall into the same bucket as valid samples. + // TODO(kaiwang): Change name to ArrayToCustomEnumRanges. + static std::vector<Sample> ArrayToCustomRanges(const Sample* values, + size_t num_values); + + // Helper for deserializing CustomHistograms. |*ranges| should already be + // correctly sized before this call. Return true on success. + static bool DeserializeRanges(PickleIterator* iter, + std::vector<Sample>* ranges); + protected: + CustomHistogram(const std::string& name, + const BucketRanges* ranges); + + virtual bool SerializeRanges(Pickle* pickle) const OVERRIDE; + + virtual double GetBucketSize(Count current, size_t i) const OVERRIDE; + + private: + static bool ValidateCustomRanges(const std::vector<Sample>& custom_ranges); + static BucketRanges* CreateBucketRangesFromCustomRanges( + const std::vector<Sample>& custom_ranges); + + DISALLOW_COPY_AND_ASSIGN(CustomHistogram); +}; + +} // namespace base + +#endif // BASE_METRICS_HISTOGRAM_H_
diff --git a/src/base/metrics/histogram_base.cc b/src/base/metrics/histogram_base.cc new file mode 100644 index 0000000..bcfb57f --- /dev/null +++ b/src/base/metrics/histogram_base.cc
@@ -0,0 +1,67 @@ +// 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/metrics/histogram_base.h" + +#include <climits> + +#include "base/logging.h" +#include "base/json/json_string_value_serializer.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" + +namespace base { + +std::string HistogramTypeToString(HistogramType type) { + switch(type) { + case HISTOGRAM: + return "HISTOGRAM"; + case LINEAR_HISTOGRAM: + return "LINEAR_HISTOGRAM"; + case BOOLEAN_HISTOGRAM: + return "BOOLEAN_HISTOGRAM"; + case CUSTOM_HISTOGRAM: + return "CUSTOM_HISTOGRAM"; + case SPARSE_HISTOGRAM: + return "SPARSE_HISTOGRAM"; + default: + NOTREACHED(); + } + return "UNKNOWN"; +} + +const HistogramBase::Sample HistogramBase::kSampleType_MAX = INT_MAX; + +HistogramBase::HistogramBase(const std::string& name) + : histogram_name_(name), + flags_(kNoFlags) {} + +HistogramBase::~HistogramBase() {} + +void HistogramBase::SetFlags(int32 flags) { + flags_ |= flags; +} + +void HistogramBase::ClearFlags(int32 flags) { + flags_ &= ~flags; +} + +void HistogramBase::WriteJSON(std::string* output) const { + Count count; + scoped_ptr<ListValue> buckets(new ListValue()); + GetCountAndBucketData(&count, buckets.get()); + scoped_ptr<DictionaryValue> parameters(new DictionaryValue()); + GetParameters(parameters.get()); + + JSONStringValueSerializer serializer(output); + DictionaryValue root; + root.SetString("name", histogram_name()); + root.SetInteger("count", count); + root.SetInteger("flags", flags()); + root.Set("params", parameters.release()); + root.Set("buckets", buckets.release()); + serializer.Serialize(root); +} + +} // namespace base
diff --git a/src/base/metrics/histogram_base.h b/src/base/metrics/histogram_base.h new file mode 100644 index 0000000..302795a --- /dev/null +++ b/src/base/metrics/histogram_base.h
@@ -0,0 +1,110 @@ +// 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_METRICS_HISTOGRAM_BASE_H_ +#define BASE_METRICS_HISTOGRAM_BASE_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" + +namespace base { + +class DictionaryValue; +class HistogramSamples; +class ListValue; + +//////////////////////////////////////////////////////////////////////////////// +// These enums are used to facilitate deserialization of histograms from other +// processes into the browser. If you create another class that inherits from +// HistogramBase, add new histogram types and names below. + +enum BASE_EXPORT HistogramType { + HISTOGRAM, + LINEAR_HISTOGRAM, + BOOLEAN_HISTOGRAM, + CUSTOM_HISTOGRAM, + SPARSE_HISTOGRAM, +}; + +std::string HistogramTypeToString(HistogramType type); + +//////////////////////////////////////////////////////////////////////////////// + +class BASE_EXPORT HistogramBase { + public: + typedef int Sample; // Used for samples. + typedef int Count; // Used to count samples. + + static const Sample kSampleType_MAX; // INT_MAX + + enum Flags { + kNoFlags = 0, + kUmaTargetedHistogramFlag = 0x1, // Histogram should be UMA uploaded. + + // Indicate that the histogram was pickled to be sent across an IPC Channel. + // If we observe this flag on a histogram being aggregated into after IPC, + // then we are running in a single process mode, and the aggregation should + // not take place (as we would be aggregating back into the source + // histogram!). + kIPCSerializationSourceFlag = 0x10, + + // Only for Histogram and its sub classes: fancy bucket-naming support. + kHexRangePrintingFlag = 0x8000, + }; + + HistogramBase(const std::string& name); + virtual ~HistogramBase(); + + std::string histogram_name() const { return histogram_name_; } + + // Operations with Flags enum. + int32 flags() const { return flags_; } + void SetFlags(int32 flags); + void ClearFlags(int32 flags); + + virtual HistogramType GetHistogramType() const = 0; + + // Whether the histogram has construction arguments as parameters specified. + // For histograms that don't have the concept of minimum, maximum or + // bucket_count, this function always returns false. + virtual bool HasConstructionArguments(Sample minimum, + Sample maximum, + size_t bucket_count) const = 0; + + virtual void Add(Sample value) = 0; + + // Snapshot the current complete set of sample data. + // Override with atomic/locked snapshot if needed. + virtual scoped_ptr<HistogramSamples> SnapshotSamples() const = 0; + + // The following methods provide graphical histogram displays. + virtual void WriteHTMLGraph(std::string* output) const = 0; + virtual void WriteAscii(std::string* output) const = 0; + + // Produce a JSON representation of the histogram. This is implemented with + // the help of GetParameters and GetCountAndBucketData; overwrite them to + // customize the output. + void WriteJSON(std::string* output) const; + +protected: + // Writes information about the construction parameters in |params|. + virtual void GetParameters(DictionaryValue* params) const = 0; + + // Writes information about the current (non-empty) buckets and their sample + // counts to |buckets| and the total sample count to |count|. + virtual void GetCountAndBucketData(Count* count, + ListValue* buckets) const = 0; + private: + const std::string histogram_name_; + int32 flags_; + + DISALLOW_COPY_AND_ASSIGN(HistogramBase); +}; + +} // namespace base + +#endif // BASE_METRICS_HISTOGRAM_BASE_H_
diff --git a/src/base/metrics/histogram_flattener.h b/src/base/metrics/histogram_flattener.h new file mode 100644 index 0000000..6dd169e --- /dev/null +++ b/src/base/metrics/histogram_flattener.h
@@ -0,0 +1,52 @@ +// 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_METRICS_HISTOGRAM_FLATTENER_H_ +#define BASE_METRICS_HISTOGRAM_FLATTENER_H_ + +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram.h" + +namespace base { + +class HistogramSamples; + +// HistogramFlattener is an interface used by HistogramSnapshotManager, which +// handles the logistics of gathering up available histograms for recording. +// The implementors handle the exact lower level recording mechanism, or +// error report mechanism. +class BASE_EXPORT HistogramFlattener { + public: + virtual void RecordDelta(const Histogram& histogram, + const HistogramSamples& snapshot) = 0; + + // Will be called each time a type of Inconsistenies is seen on a histogram, + // during inspections done internally in HistogramSnapshotManager class. + virtual void InconsistencyDetected(Histogram::Inconsistencies problem) = 0; + + // Will be called when a type of Inconsistenies is seen for the first time + // on a histogram. + virtual void UniqueInconsistencyDetected( + Histogram::Inconsistencies problem) = 0; + + // Will be called when the total logged sample count of a histogram + // differs from the sum of logged sample count in all the buckets. The + // argument |amount| is the non-zero discrepancy. + virtual void InconsistencyDetectedInLoggedCount(int amount) = 0; + + protected: + HistogramFlattener() {} + virtual ~HistogramFlattener() {} + + private: + DISALLOW_COPY_AND_ASSIGN(HistogramFlattener); +}; + +} // namespace base + +#endif // BASE_METRICS_HISTOGRAM_FLATTENER_H_
diff --git a/src/base/metrics/histogram_samples.cc b/src/base/metrics/histogram_samples.cc new file mode 100644 index 0000000..e375952 --- /dev/null +++ b/src/base/metrics/histogram_samples.cc
@@ -0,0 +1,126 @@ +// 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/metrics/histogram_samples.h" + +#include "base/compiler_specific.h" +#include "base/pickle.h" + +namespace base { + +namespace { + +class SampleCountPickleIterator : public SampleCountIterator { + public: + SampleCountPickleIterator(PickleIterator* iter); + + virtual bool Done() const OVERRIDE; + virtual void Next() OVERRIDE; + virtual void Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const OVERRIDE; + private: + PickleIterator* const iter_; + + HistogramBase::Sample min_; + HistogramBase::Sample max_; + HistogramBase::Count count_; + bool is_done_; +}; + +SampleCountPickleIterator::SampleCountPickleIterator(PickleIterator* iter) + : iter_(iter), + is_done_(false) { + Next(); +} + +bool SampleCountPickleIterator::Done() const { + return is_done_; +} + +void SampleCountPickleIterator::Next() { + DCHECK(!Done()); + if (!iter_->ReadInt(&min_) || + !iter_->ReadInt(&max_) || + !iter_->ReadInt(&count_)) + is_done_ = true; +} + +void SampleCountPickleIterator::Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const { + DCHECK(!Done()); + *min = min_; + *max = max_; + *count = count_; +} + +} // namespace + +HistogramSamples::HistogramSamples() : sum_(0), redundant_count_(0) {} + +HistogramSamples::~HistogramSamples() {} + +void HistogramSamples::Add(const HistogramSamples& other) { + sum_ += other.sum(); + redundant_count_ += other.redundant_count(); + bool success = AddSubtractImpl(other.Iterator().get(), ADD); + DCHECK(success); +} + +bool HistogramSamples::AddFromPickle(PickleIterator* iter) { + int64 sum; + HistogramBase::Count redundant_count; + + if (!iter->ReadInt64(&sum) || !iter->ReadInt(&redundant_count)) + return false; + sum_ += sum; + redundant_count_ += redundant_count; + + SampleCountPickleIterator pickle_iter(iter); + return AddSubtractImpl(&pickle_iter, ADD); +} + +void HistogramSamples::Subtract(const HistogramSamples& other) { + sum_ -= other.sum(); + redundant_count_ -= other.redundant_count(); + bool success = AddSubtractImpl(other.Iterator().get(), SUBTRACT); + DCHECK(success); +} + +bool HistogramSamples::Serialize(Pickle* pickle) const { + if (!pickle->WriteInt64(sum_) || !pickle->WriteInt(redundant_count_)) + return false; + + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + for (scoped_ptr<SampleCountIterator> it = Iterator(); + !it->Done(); + it->Next()) { + it->Get(&min, &max, &count); + if (!pickle->WriteInt(min) || + !pickle->WriteInt(max) || + !pickle->WriteInt(count)) + return false; + } + return true; +} + +void HistogramSamples::IncreaseSum(int64 diff) { + sum_ += diff; +} + +void HistogramSamples::IncreaseRedundantCount(HistogramBase::Count diff) { + redundant_count_ += diff; +} + +SampleCountIterator::~SampleCountIterator() {} + +bool SampleCountIterator::GetBucketIndex(size_t* index) const { + DCHECK(!Done()); + return false; +} + +} // namespace base
diff --git a/src/base/metrics/histogram_samples.h b/src/base/metrics/histogram_samples.h new file mode 100644 index 0000000..c55b584 --- /dev/null +++ b/src/base/metrics/histogram_samples.h
@@ -0,0 +1,87 @@ +// 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_METRICS_HISTOGRAM_SAMPLES_H_ +#define BASE_METRICS_HISTOGRAM_SAMPLES_H_ + +#include "base/basictypes.h" +#include "base/metrics/histogram_base.h" +#include "base/memory/scoped_ptr.h" + +class Pickle; +class PickleIterator; + +namespace base { + +class SampleCountIterator; + +// HistogramSamples is a container storing all samples of a histogram. +class BASE_EXPORT HistogramSamples { + public: + HistogramSamples(); + virtual ~HistogramSamples(); + + virtual void Accumulate(HistogramBase::Sample value, + HistogramBase::Count count) = 0; + virtual HistogramBase::Count GetCount(HistogramBase::Sample value) const = 0; + virtual HistogramBase::Count TotalCount() const = 0; + + virtual void Add(const HistogramSamples& other); + + // Add from serialized samples. + virtual bool AddFromPickle(PickleIterator* iter); + + virtual void Subtract(const HistogramSamples& other); + + virtual scoped_ptr<SampleCountIterator> Iterator() const = 0; + virtual bool Serialize(Pickle* pickle) const; + + // Accessor fuctions. + int64 sum() const { return sum_; } + HistogramBase::Count redundant_count() const { return redundant_count_; } + + protected: + // Based on |op| type, add or subtract sample counts data from the iterator. + enum Operator { ADD, SUBTRACT }; + virtual bool AddSubtractImpl(SampleCountIterator* iter, Operator op) = 0; + + void IncreaseSum(int64 diff); + void IncreaseRedundantCount(HistogramBase::Count diff); + + private: + int64 sum_; + + // |redundant_count_| helps identify memory corruption. It redundantly stores + // the total number of samples accumulated in the histogram. We can compare + // this count to the sum of the counts (TotalCount() function), and detect + // problems. Note, depending on the implementation of different histogram + // types, there might be races during histogram accumulation and snapshotting + // that we choose to accept. In this case, the tallies might mismatch even + // when no memory corruption has happened. + HistogramBase::Count redundant_count_; +}; + +class BASE_EXPORT SampleCountIterator { + public: + virtual ~SampleCountIterator(); + + virtual bool Done() const = 0; + virtual void Next() = 0; + + // Get the sample and count at current position. + // |min| |max| and |count| can be NULL if the value is not of interest. + // Requires: !Done(); + virtual void Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const = 0; + + // Get the index of current histogram bucket. + // For histograms that don't use predefined buckets, it returns false. + // Requires: !Done(); + virtual bool GetBucketIndex(size_t* index) const; +}; + +} // namespace base + +#endif // BASE_METRICS_HISTOGRAM_SAMPLES_H_
diff --git a/src/base/metrics/histogram_snapshot_manager.cc b/src/base/metrics/histogram_snapshot_manager.cc new file mode 100644 index 0000000..ad054b1 --- /dev/null +++ b/src/base/metrics/histogram_snapshot_manager.cc
@@ -0,0 +1,117 @@ +// 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/metrics/histogram_snapshot_manager.h" + +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram_flattener.h" +#include "base/metrics/histogram_samples.h" +#include "base/metrics/statistics_recorder.h" +#include "base/stl_util.h" + +using std::map; +using std::string; + +namespace base { + +HistogramSnapshotManager::HistogramSnapshotManager( + HistogramFlattener* histogram_flattener) + : histogram_flattener_(histogram_flattener) { + DCHECK(histogram_flattener_); +} + +HistogramSnapshotManager::~HistogramSnapshotManager() { + STLDeleteValues(&logged_samples_); +} + +void HistogramSnapshotManager::PrepareDeltas(Histogram::Flags flag_to_set, + bool record_only_uma) { + StatisticsRecorder::Histograms histograms; + StatisticsRecorder::GetHistograms(&histograms); + for (StatisticsRecorder::Histograms::const_iterator it = histograms.begin(); + histograms.end() != it; + ++it) { + (*it)->SetFlags(flag_to_set); + if (record_only_uma && + 0 == ((*it)->flags() & Histogram::kUmaTargetedHistogramFlag)) + continue; + PrepareDelta(**it); + } +} + +void HistogramSnapshotManager::PrepareDelta(const Histogram& histogram) { + DCHECK(histogram_flattener_); + + // Get up-to-date snapshot of sample stats. + scoped_ptr<HistogramSamples> snapshot(histogram.SnapshotSamples()); + const std::string& histogram_name = histogram.histogram_name(); + + int corruption = histogram.FindCorruption(*snapshot); + + // Crash if we detect that our histograms have been overwritten. This may be + // a fair distance from the memory smasher, but we hope to correlate these + // crashes with other events, such as plugins, or usage patterns, etc. + if (Histogram::BUCKET_ORDER_ERROR & corruption) { + // The checksum should have caught this, so crash separately if it didn't. + CHECK_NE(0, Histogram::RANGE_CHECKSUM_ERROR & corruption); + CHECK(false); // Crash for the bucket order corruption. + } + // Checksum corruption might not have caused order corruption. + CHECK_EQ(0, Histogram::RANGE_CHECKSUM_ERROR & corruption); + + // Note, at this point corruption can only be COUNT_HIGH_ERROR or + // COUNT_LOW_ERROR and they never arise together, so we don't need to extract + // bits from corruption. + if (corruption) { + DLOG(ERROR) << "Histogram: " << histogram_name + << " has data corruption: " << corruption; + histogram_flattener_->InconsistencyDetected( + static_cast<Histogram::Inconsistencies>(corruption)); + // Don't record corrupt data to metrics services. + int old_corruption = inconsistencies_[histogram_name]; + if (old_corruption == (corruption | old_corruption)) + return; // We've already seen this corruption for this histogram. + inconsistencies_[histogram_name] |= corruption; + histogram_flattener_->UniqueInconsistencyDetected( + static_cast<Histogram::Inconsistencies>(corruption)); + return; + } + + HistogramSamples* to_log; + map<string, HistogramSamples*>::iterator it = + logged_samples_.find(histogram_name); + if (it == logged_samples_.end()) { + to_log = snapshot.release(); + + // This histogram has not been logged before, add a new entry. + logged_samples_[histogram_name] = to_log; + } else { + HistogramSamples* already_logged = it->second; + InspectLoggedSamplesInconsistency(*snapshot, already_logged); + snapshot->Subtract(*already_logged); + already_logged->Add(*snapshot); + to_log = snapshot.get(); + } + + if (to_log->redundant_count() > 0) + histogram_flattener_->RecordDelta(histogram, *to_log); +} + +void HistogramSnapshotManager::InspectLoggedSamplesInconsistency( + const HistogramSamples& new_snapshot, + HistogramSamples* logged_samples) { + HistogramBase::Count discrepancy = + logged_samples->TotalCount() - logged_samples->redundant_count(); + if (!discrepancy) + return; + + histogram_flattener_->InconsistencyDetectedInLoggedCount(discrepancy); + if (discrepancy > Histogram::kCommonRaceBasedCountMismatch) { + // Fix logged_samples. + logged_samples->Subtract(*logged_samples); + logged_samples->Add(new_snapshot); + } +} + +} // namespace base
diff --git a/src/base/metrics/histogram_snapshot_manager.h b/src/base/metrics/histogram_snapshot_manager.h new file mode 100644 index 0000000..0ec1ac3 --- /dev/null +++ b/src/base/metrics/histogram_snapshot_manager.h
@@ -0,0 +1,61 @@ +// 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_METRICS_HISTOGRAM_SNAPSHOT_MANAGER_H_ +#define BASE_METRICS_HISTOGRAM_SNAPSHOT_MANAGER_H_ + +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/metrics/histogram.h" + +namespace base { + +class HistogramSamples; +class HistogramFlattener; + +// HistogramSnapshotManager handles the logistics of gathering up available +// histograms for recording either to disk or for transmission (such as from +// renderer to browser, or from browser to UMA upload). Since histograms can sit +// in memory for an extended period of time, and are vulnerable to memory +// corruption, this class also validates as much rendundancy as it can before +// calling for the marginal change (a.k.a., delta) in a histogram to be +// recorded. +class BASE_EXPORT HistogramSnapshotManager { + public: + explicit HistogramSnapshotManager(HistogramFlattener* histogram_flattener); + virtual ~HistogramSnapshotManager(); + + // Snapshot all histograms, and ask |histogram_flattener_| to record the + // delta. The arguments allow selecting only a subset of histograms for + // recording, or to set a flag in each recorded histogram. + void PrepareDeltas(Histogram::Flags flags_to_set, bool record_only_uma); + + private: + // Snapshot this histogram, and record the delta. + void PrepareDelta(const Histogram& histogram); + + // Try to detect and fix count inconsistency of logged samples. + void InspectLoggedSamplesInconsistency( + const HistogramSamples& new_snapshot, + HistogramSamples* logged_samples); + + // For histograms, track what we've already recorded (as a sample for + // each histogram) so that we can record only the delta with the next log. + std::map<std::string, HistogramSamples*> logged_samples_; + + // List of histograms found to be corrupt, and their problems. + std::map<std::string, int> inconsistencies_; + + // |histogram_flattener_| handles the logistics of recording the histogram + // deltas. + HistogramFlattener* histogram_flattener_; // Weak. + + DISALLOW_COPY_AND_ASSIGN(HistogramSnapshotManager); +}; + +} // namespace base + +#endif // BASE_METRICS_HISTOGRAM_SNAPSHOT_MANAGER_H_
diff --git a/src/base/metrics/histogram_unittest.cc b/src/base/metrics/histogram_unittest.cc new file mode 100644 index 0000000..9fb9422 --- /dev/null +++ b/src/base/metrics/histogram_unittest.cc
@@ -0,0 +1,404 @@ +// 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. + +// Test of Histogram class + +#include <climits> +#include <algorithm> +#include <vector> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/bucket_ranges.h" +#include "base/metrics/histogram.h" +#include "base/metrics/sample_vector.h" +#include "base/metrics/statistics_recorder.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::vector; + +namespace base { + +class HistogramTest : public testing::Test { + protected: + virtual void SetUp() { + // Each test will have a clean state (no Histogram / BucketRanges + // registered). + InitializeStatisticsRecorder(); + } + + virtual void TearDown() { + UninitializeStatisticsRecorder(); + } + + void InitializeStatisticsRecorder() { + statistics_recorder_ = new StatisticsRecorder(); + } + + void UninitializeStatisticsRecorder() { + delete statistics_recorder_; + statistics_recorder_ = NULL; + } + + StatisticsRecorder* statistics_recorder_; +}; + +// Check for basic syntax and use. +TEST_F(HistogramTest, BasicTest) { + // Try basic construction + Histogram* histogram(Histogram::FactoryGet( + "TestHistogram", 1, 1000, 10, Histogram::kNoFlags)); + EXPECT_NE(reinterpret_cast<Histogram*>(NULL), histogram); + + Histogram* linear_histogram(LinearHistogram::FactoryGet( + "TestLinearHistogram", 1, 1000, 10, Histogram::kNoFlags)); + EXPECT_NE(reinterpret_cast<Histogram*>(NULL), linear_histogram); + + vector<int> custom_ranges; + custom_ranges.push_back(1); + custom_ranges.push_back(5); + Histogram* custom_histogram(CustomHistogram::FactoryGet( + "TestCustomHistogram", custom_ranges, Histogram::kNoFlags)); + EXPECT_NE(reinterpret_cast<Histogram*>(NULL), custom_histogram); + + // Use standard macros (but with fixed samples) + HISTOGRAM_TIMES("Test2Histogram", TimeDelta::FromDays(1)); + HISTOGRAM_COUNTS("Test3Histogram", 30); + + DHISTOGRAM_TIMES("Test4Histogram", TimeDelta::FromDays(1)); + DHISTOGRAM_COUNTS("Test5Histogram", 30); + + HISTOGRAM_ENUMERATION("Test6Histogram", 129, 130); +} + +// Check that the macro correctly matches histograms by name and records their +// data together. +TEST_F(HistogramTest, NameMatchTest) { + HISTOGRAM_PERCENTAGE("DuplicatedHistogram", 10); + HISTOGRAM_PERCENTAGE("DuplicatedHistogram", 10); + Histogram* histogram(LinearHistogram::FactoryGet( + "DuplicatedHistogram", 1, 101, 102, Histogram::kNoFlags)); + scoped_ptr<SampleVector> samples = histogram->SnapshotSampleVector(); + EXPECT_EQ(2, samples->TotalCount()); + EXPECT_EQ(2, samples->GetCountAtIndex(10)); +} + +TEST_F(HistogramTest, ExponentialRangesTest) { + // Check that we got a nice exponential when there was enough rooom. + BucketRanges ranges(9); + Histogram::InitializeBucketRanges(1, 64, 8, &ranges); + EXPECT_EQ(0, ranges.range(0)); + int power_of_2 = 1; + for (int i = 1; i < 8; i++) { + EXPECT_EQ(power_of_2, ranges.range(i)); + power_of_2 *= 2; + } + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges.range(8)); + + // Check the corresponding Histogram will use the correct ranges. + Histogram* histogram(Histogram::FactoryGet( + "Histogram", 1, 64, 8, Histogram::kNoFlags)); + EXPECT_TRUE(ranges.Equals(histogram->bucket_ranges())); + + // When bucket count is limited, exponential ranges will partially look like + // linear. + BucketRanges ranges2(16); + Histogram::InitializeBucketRanges(1, 32, 15, &ranges2); + + EXPECT_EQ(0, ranges2.range(0)); + EXPECT_EQ(1, ranges2.range(1)); + EXPECT_EQ(2, ranges2.range(2)); + EXPECT_EQ(3, ranges2.range(3)); + EXPECT_EQ(4, ranges2.range(4)); + EXPECT_EQ(5, ranges2.range(5)); + EXPECT_EQ(6, ranges2.range(6)); + EXPECT_EQ(7, ranges2.range(7)); + EXPECT_EQ(9, ranges2.range(8)); + EXPECT_EQ(11, ranges2.range(9)); + EXPECT_EQ(14, ranges2.range(10)); + EXPECT_EQ(17, ranges2.range(11)); + EXPECT_EQ(21, ranges2.range(12)); + EXPECT_EQ(26, ranges2.range(13)); + EXPECT_EQ(32, ranges2.range(14)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges2.range(15)); + + // Check the corresponding Histogram will use the correct ranges. + Histogram* histogram2(Histogram::FactoryGet( + "Histogram2", 1, 32, 15, Histogram::kNoFlags)); + EXPECT_TRUE(ranges2.Equals(histogram2->bucket_ranges())); +} + +TEST_F(HistogramTest, LinearRangesTest) { + BucketRanges ranges(9); + LinearHistogram::InitializeBucketRanges(1, 7, 8, &ranges); + // Gets a nice linear set of bucket ranges. + for (int i = 0; i < 8; i++) + EXPECT_EQ(i, ranges.range(i)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges.range(8)); + // The correspoding LinearHistogram should use the correct ranges. + Histogram* histogram( + LinearHistogram::FactoryGet("Linear", 1, 7, 8, Histogram::kNoFlags)); + EXPECT_TRUE(ranges.Equals(histogram->bucket_ranges())); + + // Linear ranges are not divisible. + BucketRanges ranges2(6); + LinearHistogram::InitializeBucketRanges(1, 6, 5, &ranges2); + EXPECT_EQ(0, ranges2.range(0)); + EXPECT_EQ(1, ranges2.range(1)); + EXPECT_EQ(3, ranges2.range(2)); + EXPECT_EQ(4, ranges2.range(3)); + EXPECT_EQ(6, ranges2.range(4)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges2.range(5)); + // The correspoding LinearHistogram should use the correct ranges. + Histogram* histogram2( + LinearHistogram::FactoryGet("Linear2", 1, 6, 5, Histogram::kNoFlags)); + EXPECT_TRUE(ranges2.Equals(histogram2->bucket_ranges())); +} + +TEST_F(HistogramTest, ArrayToCustomRangesTest) { + const HistogramBase::Sample ranges[3] = {5, 10, 20}; + vector<HistogramBase::Sample> ranges_vec = + CustomHistogram::ArrayToCustomRanges(ranges, 3); + ASSERT_EQ(6u, ranges_vec.size()); + EXPECT_EQ(5, ranges_vec[0]); + EXPECT_EQ(6, ranges_vec[1]); + EXPECT_EQ(10, ranges_vec[2]); + EXPECT_EQ(11, ranges_vec[3]); + EXPECT_EQ(20, ranges_vec[4]); + EXPECT_EQ(21, ranges_vec[5]); +} + +TEST_F(HistogramTest, CustomHistogramTest) { + // A well prepared custom ranges. + vector<HistogramBase::Sample> custom_ranges; + custom_ranges.push_back(1); + custom_ranges.push_back(2); + Histogram* histogram = CustomHistogram::FactoryGet( + "TestCustomHistogram1", custom_ranges, Histogram::kNoFlags); + const BucketRanges* ranges = histogram->bucket_ranges(); + ASSERT_EQ(4u, ranges->size()); + EXPECT_EQ(0, ranges->range(0)); // Auto added. + EXPECT_EQ(1, ranges->range(1)); + EXPECT_EQ(2, ranges->range(2)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges->range(3)); // Auto added. + + // A unordered custom ranges. + custom_ranges.clear(); + custom_ranges.push_back(2); + custom_ranges.push_back(1); + histogram = CustomHistogram::FactoryGet( + "TestCustomHistogram2", custom_ranges, Histogram::kNoFlags); + ranges = histogram->bucket_ranges(); + ASSERT_EQ(4u, ranges->size()); + EXPECT_EQ(0, ranges->range(0)); + EXPECT_EQ(1, ranges->range(1)); + EXPECT_EQ(2, ranges->range(2)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges->range(3)); + + // A custom ranges with duplicated values. + custom_ranges.clear(); + custom_ranges.push_back(4); + custom_ranges.push_back(1); + custom_ranges.push_back(4); + histogram = CustomHistogram::FactoryGet( + "TestCustomHistogram3", custom_ranges, Histogram::kNoFlags); + ranges = histogram->bucket_ranges(); + ASSERT_EQ(4u, ranges->size()); + EXPECT_EQ(0, ranges->range(0)); + EXPECT_EQ(1, ranges->range(1)); + EXPECT_EQ(4, ranges->range(2)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges->range(3)); +} + +TEST_F(HistogramTest, CustomHistogramWithOnly2Buckets) { + // This test exploits the fact that the CustomHistogram can have 2 buckets, + // while the base class Histogram is *supposed* to have at least 3 buckets. + // We should probably change the restriction on the base class (or not inherit + // the base class!). + + vector<HistogramBase::Sample> custom_ranges; + custom_ranges.push_back(4); + + Histogram* histogram = CustomHistogram::FactoryGet( + "2BucketsCustomHistogram", custom_ranges, Histogram::kNoFlags); + const BucketRanges* ranges = histogram->bucket_ranges(); + ASSERT_EQ(3u, ranges->size()); + EXPECT_EQ(0, ranges->range(0)); + EXPECT_EQ(4, ranges->range(1)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges->range(2)); +} + +// Make sure histogram handles out-of-bounds data gracefully. +TEST_F(HistogramTest, BoundsTest) { + const size_t kBucketCount = 50; + Histogram* histogram(Histogram::FactoryGet( + "Bounded", 10, 100, kBucketCount, Histogram::kNoFlags)); + + // Put two samples "out of bounds" above and below. + histogram->Add(5); + histogram->Add(-50); + + histogram->Add(100); + histogram->Add(10000); + + // Verify they landed in the underflow, and overflow buckets. + scoped_ptr<SampleVector> samples = histogram->SnapshotSampleVector(); + EXPECT_EQ(2, samples->GetCountAtIndex(0)); + EXPECT_EQ(0, samples->GetCountAtIndex(1)); + size_t array_size = histogram->bucket_count(); + EXPECT_EQ(kBucketCount, array_size); + EXPECT_EQ(0, samples->GetCountAtIndex(array_size - 2)); + EXPECT_EQ(2, samples->GetCountAtIndex(array_size - 1)); + + vector<int> custom_ranges; + custom_ranges.push_back(10); + custom_ranges.push_back(50); + custom_ranges.push_back(100); + Histogram* test_custom_histogram(CustomHistogram::FactoryGet( + "TestCustomRangeBoundedHistogram", custom_ranges, Histogram::kNoFlags)); + + // Put two samples "out of bounds" above and below. + test_custom_histogram->Add(5); + test_custom_histogram->Add(-50); + test_custom_histogram->Add(100); + test_custom_histogram->Add(1000); + test_custom_histogram->Add(INT_MAX); + + // Verify they landed in the underflow, and overflow buckets. + scoped_ptr<SampleVector> custom_samples = + test_custom_histogram->SnapshotSampleVector(); + EXPECT_EQ(2, custom_samples->GetCountAtIndex(0)); + EXPECT_EQ(0, custom_samples->GetCountAtIndex(1)); + size_t bucket_count = test_custom_histogram->bucket_count(); + EXPECT_EQ(0, custom_samples->GetCountAtIndex(bucket_count - 2)); + EXPECT_EQ(3, custom_samples->GetCountAtIndex(bucket_count - 1)); +} + +// Check to be sure samples land as expected is "correct" buckets. +TEST_F(HistogramTest, BucketPlacementTest) { + Histogram* histogram(Histogram::FactoryGet( + "Histogram", 1, 64, 8, Histogram::kNoFlags)); + + // Add i+1 samples to the i'th bucket. + histogram->Add(0); + int power_of_2 = 1; + for (int i = 1; i < 8; i++) { + for (int j = 0; j <= i; j++) + histogram->Add(power_of_2); + power_of_2 *= 2; + } + + // Check to see that the bucket counts reflect our additions. + scoped_ptr<SampleVector> samples = histogram->SnapshotSampleVector(); + for (int i = 0; i < 8; i++) + EXPECT_EQ(i + 1, samples->GetCountAtIndex(i)); +} + +TEST_F(HistogramTest, CorruptSampleCounts) { + Histogram* histogram(Histogram::FactoryGet( + "Histogram", 1, 64, 8, Histogram::kNoFlags)); // As per header file. + + // Add some samples. + histogram->Add(20); + histogram->Add(40); + + scoped_ptr<SampleVector> snapshot = histogram->SnapshotSampleVector(); + EXPECT_EQ(Histogram::NO_INCONSISTENCIES, + histogram->FindCorruption(*snapshot)); + EXPECT_EQ(2, snapshot->redundant_count()); + EXPECT_EQ(2, snapshot->TotalCount()); + + snapshot->counts_[3] += 100; // Sample count won't match redundant count. + EXPECT_EQ(Histogram::COUNT_LOW_ERROR, + histogram->FindCorruption(*snapshot)); + snapshot->counts_[2] -= 200; + EXPECT_EQ(Histogram::COUNT_HIGH_ERROR, + histogram->FindCorruption(*snapshot)); + + // But we can't spot a corruption if it is compensated for. + snapshot->counts_[1] += 100; + EXPECT_EQ(Histogram::NO_INCONSISTENCIES, + histogram->FindCorruption(*snapshot)); +} + +TEST_F(HistogramTest, CorruptBucketBounds) { + Histogram* histogram(Histogram::FactoryGet( + "Histogram", 1, 64, 8, Histogram::kNoFlags)); // As per header file. + + scoped_ptr<SampleVector> snapshot = histogram->SnapshotSampleVector(); + EXPECT_EQ(Histogram::NO_INCONSISTENCIES, + histogram->FindCorruption(*snapshot)); + + BucketRanges* bucket_ranges = + const_cast<BucketRanges*>(histogram->bucket_ranges()); + HistogramBase::Sample tmp = bucket_ranges->range(1); + bucket_ranges->set_range(1, bucket_ranges->range(2)); + bucket_ranges->set_range(2, tmp); + EXPECT_EQ(Histogram::BUCKET_ORDER_ERROR | Histogram::RANGE_CHECKSUM_ERROR, + histogram->FindCorruption(*snapshot)); + + bucket_ranges->set_range(2, bucket_ranges->range(1)); + bucket_ranges->set_range(1, tmp); + EXPECT_EQ(0, histogram->FindCorruption(*snapshot)); + + // Show that two simple changes don't offset each other + bucket_ranges->set_range(3, bucket_ranges->range(3) + 1); + EXPECT_EQ(Histogram::RANGE_CHECKSUM_ERROR, + histogram->FindCorruption(*snapshot)); + + bucket_ranges->set_range(4, bucket_ranges->range(4) - 1); + EXPECT_EQ(Histogram::RANGE_CHECKSUM_ERROR, + histogram->FindCorruption(*snapshot)); + + // Repair histogram so that destructor won't DCHECK(). + bucket_ranges->set_range(3, bucket_ranges->range(3) - 1); + bucket_ranges->set_range(4, bucket_ranges->range(4) + 1); +} + +#if GTEST_HAS_DEATH_TEST +// For Histogram, LinearHistogram and CustomHistogram, the minimum for a +// declared range is 1, while the maximum is (HistogramBase::kSampleType_MAX - +// 1). But we accept ranges exceeding those limits, and silently clamped to +// those limits. This is for backwards compatibility. +TEST(HistogramDeathTest, BadRangesTest) { + Histogram* histogram = Histogram::FactoryGet( + "BadRanges", 0, HistogramBase::kSampleType_MAX, 8, Histogram::kNoFlags); + EXPECT_EQ(1, histogram->declared_min()); + EXPECT_EQ(HistogramBase::kSampleType_MAX - 1, histogram->declared_max()); + + Histogram* linear_histogram = LinearHistogram::FactoryGet( + "BadRangesLinear", 0, HistogramBase::kSampleType_MAX, 8, + Histogram::kNoFlags); + EXPECT_EQ(1, linear_histogram->declared_min()); + EXPECT_EQ(HistogramBase::kSampleType_MAX - 1, + linear_histogram->declared_max()); + + vector<int> custom_ranges; + custom_ranges.push_back(0); + custom_ranges.push_back(5); + Histogram* custom_histogram1 = CustomHistogram::FactoryGet( + "BadRangesCustom", custom_ranges, Histogram::kNoFlags); + const BucketRanges* ranges = custom_histogram1->bucket_ranges(); + ASSERT_EQ(3u, ranges->size()); + EXPECT_EQ(0, ranges->range(0)); + EXPECT_EQ(5, ranges->range(1)); + EXPECT_EQ(HistogramBase::kSampleType_MAX, ranges->range(2)); + + // CustomHistogram does not accepts kSampleType_MAX as range. + custom_ranges.push_back(HistogramBase::kSampleType_MAX); + EXPECT_DEATH(CustomHistogram::FactoryGet("BadRangesCustom2", custom_ranges, + Histogram::kNoFlags), + ""); + + // CustomHistogram needs at least 1 valid range. + custom_ranges.clear(); + custom_ranges.push_back(0); + EXPECT_DEATH(CustomHistogram::FactoryGet("BadRangesCustom3", custom_ranges, + Histogram::kNoFlags), + ""); +} +#endif + +} // namespace base
diff --git a/src/base/metrics/sample_map.cc b/src/base/metrics/sample_map.cc new file mode 100644 index 0000000..9bd78e7 --- /dev/null +++ b/src/base/metrics/sample_map.cc
@@ -0,0 +1,91 @@ +// 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/metrics/sample_map.h" + +#include "base/logging.h" + +using std::map; + +namespace base { + +typedef HistogramBase::Count Count; +typedef HistogramBase::Sample Sample; + +SampleMap::SampleMap() {} + +SampleMap::~SampleMap() {} + +void SampleMap::Accumulate(Sample value, Count count) { + sample_counts_[value] += count; + IncreaseSum(count * value); + IncreaseRedundantCount(count); +} + +Count SampleMap::GetCount(Sample value) const { + map<Sample, Count>::const_iterator it = sample_counts_.find(value); + if (it == sample_counts_.end()) + return 0; + return it->second; +} + +Count SampleMap::TotalCount() const { + Count count = 0; + for (map<Sample, Count>::const_iterator it = sample_counts_.begin(); + it != sample_counts_.end(); + ++it) { + count += it->second; + } + return count; +} + +scoped_ptr<SampleCountIterator> SampleMap::Iterator() const { + return scoped_ptr<SampleCountIterator>(new SampleMapIterator(sample_counts_)); +} + +void SampleMap::ResetRedundantCount(Count count) { + IncreaseRedundantCount(-redundant_count()); + IncreaseRedundantCount(count); +} + +bool SampleMap::AddSubtractImpl(SampleCountIterator* iter, + HistogramSamples::Operator op) { + Sample min; + Sample max; + Count count; + for (; !iter->Done(); iter->Next()) { + iter->Get(&min, &max, &count); + if (min + 1 != max) + return false; // SparseHistogram only supports bucket with size 1. + sample_counts_[min] += (op == HistogramSamples::ADD) ? count : -count; + } + return true; +} + +SampleMapIterator::SampleMapIterator(const SampleToCountMap& sample_counts) + : iter_(sample_counts.begin()), + end_(sample_counts.end()) {} + +SampleMapIterator::~SampleMapIterator() {} + +bool SampleMapIterator::Done() const { + return iter_ == end_; +} + +void SampleMapIterator::Next() { + DCHECK(!Done()); + iter_++; +} + +void SampleMapIterator::Get(Sample* min, Sample* max, Count* count) const { + DCHECK(!Done()); + if (min != NULL) + *min = iter_->first; + if (max != NULL) + *max = iter_->first + 1; + if (count != NULL) + *count = iter_->second; +} + +} // namespace base
diff --git a/src/base/metrics/sample_map.h b/src/base/metrics/sample_map.h new file mode 100644 index 0000000..65a31f1 --- /dev/null +++ b/src/base/metrics/sample_map.h
@@ -0,0 +1,67 @@ +// 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. + +// SampleMap implements HistogramSamples interface. It is used by the +// SparseHistogram class to store samples. + +#ifndef BASE_METRICS_SAMPLE_MAP_H_ +#define BASE_METRICS_SAMPLE_MAP_H_ + +#include <map> + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_samples.h" + +namespace base { + +class BASE_EXPORT_PRIVATE SampleMap : public HistogramSamples { + public: + SampleMap(); + virtual ~SampleMap(); + + // HistogramSamples implementation: + virtual void Accumulate(HistogramBase::Sample value, + HistogramBase::Count count) OVERRIDE; + virtual HistogramBase::Count GetCount( + HistogramBase::Sample value) const OVERRIDE; + virtual HistogramBase::Count TotalCount() const OVERRIDE; + virtual scoped_ptr<SampleCountIterator> Iterator() const OVERRIDE; + + void ResetRedundantCount(HistogramBase::Count count); + + protected: + virtual bool AddSubtractImpl( + SampleCountIterator* iter, + HistogramSamples::Operator op) OVERRIDE; // |op| is ADD or SUBTRACT. + + private: + std::map<HistogramBase::Sample, HistogramBase::Count> sample_counts_; + + DISALLOW_COPY_AND_ASSIGN(SampleMap); +}; + +class BASE_EXPORT_PRIVATE SampleMapIterator : public SampleCountIterator { + public: + typedef std::map<HistogramBase::Sample, HistogramBase::Count> + SampleToCountMap; + + SampleMapIterator(const SampleToCountMap& sample_counts); + virtual ~SampleMapIterator(); + + // SampleCountIterator implementation: + virtual bool Done() const OVERRIDE; + virtual void Next() OVERRIDE; + virtual void Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const OVERRIDE; + private: + SampleToCountMap::const_iterator iter_; + const SampleToCountMap::const_iterator end_; +}; + +} // namespace base + +#endif // BASE_METRICS_SAMPLE_MAP_H_
diff --git a/src/base/metrics/sample_map_unittest.cc b/src/base/metrics/sample_map_unittest.cc new file mode 100644 index 0000000..1a53ee7 --- /dev/null +++ b/src/base/metrics/sample_map_unittest.cc
@@ -0,0 +1,123 @@ +// 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/memory/scoped_ptr.h" +#include "base/metrics/sample_map.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +TEST(SampleMapTest, AccumulateTest) { + SampleMap samples; + + samples.Accumulate(1, 100); + samples.Accumulate(2, 200); + samples.Accumulate(1, -200); + EXPECT_EQ(-100, samples.GetCount(1)); + EXPECT_EQ(200, samples.GetCount(2)); + + EXPECT_EQ(300, samples.sum()); + EXPECT_EQ(100, samples.TotalCount()); + EXPECT_EQ(samples.redundant_count(), samples.TotalCount()); +} + +TEST(SampleMapTest, AddSubtractTest) { + SampleMap samples1; + SampleMap samples2; + + samples1.Accumulate(1, 100); + samples1.Accumulate(2, 100); + samples1.Accumulate(3, 100); + + samples2.Accumulate(1, 200); + samples2.Accumulate(2, 200); + samples2.Accumulate(4, 200); + + samples1.Add(samples2); + EXPECT_EQ(300, samples1.GetCount(1)); + EXPECT_EQ(300, samples1.GetCount(2)); + EXPECT_EQ(100, samples1.GetCount(3)); + EXPECT_EQ(200, samples1.GetCount(4)); + EXPECT_EQ(2000, samples1.sum()); + EXPECT_EQ(900, samples1.TotalCount()); + EXPECT_EQ(samples1.redundant_count(), samples1.TotalCount()); + + samples1.Subtract(samples2); + EXPECT_EQ(100, samples1.GetCount(1)); + EXPECT_EQ(100, samples1.GetCount(2)); + EXPECT_EQ(100, samples1.GetCount(3)); + EXPECT_EQ(0, samples1.GetCount(4)); + EXPECT_EQ(600, samples1.sum()); + EXPECT_EQ(300, samples1.TotalCount()); + EXPECT_EQ(samples1.redundant_count(), samples1.TotalCount()); +} + +TEST(SampleMapIteratorTest, IterateTest) { + SampleMap samples; + samples.Accumulate(1, 100); + samples.Accumulate(2, 200); + samples.Accumulate(4, -300); + samples.Accumulate(5, 0); + + scoped_ptr<SampleCountIterator> it = samples.Iterator(); + + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + + it->Get(&min, &max, &count); + EXPECT_EQ(1, min); + EXPECT_EQ(2, max); + EXPECT_EQ(100, count); + EXPECT_FALSE(it->GetBucketIndex(NULL)); + + it->Next(); + it->Get(&min, &max, &count); + EXPECT_EQ(2, min); + EXPECT_EQ(3, max); + EXPECT_EQ(200, count); + + it->Next(); + it->Get(&min, &max, &count); + EXPECT_EQ(4, min); + EXPECT_EQ(5, max); + EXPECT_EQ(-300, count); + + it->Next(); + it->Get(&min, &max, &count); + EXPECT_EQ(5, min); + EXPECT_EQ(6, max); + EXPECT_EQ(0, count); + + it->Next(); + EXPECT_TRUE(it->Done()); +} + +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST + +TEST(SampleMapIteratorDeathTest, IterateDoneTest) { + SampleMap samples; + + scoped_ptr<SampleCountIterator> it = samples.Iterator(); + + EXPECT_TRUE(it->Done()); + + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + EXPECT_DEATH(it->Get(&min, &max, &count), ""); + + EXPECT_DEATH(it->Next(), ""); + + samples.Accumulate(1, 100); + it = samples.Iterator(); + EXPECT_FALSE(it->Done()); +} + +#endif +// (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST + +} // namespace +} // namespace base
diff --git a/src/base/metrics/sample_vector.cc b/src/base/metrics/sample_vector.cc new file mode 100644 index 0000000..8f14510 --- /dev/null +++ b/src/base/metrics/sample_vector.cc
@@ -0,0 +1,161 @@ +// 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/metrics/sample_vector.h" + +#include "base/logging.h" +#include "base/metrics/bucket_ranges.h" + +using std::vector; + +namespace base { + +typedef HistogramBase::Count Count; +typedef HistogramBase::Sample Sample; + +SampleVector::SampleVector(const BucketRanges* bucket_ranges) + : counts_(bucket_ranges->size() - 1), + bucket_ranges_(bucket_ranges) { + CHECK_GE(bucket_ranges_->size(), 2u); +} + +SampleVector::~SampleVector() {} + +void SampleVector::Accumulate(Sample value, Count count) { + size_t bucket_index = GetBucketIndex(value); + counts_[bucket_index] += count; + IncreaseSum(count * value); + IncreaseRedundantCount(count); +} + +Count SampleVector::GetCount(Sample value) const { + size_t bucket_index = GetBucketIndex(value); + return counts_[bucket_index]; +} + +Count SampleVector::TotalCount() const { + Count count = 0; + for (size_t i = 0; i < counts_.size(); i++) { + count += counts_[i]; + } + return count; +} + +Count SampleVector::GetCountAtIndex(size_t bucket_index) const { + DCHECK(bucket_index < counts_.size()); + return counts_[bucket_index]; +} + +scoped_ptr<SampleCountIterator> SampleVector::Iterator() const { + return scoped_ptr<SampleCountIterator>( + new SampleVectorIterator(&counts_, bucket_ranges_)); +} + +bool SampleVector::AddSubtractImpl(SampleCountIterator* iter, + HistogramSamples::Operator op) { + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + + // Go through the iterator and add the counts into correct bucket. + size_t index = 0; + while (index < counts_.size() && !iter->Done()) { + iter->Get(&min, &max, &count); + if (min == bucket_ranges_->range(index) && + max == bucket_ranges_->range(index + 1)) { + // Sample matches this bucket! + counts_[index] += (op == HistogramSamples::ADD) ? count : -count; + iter->Next(); + } else if (min > bucket_ranges_->range(index)) { + // Sample is larger than current bucket range. Try next. + index++; + } else { + // Sample is smaller than current bucket range. We scan buckets from + // smallest to largest, so the sample value must be invalid. + return false; + } + } + + return iter->Done(); +} + +// Use simple binary search. This is very general, but there are better +// approaches if we knew that the buckets were linearly distributed. +size_t SampleVector::GetBucketIndex(Sample value) const { + size_t bucket_count = bucket_ranges_->size() - 1; + CHECK_GE(bucket_count, 1u); + CHECK_GE(value, bucket_ranges_->range(0)); + CHECK_LT(value, bucket_ranges_->range(bucket_count)); + + size_t under = 0; + size_t over = bucket_count; + size_t mid; + do { + DCHECK_GE(over, under); + mid = under + (over - under)/2; + if (mid == under) + break; + if (bucket_ranges_->range(mid) <= value) + under = mid; + else + over = mid; + } while (true); + + DCHECK_LE(bucket_ranges_->range(mid), value); + CHECK_GT(bucket_ranges_->range(mid + 1), value); + return mid; +} + +SampleVectorIterator::SampleVectorIterator(const vector<Count>* counts, + const BucketRanges* bucket_ranges) + : counts_(counts), + bucket_ranges_(bucket_ranges), + index_(0) { + CHECK_GT(bucket_ranges_->size(), counts_->size()); + SkipEmptyBuckets(); +} + +SampleVectorIterator::~SampleVectorIterator() {} + +bool SampleVectorIterator::Done() const { + return index_ >= counts_->size(); +} + +void SampleVectorIterator::Next() { + DCHECK(!Done()); + index_++; + SkipEmptyBuckets(); +} + +void SampleVectorIterator::Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const { + DCHECK(!Done()); + if (min != NULL) + *min = bucket_ranges_->range(index_); + if (max != NULL) + *max = bucket_ranges_->range(index_ + 1); + if (count != NULL) + *count = (*counts_)[index_]; +} + +bool SampleVectorIterator::GetBucketIndex(size_t* index) const { + DCHECK(!Done()); + if (index != NULL) + *index = index_; + return true; +} + +void SampleVectorIterator::SkipEmptyBuckets() { + if (Done()) + return; + + while (index_ < counts_->size()) { + if ((*counts_)[index_] != 0) + return; + index_++; + } +} + +} // namespace base
diff --git a/src/base/metrics/sample_vector.h b/src/base/metrics/sample_vector.h new file mode 100644 index 0000000..67c344a --- /dev/null +++ b/src/base/metrics/sample_vector.h
@@ -0,0 +1,84 @@ +// 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. + +// SampleVector implements HistogramSamples interface. It is used by all +// Histogram based classes to store samples. + +#ifndef BASE_METRICS_SAMPLE_VECTOR_H_ +#define BASE_METRICS_SAMPLE_VECTOR_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_samples.h" + +namespace base { + +class BucketRanges; + +class BASE_EXPORT_PRIVATE SampleVector : public HistogramSamples { + public: + explicit SampleVector(const BucketRanges* bucket_ranges); + virtual ~SampleVector(); + + // HistogramSamples implementation: + virtual void Accumulate(HistogramBase::Sample value, + HistogramBase::Count count) OVERRIDE; + virtual HistogramBase::Count GetCount( + HistogramBase::Sample value) const OVERRIDE; + virtual HistogramBase::Count TotalCount() const OVERRIDE; + virtual scoped_ptr<SampleCountIterator> Iterator() const OVERRIDE; + + // Get count of a specific bucket. + HistogramBase::Count GetCountAtIndex(size_t bucket_index) const; + + protected: + virtual bool AddSubtractImpl( + SampleCountIterator* iter, + HistogramSamples::Operator op) OVERRIDE; // |op| is ADD or SUBTRACT. + + virtual size_t GetBucketIndex(HistogramBase::Sample value) const; + + private: + FRIEND_TEST_ALL_PREFIXES(HistogramTest, CorruptSampleCounts); + + std::vector<HistogramBase::Count> counts_; + + // Shares the same BucketRanges with Histogram object. + const BucketRanges* const bucket_ranges_; + + DISALLOW_COPY_AND_ASSIGN(SampleVector); +}; + +class BASE_EXPORT_PRIVATE SampleVectorIterator : public SampleCountIterator { + public: + SampleVectorIterator(const std::vector<HistogramBase::Count>* counts, + const BucketRanges* bucket_ranges); + virtual ~SampleVectorIterator(); + + // SampleCountIterator implementation: + virtual bool Done() const OVERRIDE; + virtual void Next() OVERRIDE; + virtual void Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const OVERRIDE; + + // SampleVector uses predefined buckets, so iterator can return bucket index. + virtual bool GetBucketIndex(size_t* index) const OVERRIDE; + + private: + void SkipEmptyBuckets(); + + const std::vector<HistogramBase::Count>* counts_; + const BucketRanges* bucket_ranges_; + + size_t index_; +}; + +} // namespace base + +#endif // BASE_METRICS_SAMPLE_VECTOR_H_
diff --git a/src/base/metrics/sample_vector_unittest.cc b/src/base/metrics/sample_vector_unittest.cc new file mode 100644 index 0000000..dbbbd15 --- /dev/null +++ b/src/base/metrics/sample_vector_unittest.cc
@@ -0,0 +1,265 @@ +// 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 <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/metrics/bucket_ranges.h" +#include "base/metrics/histogram.h" +#include "base/metrics/sample_vector.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::vector; + +namespace base { +namespace { + +TEST(SampleVectorTest, AccumulateTest) { + // Custom buckets: [1, 5) [5, 10) + BucketRanges ranges(3); + ranges.set_range(0, 1); + ranges.set_range(1, 5); + ranges.set_range(2, 10); + SampleVector samples(&ranges); + + samples.Accumulate(1, 200); + samples.Accumulate(2, -300); + EXPECT_EQ(-100, samples.GetCountAtIndex(0)); + + samples.Accumulate(5, 200); + EXPECT_EQ(200, samples.GetCountAtIndex(1)); + + EXPECT_EQ(600, samples.sum()); + EXPECT_EQ(100, samples.redundant_count()); + EXPECT_EQ(samples.TotalCount(), samples.redundant_count()); + + samples.Accumulate(5, -100); + EXPECT_EQ(100, samples.GetCountAtIndex(1)); + + EXPECT_EQ(100, samples.sum()); + EXPECT_EQ(0, samples.redundant_count()); + EXPECT_EQ(samples.TotalCount(), samples.redundant_count()); +} + +TEST(SampleVectorTest, AddSubtractTest) { + // Custom buckets: [0, 1) [1, 2) [2, 3) [3, INT_MAX) + BucketRanges ranges(5); + ranges.set_range(0, 0); + ranges.set_range(1, 1); + ranges.set_range(2, 2); + ranges.set_range(3, 3); + ranges.set_range(4, INT_MAX); + + SampleVector samples1(&ranges); + samples1.Accumulate(0, 100); + samples1.Accumulate(2, 100); + samples1.Accumulate(4, 100); + EXPECT_EQ(600, samples1.sum()); + EXPECT_EQ(300, samples1.TotalCount()); + EXPECT_EQ(samples1.redundant_count(), samples1.TotalCount()); + + SampleVector samples2(&ranges); + samples2.Accumulate(1, 200); + samples2.Accumulate(2, 200); + samples2.Accumulate(4, 200); + EXPECT_EQ(1400, samples2.sum()); + EXPECT_EQ(600, samples2.TotalCount()); + EXPECT_EQ(samples2.redundant_count(), samples2.TotalCount()); + + samples1.Add(samples2); + EXPECT_EQ(100, samples1.GetCountAtIndex(0)); + EXPECT_EQ(200, samples1.GetCountAtIndex(1)); + EXPECT_EQ(300, samples1.GetCountAtIndex(2)); + EXPECT_EQ(300, samples1.GetCountAtIndex(3)); + EXPECT_EQ(2000, samples1.sum()); + EXPECT_EQ(900, samples1.TotalCount()); + EXPECT_EQ(samples1.redundant_count(), samples1.TotalCount()); + + samples1.Subtract(samples2); + EXPECT_EQ(100, samples1.GetCountAtIndex(0)); + EXPECT_EQ(0, samples1.GetCountAtIndex(1)); + EXPECT_EQ(100, samples1.GetCountAtIndex(2)); + EXPECT_EQ(100, samples1.GetCountAtIndex(3)); + EXPECT_EQ(600, samples1.sum()); + EXPECT_EQ(300, samples1.TotalCount()); + EXPECT_EQ(samples1.redundant_count(), samples1.TotalCount()); +} + +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST +TEST(SampleVectorDeathTest, BucketIndexTest) { + // 8 buckets with exponential layout: + // [0, 1) [1, 2) [2, 4) [4, 8) [8, 16) [16, 32) [32, 64) [64, INT_MAX) + BucketRanges ranges(9); + Histogram::InitializeBucketRanges(1, 64, 8, &ranges); + SampleVector samples(&ranges); + + // Normal case + samples.Accumulate(0, 1); + samples.Accumulate(3, 2); + samples.Accumulate(64, 3); + EXPECT_EQ(1, samples.GetCount(0)); + EXPECT_EQ(2, samples.GetCount(2)); + EXPECT_EQ(3, samples.GetCount(65)); + + // Extreme case. + EXPECT_DEATH(samples.Accumulate(INT_MIN, 100), ""); + EXPECT_DEATH(samples.Accumulate(-1, 100), ""); + EXPECT_DEATH(samples.Accumulate(INT_MAX, 100), ""); + + // Custom buckets: [1, 5) [5, 10) + // Note, this is not a valid BucketRanges for Histogram because it does not + // have overflow buckets. + BucketRanges ranges2(3); + ranges2.set_range(0, 1); + ranges2.set_range(1, 5); + ranges2.set_range(2, 10); + SampleVector samples2(&ranges2); + + // Normal case. + samples2.Accumulate(1, 1); + samples2.Accumulate(4, 1); + samples2.Accumulate(5, 2); + samples2.Accumulate(9, 2); + EXPECT_EQ(2, samples2.GetCount(1)); + EXPECT_EQ(4, samples2.GetCount(5)); + + // Extreme case. + EXPECT_DEATH(samples2.Accumulate(0, 100), ""); + EXPECT_DEATH(samples2.Accumulate(10, 100), ""); +} + +TEST(SampleVectorDeathTest, AddSubtractBucketNotMatchTest) { + // Custom buckets 1: [1, 3) [3, 5) + BucketRanges ranges1(3); + ranges1.set_range(0, 1); + ranges1.set_range(1, 3); + ranges1.set_range(2, 5); + SampleVector samples1(&ranges1); + + // Custom buckets 2: [0, 1) [1, 3) [3, 6) [6, 7) + BucketRanges ranges2(5); + ranges2.set_range(0, 0); + ranges2.set_range(1, 1); + ranges2.set_range(2, 3); + ranges2.set_range(3, 6); + ranges2.set_range(4, 7); + SampleVector samples2(&ranges2); + + samples2.Accumulate(1, 100); + samples1.Add(samples2); + EXPECT_EQ(100, samples1.GetCountAtIndex(0)); + + // Extra bucket in the beginning. + samples2.Accumulate(0, 100); + EXPECT_DEATH(samples1.Add(samples2), ""); + EXPECT_DEATH(samples1.Subtract(samples2), ""); + + // Extra bucket in the end. + samples2.Accumulate(0, -100); + samples2.Accumulate(6, 100); + EXPECT_DEATH(samples1.Add(samples2), ""); + EXPECT_DEATH(samples1.Subtract(samples2), ""); + + // Bucket not match: [3, 5) VS [3, 6) + samples2.Accumulate(6, -100); + samples2.Accumulate(3, 100); + EXPECT_DEATH(samples1.Add(samples2), ""); + EXPECT_DEATH(samples1.Subtract(samples2), ""); +} + +#endif +// (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST + +TEST(SampleVectorIteratorTest, IterateTest) { + BucketRanges ranges(5); + ranges.set_range(0, 0); + ranges.set_range(1, 1); + ranges.set_range(2, 2); + ranges.set_range(3, 3); + ranges.set_range(4, 4); + + vector<HistogramBase::Count> counts(3); + counts[0] = 1; + counts[1] = 0; // Iterator will bypass this empty bucket. + counts[2] = 2; + + // BucketRanges can have larger size than counts. + SampleVectorIterator it(&counts, &ranges); + size_t index; + + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + it.Get(&min, &max, &count); + EXPECT_EQ(0, min); + EXPECT_EQ(1, max); + EXPECT_EQ(1, count); + EXPECT_TRUE(it.GetBucketIndex(&index)); + EXPECT_EQ(0u, index); + + it.Next(); + it.Get(&min, &max, &count); + EXPECT_EQ(2, min); + EXPECT_EQ(3, max); + EXPECT_EQ(2, count); + EXPECT_TRUE(it.GetBucketIndex(&index)); + EXPECT_EQ(2u, index); + + it.Next(); + EXPECT_TRUE(it.Done()); + + // Create iterator from SampleVector. + SampleVector samples(&ranges); + samples.Accumulate(0, 0); + samples.Accumulate(1, 1); + samples.Accumulate(2, 2); + samples.Accumulate(3, 3); + scoped_ptr<SampleCountIterator> it2 = samples.Iterator(); + + int i; + for (i = 1; !it2->Done(); i++, it2->Next()) { + it2->Get(&min, &max, &count); + EXPECT_EQ(i, min); + EXPECT_EQ(i + 1, max); + EXPECT_EQ(i, count); + + size_t index; + EXPECT_TRUE(it2->GetBucketIndex(&index)); + EXPECT_EQ(static_cast<size_t>(i), index); + } + EXPECT_EQ(4, i); +} + +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST + +TEST(SampleVectorIteratorDeathTest, IterateDoneTest) { + BucketRanges ranges(5); + ranges.set_range(0, 0); + ranges.set_range(1, 1); + ranges.set_range(2, 2); + ranges.set_range(3, 3); + ranges.set_range(4, INT_MAX); + SampleVector samples(&ranges); + + scoped_ptr<SampleCountIterator> it = samples.Iterator(); + + EXPECT_TRUE(it->Done()); + + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + EXPECT_DEATH(it->Get(&min, &max, &count), ""); + + EXPECT_DEATH(it->Next(), ""); + + samples.Accumulate(2, 100); + it = samples.Iterator(); + EXPECT_FALSE(it->Done()); +} + +#endif +// (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST + +} // namespace +} // namespace base
diff --git a/src/base/metrics/sparse_histogram.cc b/src/base/metrics/sparse_histogram.cc new file mode 100644 index 0000000..169037d --- /dev/null +++ b/src/base/metrics/sparse_histogram.cc
@@ -0,0 +1,80 @@ +// 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/metrics/sparse_histogram.h" + +#include "base/metrics/sample_map.h" +#include "base/metrics/statistics_recorder.h" +#include "base/synchronization/lock.h" + +using std::map; +using std::string; + +namespace base { + +typedef HistogramBase::Count Count; +typedef HistogramBase::Sample Sample; + +// static +HistogramBase* SparseHistogram::FactoryGet(const string& name, int32 flags) { + // TODO(kaiwang): Register and get SparseHistogram with StatisticsRecorder. + HistogramBase* histogram = new SparseHistogram(name); + histogram->SetFlags(flags); + return histogram; +} + +SparseHistogram::~SparseHistogram() {} + +HistogramType SparseHistogram::GetHistogramType() const { + return SPARSE_HISTOGRAM; +} + +bool SparseHistogram::HasConstructionArguments(Sample minimum, + Sample maximum, + size_t bucket_count) const { + // SparseHistogram never has min/max/bucket_count limit. + return false; +} + +void SparseHistogram::Add(Sample value) { + base::AutoLock auto_lock(lock_); + sample_counts_[value]++; + redundant_count_ += 1; +} + +scoped_ptr<HistogramSamples> SparseHistogram::SnapshotSamples() const { + scoped_ptr<SampleMap> snapshot(new SampleMap()); + + base::AutoLock auto_lock(lock_); + for(map<Sample, Count>::const_iterator it = sample_counts_.begin(); + it != sample_counts_.end(); + ++it) { + snapshot->Accumulate(it->first, it->second); + } + snapshot->ResetRedundantCount(redundant_count_); + return snapshot.PassAs<HistogramSamples>(); +} + +void SparseHistogram::WriteHTMLGraph(string* output) const { + // TODO(kaiwang): Implement. +} + +void SparseHistogram::WriteAscii(string* output) const { + // TODO(kaiwang): Implement. +} + +SparseHistogram::SparseHistogram(const string& name) + : HistogramBase(name), + redundant_count_(0) {} + +void SparseHistogram::GetParameters(DictionaryValue* params) const { + // TODO(kaiwang): Implement. (See HistogramBase::WriteJSON.) +} + +void SparseHistogram::GetCountAndBucketData(Count* count, + ListValue* buckets) const { + // TODO(kaiwang): Implement. (See HistogramBase::WriteJSON.) +} + +} // namespace base
diff --git a/src/base/metrics/sparse_histogram.h b/src/base/metrics/sparse_histogram.h new file mode 100644 index 0000000..a7c5695 --- /dev/null +++ b/src/base/metrics/sparse_histogram.h
@@ -0,0 +1,60 @@ +// 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_METRICS_SPARSE_HISTOGRAM_H_ +#define BASE_METRICS_SPARSE_HISTOGRAM_H_ + +#include <map> +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram_base.h" +#include "base/synchronization/lock.h" + +namespace base { + +class HistogramSamples; + +class BASE_EXPORT_PRIVATE SparseHistogram : public HistogramBase { + public: + // If there's one with same name, return the existing one. If not, create a + // new one. + static HistogramBase* FactoryGet(const std::string& name, int32 flags); + + virtual ~SparseHistogram(); + + // HistogramBase implementation: + virtual HistogramType GetHistogramType() const OVERRIDE; + virtual bool HasConstructionArguments(Sample minimum, + Sample maximum, + size_t bucket_count) const OVERRIDE; + virtual void Add(Sample value) OVERRIDE; + virtual void WriteHTMLGraph(std::string* output) const OVERRIDE; + virtual void WriteAscii(std::string* output) const OVERRIDE; + virtual scoped_ptr<HistogramSamples> SnapshotSamples() const OVERRIDE; + + private: + // Clients should always use FactoryGet to create SparseHistogram. + SparseHistogram(const std::string& name); + + virtual void GetParameters(DictionaryValue* params) const OVERRIDE; + virtual void GetCountAndBucketData(Count* count, + ListValue* buckets) const OVERRIDE; + + friend class SparseHistogramTest; // For constuctor calling. + + // Protects access to |sample_counts_| and |redundant_count_|. + mutable base::Lock lock_; + std::map<HistogramBase::Sample, HistogramBase::Count> sample_counts_; + HistogramBase::Count redundant_count_; + + DISALLOW_COPY_AND_ASSIGN(SparseHistogram); +}; + +} // namespace base + +#endif // BASE_METRICS_SPARSE_HISTOGRAM_H_
diff --git a/src/base/metrics/sparse_histogram_unittest.cc b/src/base/metrics/sparse_histogram_unittest.cc new file mode 100644 index 0000000..b4eb8fd --- /dev/null +++ b/src/base/metrics/sparse_histogram_unittest.cc
@@ -0,0 +1,40 @@ +// 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 <string> + +#include "base/memory/scoped_ptr.h" +#include "base/metrics/sample_map.h" +#include "base/metrics/sparse_histogram.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +class SparseHistogramTest : public testing::Test { + protected: + scoped_ptr<SparseHistogram> NewSparseHistogram(const std::string& name) { + return scoped_ptr<SparseHistogram>(new SparseHistogram(name)); + } +}; + +TEST_F(SparseHistogramTest, BasicTest) { + scoped_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse1")); + scoped_ptr<HistogramSamples> snapshot(histogram->SnapshotSamples()); + EXPECT_EQ(0, snapshot->TotalCount()); + EXPECT_EQ(0, snapshot->sum()); + + histogram->Add(100); + scoped_ptr<HistogramSamples> snapshot1(histogram->SnapshotSamples()); + EXPECT_EQ(1, snapshot1->TotalCount()); + EXPECT_EQ(1, snapshot1->GetCount(100)); + + histogram->Add(100); + histogram->Add(101); + scoped_ptr<HistogramSamples> snapshot2(histogram->SnapshotSamples()); + EXPECT_EQ(3, snapshot2->TotalCount()); + EXPECT_EQ(2, snapshot2->GetCount(100)); + EXPECT_EQ(1, snapshot2->GetCount(101)); +} + +} // namespace base
diff --git a/src/base/metrics/statistics_recorder.cc b/src/base/metrics/statistics_recorder.cc new file mode 100644 index 0000000..c1111c3 --- /dev/null +++ b/src/base/metrics/statistics_recorder.cc
@@ -0,0 +1,340 @@ +// 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/metrics/statistics_recorder.h" + +#include "base/debug/leak_annotations.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram.h" +#include "base/stringprintf.h" +#include "base/synchronization/lock.h" + +using std::list; +using std::string; + +namespace { +// Initialize histogram statistics gathering system. +base::LazyInstance<base::StatisticsRecorder>::Leaky g_statistics_recorder_ = + LAZY_INSTANCE_INITIALIZER; +} // namespace + +namespace base { + +// Collect the number of histograms created. +static uint32 number_of_histograms_ = 0; +// Collect the number of vectors saved because of caching ranges. +static uint32 number_of_vectors_saved_ = 0; +// Collect the number of ranges_ elements saved because of caching ranges. +static size_t saved_ranges_size_ = 0; + +// static +void StatisticsRecorder::Initialize() { + // Ensure that an instance of the StatisticsRecorder object is created. + g_statistics_recorder_.Get(); +} + + +// static +bool StatisticsRecorder::IsActive() { + if (lock_ == NULL) + return false; + base::AutoLock auto_lock(*lock_); + return NULL != histograms_; +} + +// static +Histogram* StatisticsRecorder::RegisterOrDeleteDuplicate(Histogram* histogram) { + // As per crbug.com/79322 the histograms are intentionally leaked, so we need + // to annotate them. Because ANNOTATE_LEAKING_OBJECT_PTR may be used only once + // for an object, the duplicates should not be annotated. + // Callers are responsible for not calling RegisterOrDeleteDuplicate(ptr) + // twice if (lock_ == NULL) || (!histograms_). + if (lock_ == NULL) { + ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322 + return histogram; + } + + Histogram* histogram_to_delete = NULL; + Histogram* histogram_to_return = NULL; + { + base::AutoLock auto_lock(*lock_); + if (histograms_ == NULL) { + ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322 + histogram_to_return = histogram; + } else { + const string& name = histogram->histogram_name(); + HistogramMap::iterator it = histograms_->find(name); + if (histograms_->end() == it) { + (*histograms_)[name] = histogram; + ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322 + ++number_of_histograms_; + histogram_to_return = histogram; + } else if (histogram == it->second) { + // The histogram was registered before. + histogram_to_return = histogram; + } else { + // We already have one histogram with this name. + histogram_to_return = it->second; + histogram_to_delete = histogram; + } + } + } + delete histogram_to_delete; + return histogram_to_return; +} + +// static +const BucketRanges* StatisticsRecorder::RegisterOrDeleteDuplicateRanges( + const BucketRanges* ranges) { + DCHECK(ranges->HasValidChecksum()); + scoped_ptr<const BucketRanges> ranges_deleter; + + if (lock_ == NULL) { + ANNOTATE_LEAKING_OBJECT_PTR(ranges); + return ranges; + } + + base::AutoLock auto_lock(*lock_); + if (ranges_ == NULL) { + ANNOTATE_LEAKING_OBJECT_PTR(ranges); + return ranges; + } + + list<const BucketRanges*>* checksum_matching_list; + RangesMap::iterator ranges_it = ranges_->find(ranges->checksum()); + if (ranges_->end() == ranges_it) { + // Add a new matching list to map. + checksum_matching_list = new list<const BucketRanges*>(); + ANNOTATE_LEAKING_OBJECT_PTR(checksum_matching_list); + (*ranges_)[ranges->checksum()] = checksum_matching_list; + } else { + checksum_matching_list = ranges_it->second; + } + + list<const BucketRanges*>::iterator checksum_matching_list_it; + for (checksum_matching_list_it = checksum_matching_list->begin(); + checksum_matching_list_it != checksum_matching_list->end(); + ++checksum_matching_list_it) { + const BucketRanges* existing_ranges = *checksum_matching_list_it; + if (existing_ranges->Equals(ranges)) { + if (existing_ranges == ranges) { + return ranges; + } else { + ++number_of_vectors_saved_; + saved_ranges_size_ += ranges->size(); + ranges_deleter.reset(ranges); + return existing_ranges; + } + } + } + // We haven't found a BucketRanges which has the same ranges. Register the + // new BucketRanges. + checksum_matching_list->push_front(ranges); + return ranges; +} + +// static +void StatisticsRecorder::CollectHistogramStats(const std::string& suffix) { + static int uma_upload_attempt = 0; + ++uma_upload_attempt; + if (uma_upload_attempt == 1) { + UMA_HISTOGRAM_COUNTS_10000( + "Histogram.SharedRange.Count.FirstUpload." + suffix, + number_of_histograms_); + UMA_HISTOGRAM_COUNTS_10000( + "Histogram.SharedRange.RangesSaved.FirstUpload." + suffix, + number_of_vectors_saved_); + UMA_HISTOGRAM_COUNTS( + "Histogram.SharedRange.ElementsSaved.FirstUpload." + suffix, + static_cast<int>(saved_ranges_size_)); + number_of_histograms_ = 0; + number_of_vectors_saved_ = 0; + saved_ranges_size_ = 0; + return; + } + if (uma_upload_attempt == 2) { + UMA_HISTOGRAM_COUNTS_10000( + "Histogram.SharedRange.Count.SecondUpload." + suffix, + number_of_histograms_); + UMA_HISTOGRAM_COUNTS_10000( + "Histogram.SharedRange.RangesSaved.SecondUpload." + suffix, + number_of_vectors_saved_); + UMA_HISTOGRAM_COUNTS( + "Histogram.SharedRange.ElementsSaved.SecondUpload." + suffix, + static_cast<int>(saved_ranges_size_)); + number_of_histograms_ = 0; + number_of_vectors_saved_ = 0; + saved_ranges_size_ = 0; + return; + } + UMA_HISTOGRAM_COUNTS_10000( + "Histogram.SharedRange.Count.RestOfUploads." + suffix, + number_of_histograms_); + UMA_HISTOGRAM_COUNTS_10000( + "Histogram.SharedRange.RangesSaved.RestOfUploads." + suffix, + number_of_vectors_saved_); + UMA_HISTOGRAM_COUNTS( + "Histogram.SharedRange.ElementsSaved.RestOfUploads." + suffix, + static_cast<int>(saved_ranges_size_)); +} + +// static +void StatisticsRecorder::WriteHTMLGraph(const std::string& query, + std::string* output) { + if (!IsActive()) + return; + + Histograms snapshot; + GetSnapshot(query, &snapshot); + for (Histograms::iterator it = snapshot.begin(); + it != snapshot.end(); + ++it) { + (*it)->WriteHTMLGraph(output); + output->append("<br><hr><br>"); + } +} + +// static +void StatisticsRecorder::WriteGraph(const std::string& query, + std::string* output) { + if (!IsActive()) + return; + if (query.length()) + StringAppendF(output, "Collections of histograms for %s\n", query.c_str()); + else + output->append("Collections of all histograms\n"); + + Histograms snapshot; + GetSnapshot(query, &snapshot); + for (Histograms::iterator it = snapshot.begin(); + it != snapshot.end(); + ++it) { + (*it)->WriteAscii(output); + output->append("\n"); + } +} + +// static +void StatisticsRecorder::GetHistograms(Histograms* output) { + if (lock_ == NULL) + return; + base::AutoLock auto_lock(*lock_); + if (histograms_ == NULL) + return; + + for (HistogramMap::iterator it = histograms_->begin(); + histograms_->end() != it; + ++it) { + DCHECK_EQ(it->first, it->second->histogram_name()); + output->push_back(it->second); + } +} + +// static +void StatisticsRecorder::GetBucketRanges( + std::vector<const BucketRanges*>* output) { + if (lock_ == NULL) + return; + base::AutoLock auto_lock(*lock_); + if (ranges_ == NULL) + return; + + for (RangesMap::iterator it = ranges_->begin(); + ranges_->end() != it; + ++it) { + list<const BucketRanges*>* ranges_list = it->second; + list<const BucketRanges*>::iterator ranges_list_it; + for (ranges_list_it = ranges_list->begin(); + ranges_list_it != ranges_list->end(); + ++ranges_list_it) { + output->push_back(*ranges_list_it); + } + } +} + +// static +Histogram* StatisticsRecorder::FindHistogram(const std::string& name) { + if (lock_ == NULL) + return NULL; + base::AutoLock auto_lock(*lock_); + if (histograms_ == NULL) + return NULL; + + HistogramMap::iterator it = histograms_->find(name); + if (histograms_->end() == it) + return NULL; + return it->second; +} + +// private static +void StatisticsRecorder::GetSnapshot(const std::string& query, + Histograms* snapshot) { + if (lock_ == NULL) + return; + base::AutoLock auto_lock(*lock_); + if (histograms_ == NULL) + return; + + for (HistogramMap::iterator it = histograms_->begin(); + histograms_->end() != it; + ++it) { + if (it->first.find(query) != std::string::npos) + snapshot->push_back(it->second); + } +} + +// This singleton instance should be started during the single threaded portion +// of main(), and hence it is not thread safe. It initializes globals to +// provide support for all future calls. +StatisticsRecorder::StatisticsRecorder() { + DCHECK(!histograms_); + if (lock_ == NULL) { + // This will leak on purpose. It's the only way to make sure we won't race + // against the static uninitialization of the module while one of our + // static methods relying on the lock get called at an inappropriate time + // during the termination phase. Since it's a static data member, we will + // leak one per process, which would be similar to the instance allocated + // during static initialization and released only on process termination. + lock_ = new base::Lock; + } + base::AutoLock auto_lock(*lock_); + histograms_ = new HistogramMap; + ranges_ = new RangesMap; +} + +StatisticsRecorder::~StatisticsRecorder() { + DCHECK(histograms_ && ranges_ && lock_); + if (dump_on_exit_) { + string output; + WriteGraph("", &output); + DLOG(INFO) << output; + } + + // Clean up. + scoped_ptr<HistogramMap> histograms_deleter; + scoped_ptr<RangesMap> ranges_deleter; + // We don't delete lock_ on purpose to avoid having to properly protect + // against it going away after we checked for NULL in the static methods. + { + base::AutoLock auto_lock(*lock_); + histograms_deleter.reset(histograms_); + ranges_deleter.reset(ranges_); + histograms_ = NULL; + ranges_ = NULL; + } + // We are going to leak the histograms and the ranges. +} + + +// static +StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = NULL; +// static +StatisticsRecorder::RangesMap* StatisticsRecorder::ranges_ = NULL; +// static +base::Lock* StatisticsRecorder::lock_ = NULL; +// static +bool StatisticsRecorder::dump_on_exit_ = false; + +} // namespace base
diff --git a/src/base/metrics/statistics_recorder.h b/src/base/metrics/statistics_recorder.h new file mode 100644 index 0000000..5345735 --- /dev/null +++ b/src/base/metrics/statistics_recorder.h
@@ -0,0 +1,116 @@ +// 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. + +// StatisticsRecorder holds all Histograms and BucketRanges that are used by +// Histograms in the system. It provides a general place for +// Histograms/BucketRanges to register, and supports a global API for accessing +// (i.e., dumping, or graphing) the data. + +#ifndef BASE_METRICS_STATISTICS_RECORDER_H_ +#define BASE_METRICS_STATISTICS_RECORDER_H_ + +#include <list> +#include <map> +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/lazy_instance.h" + +namespace base { + +class BucketRanges; +class Histogram; +class Lock; + +class BASE_EXPORT StatisticsRecorder { + public: + typedef std::vector<Histogram*> Histograms; + + // Initializes the StatisticsRecorder system. + static void Initialize(); + + // Find out if histograms can now be registered into our list. + static bool IsActive(); + + // Register, or add a new histogram to the collection of statistics. If an + // identically named histogram is already registered, then the argument + // |histogram| will deleted. The returned value is always the registered + // histogram (either the argument, or the pre-existing registered histogram). + static Histogram* RegisterOrDeleteDuplicate(Histogram* histogram); + + // Register, or add a new BucketRanges. If an identically BucketRanges is + // already registered, then the argument |ranges| will deleted. The returned + // value is always the registered BucketRanges (either the argument, or the + // pre-existing one). + static const BucketRanges* RegisterOrDeleteDuplicateRanges( + const BucketRanges* ranges); + + // Method for collecting stats about histograms created in browser and + // renderer processes. |suffix| is appended to histogram names. |suffix| could + // be either browser or renderer. + static void CollectHistogramStats(const std::string& suffix); + + // Methods for printing histograms. 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); + + // Method for extracting histograms which were marked for use by UMA. + static void GetHistograms(Histograms* output); + + // Method for extracting BucketRanges used by all histograms registered. + static void GetBucketRanges(std::vector<const BucketRanges*>* output); + + // Find a histogram by name. It matches the exact name. This method is thread + // safe. It returns NULL if a matching histogram is not found. + static Histogram* FindHistogram(const std::string& name); + + static bool dump_on_exit() { return dump_on_exit_; } + + static void set_dump_on_exit(bool enable) { dump_on_exit_ = enable; } + + // GetSnapshot copies some of the pointers to registered histograms into the + // caller supplied vector (Histograms). Only histograms with names matching + // query are returned. The query must be a substring of histogram name for its + // pointer to be copied. + static void GetSnapshot(const std::string& query, Histograms* snapshot); + + private: + // We keep all registered histograms in a map, from name to histogram. + typedef std::map<std::string, Histogram*> HistogramMap; + + // We keep all |bucket_ranges_| in a map, from checksum to a list of + // |bucket_ranges_|. Checksum is calculated from the |ranges_| in + // |bucket_ranges_|. + typedef std::map<uint32, std::list<const BucketRanges*>*> RangesMap; + + friend struct DefaultLazyInstanceTraits<StatisticsRecorder>; + friend class HistogramTest; + friend class StatisticsRecorderTest; + + // 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 + // call destructor/constructor to get a clean StatisticsRecorder. + StatisticsRecorder(); + ~StatisticsRecorder(); + + static HistogramMap* histograms_; + static RangesMap* ranges_; + + // Lock protects access to above maps. + static base::Lock* lock_; + + // Dump all known histograms to log. + static bool dump_on_exit_; + + DISALLOW_COPY_AND_ASSIGN(StatisticsRecorder); +}; + +} // namespace base + +#endif // BASE_METRICS_STATISTICS_RECORDER_H_
diff --git a/src/base/metrics/statistics_recorder_unittest.cc b/src/base/metrics/statistics_recorder_unittest.cc new file mode 100644 index 0000000..95759c1 --- /dev/null +++ b/src/base/metrics/statistics_recorder_unittest.cc
@@ -0,0 +1,256 @@ +// 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 <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram.h" +#include "base/metrics/statistics_recorder.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +class StatisticsRecorderTest : public testing::Test { + protected: + virtual void SetUp() { + // Each test will have a clean state (no Histogram / BucketRanges + // registered). + InitializeStatisticsRecorder(); + } + + virtual void TearDown() { + UninitializeStatisticsRecorder(); + } + + void InitializeStatisticsRecorder() { + statistics_recorder_ = new StatisticsRecorder(); + } + + void UninitializeStatisticsRecorder() { + delete statistics_recorder_; + statistics_recorder_ = NULL; + } + + void DeleteHistogram(Histogram* histogram) { + delete histogram; + } + + StatisticsRecorder* statistics_recorder_; +}; + +TEST_F(StatisticsRecorderTest, NotInitialized) { + UninitializeStatisticsRecorder(); + + ASSERT_FALSE(StatisticsRecorder::IsActive()); + + StatisticsRecorder::Histograms registered_histograms; + std::vector<const BucketRanges*> registered_ranges; + + // We can still create histograms, but it's not registered. + // TODO(kaiwang): Do not depend on Histogram FactoryGet implementation. + Histogram* histogram( + Histogram::FactoryGet("StatisticsRecorderTest_NotInitialized", + 1, 1000, 10, Histogram::kNoFlags)); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(0u, registered_histograms.size()); + + // RegisterOrDeleteDuplicateRanges is a no-op. + BucketRanges* ranges = new BucketRanges(3);; + ranges->ResetChecksum(); + EXPECT_EQ(ranges, + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges)); + StatisticsRecorder::GetBucketRanges(®istered_ranges); + EXPECT_EQ(0u, registered_ranges.size()); + + DeleteHistogram(histogram); +} + +TEST_F(StatisticsRecorderTest, RegisterBucketRanges) { + std::vector<const BucketRanges*> registered_ranges; + + BucketRanges* ranges1 = new BucketRanges(3);; + ranges1->ResetChecksum(); + BucketRanges* ranges2 = new BucketRanges(4);; + ranges2->ResetChecksum(); + + // Register new ranges. + EXPECT_EQ(ranges1, + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges1)); + EXPECT_EQ(ranges2, + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges2)); + StatisticsRecorder::GetBucketRanges(®istered_ranges); + ASSERT_EQ(2u, registered_ranges.size()); + + // Register some ranges again. + EXPECT_EQ(ranges1, + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges1)); + registered_ranges.clear(); + StatisticsRecorder::GetBucketRanges(®istered_ranges); + ASSERT_EQ(2u, registered_ranges.size()); + // Make sure the ranges is still the one we know. + ASSERT_EQ(3u, ranges1->size()); + EXPECT_EQ(0, ranges1->range(0)); + EXPECT_EQ(0, ranges1->range(1)); + EXPECT_EQ(0, ranges1->range(2)); + + // Register ranges with same values. + BucketRanges* ranges3 = new BucketRanges(3);; + ranges3->ResetChecksum(); + EXPECT_EQ(ranges1, // returning ranges1 + StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges3)); + registered_ranges.clear(); + StatisticsRecorder::GetBucketRanges(®istered_ranges); + ASSERT_EQ(2u, registered_ranges.size()); +} + +TEST_F(StatisticsRecorderTest, RegisterHistogram) { + // Create a Histogram that was not registered. + // TODO(kaiwang): Do not depend on Histogram FactoryGet implementation. + UninitializeStatisticsRecorder(); + Histogram* histogram = Histogram::FactoryGet( + "TestHistogram", 1, 1000, 10, Histogram::kNoFlags); + + // Clean StatisticsRecorder. + InitializeStatisticsRecorder(); + StatisticsRecorder::Histograms registered_histograms; + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(0u, registered_histograms.size()); + + // Register the Histogram. + EXPECT_EQ(histogram, + StatisticsRecorder::RegisterOrDeleteDuplicate(histogram)); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(1u, registered_histograms.size()); + + // Register the same Histogram again. + EXPECT_EQ(histogram, + StatisticsRecorder::RegisterOrDeleteDuplicate(histogram)); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(1u, registered_histograms.size()); +} + +TEST_F(StatisticsRecorderTest, FindHistogram) { + Histogram* histogram1 = Histogram::FactoryGet( + "TestHistogram1", 1, 1000, 10, Histogram::kNoFlags); + Histogram* histogram2 = Histogram::FactoryGet( + "TestHistogram2", 1, 1000, 10, Histogram::kNoFlags); + + EXPECT_EQ(histogram1, StatisticsRecorder::FindHistogram("TestHistogram1")); + EXPECT_EQ(histogram2, StatisticsRecorder::FindHistogram("TestHistogram2")); + EXPECT_TRUE(StatisticsRecorder::FindHistogram("TestHistogram") == NULL); +} + +TEST_F(StatisticsRecorderTest, GetSnapshot) { + Histogram::FactoryGet("TestHistogram1", 1, 1000, 10, Histogram::kNoFlags); + Histogram::FactoryGet("TestHistogram2", 1, 1000, 10, Histogram::kNoFlags); + Histogram::FactoryGet("TestHistogram3", 1, 1000, 10, Histogram::kNoFlags); + + StatisticsRecorder::Histograms snapshot; + StatisticsRecorder::GetSnapshot("Test", &snapshot); + EXPECT_EQ(3u, snapshot.size()); + + snapshot.clear(); + StatisticsRecorder::GetSnapshot("1", &snapshot); + EXPECT_EQ(1u, snapshot.size()); + + snapshot.clear(); + StatisticsRecorder::GetSnapshot("hello", &snapshot); + EXPECT_EQ(0u, snapshot.size()); +} + +TEST_F(StatisticsRecorderTest, RegisterHistogramWithFactoryGet) { + StatisticsRecorder::Histograms registered_histograms; + + StatisticsRecorder::GetHistograms(®istered_histograms); + ASSERT_EQ(0u, registered_histograms.size()); + + // Create a Histogram. + Histogram* histogram = Histogram::FactoryGet( + "TestHistogram", 1, 1000, 10, Histogram::kNoFlags); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(1u, registered_histograms.size()); + + // Get an existing histogram. + Histogram* histogram2 = Histogram::FactoryGet( + "TestHistogram", 1, 1000, 10, Histogram::kNoFlags); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(1u, registered_histograms.size()); + EXPECT_EQ(histogram, histogram2); + + // Create a LinearHistogram. + histogram = LinearHistogram::FactoryGet( + "TestLinearHistogram", 1, 1000, 10, Histogram::kNoFlags); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(2u, registered_histograms.size()); + + // Create a BooleanHistogram. + histogram = BooleanHistogram::FactoryGet( + "TestBooleanHistogram", Histogram::kNoFlags); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(3u, registered_histograms.size()); + + // Create a CustomHistogram. + std::vector<int> custom_ranges; + custom_ranges.push_back(1); + custom_ranges.push_back(5); + histogram = CustomHistogram::FactoryGet( + "TestCustomHistogram", custom_ranges, Histogram::kNoFlags); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(4u, registered_histograms.size()); +} + +TEST_F(StatisticsRecorderTest, RegisterHistogramWithMacros) { + StatisticsRecorder::Histograms registered_histograms; + + Histogram* histogram = Histogram::FactoryGet( + "TestHistogramCounts", 1, 1000000, 50, Histogram::kNoFlags); + + // The histogram we got from macro is the same as from FactoryGet. + HISTOGRAM_COUNTS("TestHistogramCounts", 30); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + ASSERT_EQ(1u, registered_histograms.size()); + EXPECT_EQ(histogram, registered_histograms[0]); + + HISTOGRAM_TIMES("TestHistogramTimes", TimeDelta::FromDays(1)); + HISTOGRAM_ENUMERATION("TestHistogramEnumeration", 20, 200); + + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); + EXPECT_EQ(3u, registered_histograms.size()); + + // Debugging only macros. + DHISTOGRAM_TIMES("TestHistogramDebugTimes", TimeDelta::FromDays(1)); + DHISTOGRAM_COUNTS("TestHistogramDebugCounts", 30); + registered_histograms.clear(); + StatisticsRecorder::GetHistograms(®istered_histograms); +#ifndef NDEBUG + EXPECT_EQ(5u, registered_histograms.size()); +#else + EXPECT_EQ(3u, registered_histograms.size()); +#endif +} + +TEST_F(StatisticsRecorderTest, BucketRangesSharing) { + Histogram* histogram1(Histogram::FactoryGet( + "Histogram", 1, 64, 8, Histogram::kNoFlags)); + Histogram* histogram2(Histogram::FactoryGet( + "Histogram2", 1, 64, 8, Histogram::kNoFlags)); + Histogram* histogram3(Histogram::FactoryGet( + "Histogram3", 1, 64, 16, Histogram::kNoFlags)); + + const BucketRanges* bucket_ranges1 = histogram1->bucket_ranges(); + const BucketRanges* bucket_ranges2 = histogram2->bucket_ranges(); + const BucketRanges* bucket_ranges3 = histogram3->bucket_ranges(); + EXPECT_EQ(bucket_ranges1, bucket_ranges2); + EXPECT_FALSE(bucket_ranges1->Equals(bucket_ranges3)); +} + +} // namespace base
diff --git a/src/base/metrics/stats_counters.cc b/src/base/metrics/stats_counters.cc new file mode 100644 index 0000000..0f0a5ef --- /dev/null +++ b/src/base/metrics/stats_counters.cc
@@ -0,0 +1,165 @@ +// Copyright (c) 2010 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/stats_counters.h" + +#include "base/logging.h" + +namespace base { +#if defined(__LB_SHELL__) || defined(COBALT) +// Intentionally no-ops. +StatsCounter::StatsCounter() { +} +StatsCounter::StatsCounter(const std::string& name) { +} +StatsCounter::~StatsCounter() { +} +void StatsCounter::Set(int value) { +} +void StatsCounter::Add(int value) { +} +int* StatsCounter::GetPtr() { + return NULL; +} +StatsCounterTimer::StatsCounterTimer(const std::string& name) { +} +StatsCounterTimer::~StatsCounterTimer() { +} +void StatsCounterTimer::Start() { +} +void StatsCounterTimer::Stop() { +} +bool StatsCounterTimer::Running() { + return false; +} +void StatsCounterTimer::AddTime(TimeDelta time) { +} +void StatsCounterTimer::Record() { +} +StatsRate::StatsRate(const std::string& name) : + StatsCounterTimer(name), counter_(name), largest_add_("") { +} +StatsRate::~StatsRate() { +} +void StatsRate::Add(int value) { +} +#else + +StatsCounter::StatsCounter(const std::string& name) + : counter_id_(-1) { + // We prepend the name with 'c:' to indicate that it is a counter. + if (StatsTable::current()) { + // TODO(mbelshe): name_ construction is racy and it may corrupt memory for + // static. + name_ = "c:"; + name_.append(name); + } +} + +StatsCounter::~StatsCounter() { +} + +void StatsCounter::Set(int value) { + int* loc = GetPtr(); + if (loc) + *loc = value; +} + +void StatsCounter::Add(int value) { + int* loc = GetPtr(); + if (loc) + (*loc) += value; +} + +StatsCounter::StatsCounter() + : counter_id_(-1) { +} + +int* StatsCounter::GetPtr() { + StatsTable* table = StatsTable::current(); + if (!table) + return NULL; + + // If counter_id_ is -1, then we haven't looked it up yet. + if (counter_id_ == -1) { + counter_id_ = table->FindCounter(name_); + if (table->GetSlot() == 0) { + if (!table->RegisterThread("")) { + // There is no room for this thread. This thread + // cannot use counters. + counter_id_ = 0; + return NULL; + } + } + } + + // If counter_id_ is > 0, then we have a valid counter. + if (counter_id_ > 0) + return table->GetLocation(counter_id_, table->GetSlot()); + + // counter_id_ was zero, which means the table is full. + return NULL; +} + + +StatsCounterTimer::StatsCounterTimer(const std::string& name) { + // we prepend the name with 't:' to indicate that it is a timer. + if (StatsTable::current()) { + // TODO(mbelshe): name_ construction is racy and it may corrupt memory for + // static. + name_ = "t:"; + name_.append(name); + } +} + +StatsCounterTimer::~StatsCounterTimer() { +} + +void StatsCounterTimer::Start() { + if (!Enabled()) + return; + start_time_ = TimeTicks::Now(); + stop_time_ = TimeTicks(); +} + +// Stop the timer and record the results. +void StatsCounterTimer::Stop() { + if (!Enabled() || !Running()) + return; + stop_time_ = TimeTicks::Now(); + Record(); +} + +// Returns true if the timer is running. +bool StatsCounterTimer::Running() { + return Enabled() && !start_time_.is_null() && stop_time_.is_null(); +} + +// Accept a TimeDelta to increment. +void StatsCounterTimer::AddTime(TimeDelta time) { + Add(static_cast<int>(time.InMilliseconds())); +} + +void StatsCounterTimer::Record() { + AddTime(stop_time_ - start_time_); +} + + +StatsRate::StatsRate(const std::string& name) + : StatsCounterTimer(name), + counter_(name), + largest_add_(std::string(" ").append(name).append("MAX")) { +} + +StatsRate::~StatsRate() { +} + +void StatsRate::Add(int value) { + counter_.Increment(); + StatsCounterTimer::Add(value); + if (value > largest_add_.value()) + largest_add_.Set(value); +} +#endif +} // namespace base
diff --git a/src/base/metrics/stats_counters.h b/src/base/metrics/stats_counters.h new file mode 100644 index 0000000..bc2016c --- /dev/null +++ b/src/base/metrics/stats_counters.h
@@ -0,0 +1,205 @@ +// 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. + +#ifndef BASE_METRICS_STATS_COUNTERS_H_ +#define BASE_METRICS_STATS_COUNTERS_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/compiler_specific.h" +#if !defined(__LB_SHELL__) && !defined(COBALT) +#include "base/metrics/stats_table.h" +#endif +#include "base/time.h" + +namespace base { + +// StatsCounters are dynamically created values which can be tracked in +// the StatsTable. They are designed to be lightweight to create and +// easy to use. +// +// Since StatsCounters can be created dynamically by name, there is +// a hash table lookup to find the counter in the table. A StatsCounter +// object can be created once and used across multiple threads safely. +// +// Example usage: +// { +// StatsCounter request_count("RequestCount"); +// request_count.Increment(); +// } +// +// Note that creating counters on the stack does work, however creating +// the counter object requires a hash table lookup. For inner loops, it +// may be better to create the counter either as a member of another object +// (or otherwise outside of the loop) for maximum performance. +// +// Internally, a counter represents a value in a row of a StatsTable. +// The row has a 32bit value for each process/thread in the table and also +// a name (stored in the table metadata). +// +// NOTE: In order to make stats_counters usable in lots of different code, +// avoid any dependencies inside this header file. +// + +//------------------------------------------------------------------------------ +// Define macros for ease of use. They also allow us to change definitions +// as the implementation varies, or depending on compile options. +//------------------------------------------------------------------------------ +// First provide generic macros, which exist in production as well as debug. +#if defined(__LB_SHELL__) || defined(COBALT) +#define STATS_COUNTER(name, delta) +#define SIMPLE_STATS_COUNTER(name) +#define RATE_COUNTER(name, duration) +#else +#define STATS_COUNTER(name, delta) do { \ + base::StatsCounter counter(name); \ + counter.Add(delta); \ +} while (0) + +#define SIMPLE_STATS_COUNTER(name) STATS_COUNTER(name, 1) + +#define RATE_COUNTER(name, duration) do { \ + base::StatsRate hit_count(name); \ + hit_count.AddTime(duration); \ +} while (0) +#endif + +// Define Debug vs non-debug flavors of macros. +#ifndef NDEBUG + +#define DSTATS_COUNTER(name, delta) STATS_COUNTER(name, delta) +#define DSIMPLE_STATS_COUNTER(name) SIMPLE_STATS_COUNTER(name) +#define DRATE_COUNTER(name, duration) RATE_COUNTER(name, duration) + +#else // NDEBUG + +#define DSTATS_COUNTER(name, delta) do {} while (0) +#define DSIMPLE_STATS_COUNTER(name) do {} while (0) +#define DRATE_COUNTER(name, duration) do {} while (0) + +#endif // NDEBUG + +//------------------------------------------------------------------------------ +// StatsCounter represents a counter in the StatsTable class. +class BASE_EXPORT StatsCounter { + public: + // Create a StatsCounter object. + explicit StatsCounter(const std::string& name); + virtual ~StatsCounter(); + + // Sets the counter to a specific value. + void Set(int value); + + // Increments the counter. + void Increment() { + Add(1); + } + + virtual void Add(int value); + + // Decrements the counter. + void Decrement() { + Add(-1); + } + + void Subtract(int value) { + Add(-value); + } + + // Is this counter enabled? + // Returns false if table is full. + bool Enabled() { + return GetPtr() != NULL; + } + + int value() { + int* loc = GetPtr(); + if (loc) return *loc; + return 0; + } + + protected: + StatsCounter(); + + // Returns the cached address of this counter location. + int* GetPtr(); + + std::string name_; + // The counter id in the table. We initialize to -1 (an invalid value) + // and then cache it once it has been looked up. The counter_id is + // valid across all threads and processes. + int32 counter_id_; +}; + + +// A StatsCounterTimer is a StatsCounter which keeps a timer during +// the scope of the StatsCounterTimer. On destruction, it will record +// its time measurement. +class BASE_EXPORT StatsCounterTimer : protected StatsCounter { + public: + // Constructs and starts the timer. + explicit StatsCounterTimer(const std::string& name); + virtual ~StatsCounterTimer(); + + // Start the timer. + void Start(); + + // Stop the timer and record the results. + void Stop(); + + // Returns true if the timer is running. + bool Running(); + + // Accept a TimeDelta to increment. + virtual void AddTime(TimeDelta time); + + protected: + // Compute the delta between start and stop, in milliseconds. + void Record(); + + TimeTicks start_time_; + TimeTicks stop_time_; +}; + +// A StatsRate is a timer that keeps a count of the number of intervals added so +// that several statistics can be produced: +// min, max, avg, count, total +class BASE_EXPORT StatsRate : public StatsCounterTimer { + public: + // Constructs and starts the timer. + explicit StatsRate(const std::string& name); + virtual ~StatsRate(); + + virtual void Add(int value) OVERRIDE; + + private: + StatsCounter counter_; + StatsCounter largest_add_; +}; + + +// Helper class for scoping a timer or rate. +template<class T> class StatsScope { + public: + explicit StatsScope<T>(T& timer) + : timer_(timer) { + timer_.Start(); + } + + ~StatsScope() { + timer_.Stop(); + } + + void Stop() { + timer_.Stop(); + } + + private: + T& timer_; +}; + +} // namespace base + +#endif // BASE_METRICS_STATS_COUNTERS_H_
diff --git a/src/base/metrics/stats_table.cc b/src/base/metrics/stats_table.cc new file mode 100644 index 0000000..2bccc90 --- /dev/null +++ b/src/base/metrics/stats_table.cc
@@ -0,0 +1,560 @@ +// 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/metrics/stats_table.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/process_util.h" +#include "base/shared_memory.h" +#include "base/string_piece.h" +#include "base/string_util.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread_local_storage.h" +#include "base/utf_string_conversions.h" + +#if defined(OS_POSIX) +#include "errno.h" +#endif + +namespace base { + +// The StatsTable uses a shared memory segment that is laid out as follows +// +// +-------------------------------------------+ +// | Version | Size | MaxCounters | MaxThreads | +// +-------------------------------------------+ +// | Thread names table | +// +-------------------------------------------+ +// | Thread TID table | +// +-------------------------------------------+ +// | Thread PID table | +// +-------------------------------------------+ +// | Counter names table | +// +-------------------------------------------+ +// | Data | +// +-------------------------------------------+ +// +// The data layout is a grid, where the columns are the thread_ids and the +// rows are the counter_ids. +// +// If the first character of the thread_name is '\0', then that column is +// empty. +// If the first character of the counter_name is '\0', then that row is +// empty. +// +// About Locking: +// This class is designed to be both multi-thread and multi-process safe. +// Aside from initialization, this is done by partitioning the data which +// each thread uses so that no locking is required. However, to allocate +// the rows and columns of the table to particular threads, locking is +// required. +// +// At the shared-memory level, we have a lock. This lock protects the +// shared-memory table only, and is used when we create new counters (e.g. +// use rows) or when we register new threads (e.g. use columns). Reading +// data from the table does not require any locking at the shared memory +// level. +// +// Each process which accesses the table will create a StatsTable object. +// The StatsTable maintains a hash table of the existing counters in the +// table for faster lookup. Since the hash table is process specific, +// each process maintains its own cache. We avoid complexity here by never +// de-allocating from the hash table. (Counters are dynamically added, +// but not dynamically removed). + +// In order for external viewers to be able to read our shared memory, +// we all need to use the same size ints. +COMPILE_ASSERT(sizeof(int)==4, expect_4_byte_ints); + +namespace { + +// An internal version in case we ever change the format of this +// file, and so that we can identify our table. +const int kTableVersion = 0x13131313; + +// The name for un-named counters and threads in the table. +const char kUnknownName[] = "<unknown>"; + +// Calculates delta to align an offset to the size of an int +inline int AlignOffset(int offset) { + return (sizeof(int) - (offset % sizeof(int))) % sizeof(int); +} + +inline int AlignedSize(int size) { + return size + AlignOffset(size); +} + +} // namespace + +// The StatsTable::Private maintains convenience pointers into the +// shared memory segment. Use this class to keep the data structure +// clean and accessible. +class StatsTable::Private { + public: + // Various header information contained in the memory mapped segment. + struct TableHeader { + int version; + int size; + int max_counters; + int max_threads; + }; + + // Construct a new Private 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); + + SharedMemory* shared_memory() { return &shared_memory_; } + + // Accessors for our header pointers + TableHeader* table_header() const { return table_header_; } + int version() const { return table_header_->version; } + int size() const { return table_header_->size; } + int max_counters() const { return table_header_->max_counters; } + int max_threads() const { return table_header_->max_threads; } + + // Accessors for our tables + char* thread_name(int slot_id) const { + return &thread_names_table_[ + (slot_id-1) * (StatsTable::kMaxThreadNameLength)]; + } + PlatformThreadId* thread_tid(int slot_id) const { + return &(thread_tid_table_[slot_id-1]); + } + int* thread_pid(int slot_id) const { + return &(thread_pid_table_[slot_id-1]); + } + char* counter_name(int counter_id) const { + return &counter_names_table_[ + (counter_id-1) * (StatsTable::kMaxCounterNameLength)]; + } + int* row(int counter_id) const { + return &data_table_[(counter_id-1) * max_threads()]; + } + + private: + // Constructor is private because you should use New() instead. + Private() + : table_header_(NULL), + thread_names_table_(NULL), + thread_tid_table_(NULL), + thread_pid_table_(NULL), + counter_names_table_(NULL), + data_table_(NULL) { + } + + // Initializes the table on first access. Sets header values + // appropriately and zeroes all counters. + void InitializeTable(void* memory, int size, int max_counters, + int max_threads); + + // Initializes our in-memory pointers into a pre-created StatsTable. + void ComputeMappedPointers(void* memory); + + 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_; +}; + +// 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)) + return NULL; + if (!priv->shared_memory_.Map(size)) + return NULL; + void* memory = priv->shared_memory_.memory(); + + 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); + + // We have a valid table, so compute our pointers. + priv->ComputeMappedPointers(memory); + + return priv.release(); +} + +void StatsTable::Private::InitializeTable(void* memory, int size, + int max_counters, + int max_threads) { + // Zero everything. + memset(memory, 0, size); + + // Initialize the header. + TableHeader* header = static_cast<TableHeader*>(memory); + header->version = kTableVersion; + header->size = size; + header->max_counters = max_counters; + header->max_threads = max_threads; +} + +void StatsTable::Private::ComputeMappedPointers(void* memory) { + char* data = static_cast<char*>(memory); + int offset = 0; + + table_header_ = reinterpret_cast<TableHeader*>(data); + offset += sizeof(*table_header_); + offset += AlignOffset(offset); + + // Verify we're looking at a valid StatsTable. + DCHECK_EQ(table_header_->version, kTableVersion); + + thread_names_table_ = reinterpret_cast<char*>(data + offset); + offset += sizeof(char) * + max_threads() * StatsTable::kMaxThreadNameLength; + offset += AlignOffset(offset); + + thread_tid_table_ = reinterpret_cast<PlatformThreadId*>(data + offset); + offset += sizeof(int) * max_threads(); + offset += AlignOffset(offset); + + thread_pid_table_ = reinterpret_cast<int*>(data + offset); + offset += sizeof(int) * max_threads(); + offset += AlignOffset(offset); + + counter_names_table_ = reinterpret_cast<char*>(data + offset); + offset += sizeof(char) * + max_counters() * StatsTable::kMaxCounterNameLength; + offset += AlignOffset(offset); + + data_table_ = reinterpret_cast<int*>(data + offset); + offset += sizeof(int) * max_threads() * max_counters(); + + DCHECK_EQ(offset, size()); +} + +// TLSData carries the data stored in the TLS slots for the +// StatsTable. This is used so that we can properly cleanup when the +// thread exits and return the table slot. +// +// Each thread that calls RegisterThread in the StatsTable will have +// a TLSData stored in its TLS. +struct StatsTable::TLSData { + StatsTable* table; + int slot; +}; + +// We keep a singleton table which can be easily accessed. +StatsTable* StatsTable::global_table_ = NULL; + +StatsTable::StatsTable(const std::string& name, int max_threads, + int max_counters) + : impl_(NULL), + tls_index_(SlotReturnFunction) { + int table_size = + AlignedSize(sizeof(Private::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); + + if (!impl_) + DPLOG(ERROR) << "StatsTable did not initialize"; +} + +StatsTable::~StatsTable() { + // Before we tear down our copy of the table, be sure to + // unregister our thread. + UnregisterThread(); + + // Return ThreadLocalStorage. At this point, if any registered threads + // still exist, they cannot Unregister. + tls_index_.Free(); + + // Cleanup our shared memory. + delete impl_; + + // If we are the global table, unregister ourselves. + if (global_table_ == this) + global_table_ = NULL; +} + +int StatsTable::GetSlot() const { + TLSData* data = GetTLSData(); + if (!data) + return 0; + return data->slot; +} + +int StatsTable::RegisterThread(const std::string& name) { + int slot = 0; + if (!impl_) + 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()); + slot = FindEmptyThread(); + if (!slot) { + return 0; + } + + // We have space, so consume a column in the table. + std::string thread_name = name; + if (name.empty()) + thread_name = kUnknownName; + strlcpy(impl_->thread_name(slot), thread_name.c_str(), + kMaxThreadNameLength); + *(impl_->thread_tid(slot)) = PlatformThread::CurrentId(); + *(impl_->thread_pid(slot)) = GetCurrentProcId(); + } + + // Set our thread local storage. + TLSData* data = new TLSData; + data->table = this; + data->slot = slot; + tls_index_.Set(data); + return slot; +} + +int StatsTable::CountThreadsRegistered() const { + if (!impl_) + 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); + if (*name != '\0') + count++; + } + return count; +} + +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_) + return 0; + + // Create a scope for our auto-lock. + { + AutoLock scoped_lock(counters_lock_); + + // Attempt to find the counter. + CountersMap::const_iterator iter; + iter = counters_.find(name); + if (iter != counters_.end()) + return iter->second; + } + + // Counter does not exist, so add it. + return AddCounter(name); +} + +int* StatsTable::GetLocation(int counter_id, int slot_id) const { + if (!impl_) + return NULL; + if (slot_id > impl_->max_threads()) + return NULL; + + int* row = impl_->row(counter_id); + return &(row[slot_id-1]); +} + +const char* StatsTable::GetRowName(int index) const { + if (!impl_) + return NULL; + + return impl_->counter_name(index); +} + +int StatsTable::GetRowValue(int index) const { + return GetRowValue(index, 0); +} + +int StatsTable::GetRowValue(int index, int pid) const { + if (!impl_) + return 0; + + int rv = 0; + int* row = impl_->row(index); + for (int slot_id = 0; slot_id < impl_->max_threads(); slot_id++) { + if (pid == 0 || *impl_->thread_pid(slot_id) == pid) + rv += row[slot_id]; + } + return rv; +} + +int StatsTable::GetCounterValue(const std::string& name) { + return GetCounterValue(name, 0); +} + +int StatsTable::GetCounterValue(const std::string& name, int pid) { + if (!impl_) + return 0; + + int row = FindCounter(name); + if (!row) + return 0; + return GetRowValue(row, pid); +} + +int StatsTable::GetMaxCounters() const { + if (!impl_) + return 0; + return impl_->max_counters(); +} + +int StatsTable::GetMaxThreads() const { + if (!impl_) + return 0; + return impl_->max_threads(); +} + +int* StatsTable::FindLocation(const char* name) { + // Get the static StatsTable + StatsTable *table = StatsTable::current(); + if (!table) + return NULL; + + // Get the slot for this thread. Try to register + // it if none exists. + int slot = table->GetSlot(); + if (!slot && !(slot = table->RegisterThread(""))) + return NULL; + + // Find the counter id for the counter. + std::string str_name(name); + int counter = table->FindCounter(str_name); + + // Now we can find the location in the table. + return table->GetLocation(counter, slot); +} + +void StatsTable::UnregisterThread() { + UnregisterThread(GetTLSData()); +} + +void StatsTable::UnregisterThread(TLSData* data) { + if (!data) + return; + DCHECK(impl_); + + // Mark the slot free by zeroing out the thread name. + char* name = impl_->thread_name(data->slot); + *name = '\0'; + + // Remove the calling thread's TLS so that it cannot use the slot. + tls_index_.Set(NULL); + delete data; +} + +void StatsTable::SlotReturnFunction(void* data) { + // This is called by the TLS destructor, which on some platforms has + // already cleared the TLS info, so use the tls_data argument + // rather than trying to fetch it ourselves. + TLSData* tls_data = static_cast<TLSData*>(data); + if (tls_data) { + DCHECK(tls_data->table); + tls_data->table->UnregisterThread(tls_data); + } +} + +int StatsTable::FindEmptyThread() const { + // Note: the API returns slots numbered from 1..N, although + // internally, the array is 0..N-1. This is so that we can return + // zero as "not found". + // + // The reason for doing this is because the thread 'slot' is stored + // 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_) + return 0; + + int index = 1; + for (; index <= impl_->max_threads(); index++) { + char* name = impl_->thread_name(index); + if (!*name) + break; + } + if (index > impl_->max_threads()) + return 0; // The table is full. + return index; +} + +int StatsTable::FindCounterOrEmptyRow(const std::string& name) const { + // Note: the API returns slots numbered from 1..N, although + // internally, the array is 0..N-1. This is so that we can return + // zero as "not found". + // + // 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_) + return 0; + + int free_slot = 0; + for (int index = 1; index <= impl_->max_counters(); index++) { + char* row_name = impl_->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)) + return index; + } + return free_slot; +} + +int StatsTable::AddCounter(const std::string& name) { + if (!impl_) + 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()); + + // We have space, so create a new counter. + counter_id = FindCounterOrEmptyRow(name); + if (!counter_id) + return 0; + + std::string counter_name = name; + if (name.empty()) + counter_name = kUnknownName; + strlcpy(impl_->counter_name(counter_id), counter_name.c_str(), + kMaxCounterNameLength); + } + + // now add to our in-memory cache + { + AutoLock lock(counters_lock_); + counters_[name] = counter_id; + } + return counter_id; +} + +StatsTable::TLSData* StatsTable::GetTLSData() const { + TLSData* data = + static_cast<TLSData*>(tls_index_.Get()); + if (!data) + return NULL; + + DCHECK(data->slot); + DCHECK_EQ(data->table, this); + return data; +} + +} // namespace base
diff --git a/src/base/metrics/stats_table.h b/src/base/metrics/stats_table.h new file mode 100644 index 0000000..d972e7c --- /dev/null +++ b/src/base/metrics/stats_table.h
@@ -0,0 +1,195 @@ +// 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. +// +// A StatsTable is a table of statistics. It can be used across multiple +// processes and threads, maintaining cheap statistics counters without +// locking. +// +// The goal is to make it very cheap and easy for developers to add +// counters to code, without having to build one-off utilities or mechanisms +// to track the counters, and also to allow a single "view" to display +// the contents of all counters. +// +// To achieve this, StatsTable creates a shared memory segment to store +// the data for the counters. Upon creation, it has a specific size +// which governs the maximum number of counters and concurrent +// threads/processes which can use it. +// + +#ifndef BASE_METRICS_STATS_TABLE_H_ +#define BASE_METRICS_STATS_TABLE_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_local_storage.h" + +namespace base { + +class BASE_EXPORT StatsTable { + public: + // Create a new StatsTable. + // If a StatsTable already exists with the specified name, this StatsTable + // will use the same shared memory segment as the original. Otherwise, + // a new StatsTable is created and all counters are zeroed. + // + // name is the name of the StatsTable to use. + // + // max_threads is the maximum number of threads the table will support. + // If the StatsTable already exists, this number is ignored. + // + // max_counters is the maximum number of counters the table will support. + // If the StatsTable already exists, this number is ignored. + StatsTable(const std::string& name, int max_threads, int max_counters); + + // Destroys the StatsTable. When the last StatsTable is destroyed + // (across all processes), the StatsTable is removed from disk. + ~StatsTable(); + + // For convenience, we create a static table. This is generally + // used automatically by the counters. + static StatsTable* current() { return global_table_; } + + // Set the global table for use in this process. + static void set_current(StatsTable* value) { global_table_ = value; } + + // Get the slot id for the calling thread. Returns 0 if no + // slot is assigned. + int GetSlot() const; + + // All threads that contribute data to the table must register with the + // table first. This function will set thread local storage for the + // thread containing the location in the table where this thread will + // write its counter data. + // + // name is just a debugging tag to label the thread, and it does not + // need to be unique. It will be truncated to kMaxThreadNameLength-1 + // characters. + // + // On success, returns the slot id for this thread. On failure, + // returns 0. + int RegisterThread(const std::string& name); + + // Returns the number of threads currently registered. This is really not + // useful except for diagnostics and debugging. + int CountThreadsRegistered() const; + + // Find a counter in the StatsTable. + // + // Returns an id for the counter which can be used to call GetLocation(). + // If the counter does not exist, attempts to create a row for the new + // counter. If there is no space in the table for the new counter, + // returns 0. + int FindCounter(const std::string& name); + + // TODO(mbelshe): implement RemoveCounter. + + // Gets the location of a particular value in the table based on + // the counter id and slot id. + int* GetLocation(int counter_id, int slot_id) const; + + // Gets the counter name at a particular row. If the row is empty, + // returns NULL. + const char* GetRowName(int index) const; + + // Gets the sum of the values for a particular row. + int GetRowValue(int index) const; + + // Gets the sum of the values for a particular row for a given pid. + int GetRowValue(int index, int pid) const; + + // Gets the sum of the values for a particular counter. If the counter + // does not exist, creates the counter. + int GetCounterValue(const std::string& name); + + // Gets the sum of the values for a particular counter for a given pid. + // If the counter does not exist, creates the counter. + int GetCounterValue(const std::string& name, int pid); + + // The maxinum number of counters/rows in the table. + int GetMaxCounters() const; + + // The maxinum number of threads/columns in the table. + int GetMaxThreads() const; + + // The maximum length (in characters) of a Thread's name including + // null terminator, as stored in the shared memory. + static const int kMaxThreadNameLength = 32; + + // The maximum length (in characters) of a Counter's name including + // null terminator, as stored in the shared memory. + static const int kMaxCounterNameLength = 64; + + // Convenience function to lookup a counter location for a + // counter by name for the calling thread. Will register + // the thread if it is not already registered. + static int* FindLocation(const char *name); + + private: + class Private; + struct TLSData; + typedef hash_map<std::string, int> CountersMap; + + // Returns the space occupied by a thread in the table. Generally used + // if a thread terminates but the process continues. This function + // does not zero out the thread's counters. + // Cannot be used inside a posix tls destructor. + void UnregisterThread(); + + // This variant expects the tls data to be passed in, so it is safe to + // call from inside a posix tls destructor (see doc for pthread_key_create). + void UnregisterThread(TLSData* tls_data); + + // The SlotReturnFunction is called at thread exit for each thread + // which used the StatsTable. + static void SlotReturnFunction(void* data); + + // Locates a free slot in the table. Returns a number > 0 on success, + // or 0 on failure. The caller must hold the shared_memory lock when + // calling this function. + int FindEmptyThread() const; + + // Locates a counter in the table or finds an empty row. Returns a + // number > 0 on success, or 0 on failure. The caller must hold the + // shared_memory_lock when calling this function. + int FindCounterOrEmptyRow(const std::string& name) const; + + // Internal function to add a counter to the StatsTable. Assumes that + // the counter does not already exist in the table. + // + // name is a unique identifier for this counter, and will be truncated + // to kMaxCounterNameLength-1 characters. + // + // On success, returns the counter_id for the newly added counter. + // On failure, returns 0. + int AddCounter(const std::string& name); + + // Get the TLS data for the calling thread. Returns NULL if none is + // initialized. + TLSData* GetTLSData() const; + + Private* impl_; + + // The counters_lock_ protects the counters_ hash table. + base::Lock counters_lock_; + + // The counters_ hash map is an in-memory hash of the counters. + // It is used for quick lookup of counters, but is cannot be used + // as a substitute for what is in the shared memory. Even though + // we don't have a counter in our hash table, another process may + // have created it. + CountersMap counters_; + ThreadLocalStorage::Slot tls_index_; + + static StatsTable* global_table_; + + DISALLOW_COPY_AND_ASSIGN(StatsTable); +}; + +} // namespace base + +#endif // BASE_METRICS_STATS_TABLE_H_
diff --git a/src/base/metrics/stats_table_unittest.cc b/src/base/metrics/stats_table_unittest.cc new file mode 100644 index 0000000..579bab7 --- /dev/null +++ b/src/base/metrics/stats_table_unittest.cc
@@ -0,0 +1,423 @@ +// 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/metrics/stats_counters.h" +#include "base/metrics/stats_table.h" +#include "base/shared_memory.h" +#include "base/stringprintf.h" +#include "base/string_piece.h" +#include "base/test/multiprocess_test.h" +#include "base/threading/platform_thread.h" +#include "base/threading/simple_thread.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +namespace base { + +class StatsTableTest : public MultiProcessTest { + public: + void DeleteShmem(const std::string& name) { + SharedMemory mem; + mem.Delete(name); + } +}; + +// Open a StatsTable and verify that we can write to each of the +// locations in the table. +TEST_F(StatsTableTest, VerifySlots) { + const std::string kTableName = "VerifySlotsStatTable"; + const int kMaxThreads = 1; + const int kMaxCounter = 5; + DeleteShmem(kTableName); + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + + // Register a single thread. + std::string thread_name = "mainThread"; + int slot_id = table.RegisterThread(thread_name); + EXPECT_NE(slot_id, 0); + + // Fill up the table with counters. + std::string counter_base_name = "counter"; + for (int index = 0; index < kMaxCounter; index++) { + std::string counter_name = counter_base_name; + base::StringAppendF(&counter_name, "counter.ctr%d", index); + int counter_id = table.FindCounter(counter_name); + EXPECT_GT(counter_id, 0); + } + + // Try to allocate an additional thread. Verify it fails. + slot_id = table.RegisterThread("too many threads"); + EXPECT_EQ(slot_id, 0); + + // Try to allocate an additional counter. Verify it fails. + int counter_id = table.FindCounter(counter_base_name); + EXPECT_EQ(counter_id, 0); + + DeleteShmem(kTableName); +} + +// CounterZero will continually be set to 0. +const std::string kCounterZero = "CounterZero"; +// Counter1313 will continually be set to 1313. +const std::string kCounter1313 = "Counter1313"; +// CounterIncrement will be incremented each time. +const std::string kCounterIncrement = "CounterIncrement"; +// CounterDecrement will be decremented each time. +const std::string kCounterDecrement = "CounterDecrement"; +// CounterMixed will be incremented by odd numbered threads and +// decremented by even threads. +const std::string kCounterMixed = "CounterMixed"; +// The number of thread loops that we will do. +const int kThreadLoops = 100; + +class StatsTableThread : public SimpleThread { + public: + StatsTableThread(std::string name, int id) + : SimpleThread(name), + id_(id) {} + + virtual void Run() OVERRIDE; + + private: + int id_; +}; + +void StatsTableThread::Run() { + // Each thread will open the shared memory and set counters + // concurrently in a loop. We'll use some pauses to + // mixup the thread scheduling. + + StatsCounter zero_counter(kCounterZero); + StatsCounter lucky13_counter(kCounter1313); + StatsCounter increment_counter(kCounterIncrement); + StatsCounter decrement_counter(kCounterDecrement); + for (int index = 0; index < kThreadLoops; index++) { + StatsCounter mixed_counter(kCounterMixed); // create this one in the loop + zero_counter.Set(0); + lucky13_counter.Set(1313); + increment_counter.Increment(); + decrement_counter.Decrement(); + if (id_ % 2) + mixed_counter.Decrement(); + else + mixed_counter.Increment(); + PlatformThread::Sleep(TimeDelta::FromMilliseconds(index % 10)); + } +} + +// Create a few threads and have them poke on their counters. +// See http://crbug.com/10611 for more information. +#if defined(OS_MACOSX) +#define MAYBE_MultipleThreads DISABLED_MultipleThreads +#else +#define MAYBE_MultipleThreads MultipleThreads +#endif +TEST_F(StatsTableTest, MAYBE_MultipleThreads) { + // Create a stats table. + const std::string kTableName = "MultipleThreadStatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + DeleteShmem(kTableName); + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + StatsTable::set_current(&table); + + EXPECT_EQ(0, table.CountThreadsRegistered()); + + // Spin up a set of threads to go bang on the various counters. + // After we join the threads, we'll make sure the counters + // contain the values we expected. + StatsTableThread* threads[kMaxThreads]; + + // Spawn the threads. + for (int index = 0; index < kMaxThreads; index++) { + threads[index] = new StatsTableThread("MultipleThreadsTest", index); + threads[index]->Start(); + } + + // Wait for the threads to finish. + for (int index = 0; index < kMaxThreads; index++) { + threads[index]->Join(); + delete threads[index]; + } + + StatsCounter zero_counter(kCounterZero); + StatsCounter lucky13_counter(kCounter1313); + StatsCounter increment_counter(kCounterIncrement); + StatsCounter decrement_counter(kCounterDecrement); + StatsCounter mixed_counter(kCounterMixed); + + // Verify the various counters are correct. + std::string name; + name = "c:" + kCounterZero; + EXPECT_EQ(0, table.GetCounterValue(name)); + name = "c:" + kCounter1313; + EXPECT_EQ(1313 * kMaxThreads, + table.GetCounterValue(name)); + name = "c:" + kCounterIncrement; + EXPECT_EQ(kMaxThreads * kThreadLoops, + table.GetCounterValue(name)); + name = "c:" + kCounterDecrement; + EXPECT_EQ(-kMaxThreads * kThreadLoops, + table.GetCounterValue(name)); + name = "c:" + kCounterMixed; + EXPECT_EQ((kMaxThreads % 2) * kThreadLoops, + table.GetCounterValue(name)); + EXPECT_EQ(0, table.CountThreadsRegistered()); + + DeleteShmem(kTableName); +} + +const std::string kMPTableName = "MultipleProcessStatTable"; + +MULTIPROCESS_TEST_MAIN(StatsTableMultipleProcessMain) { + // Each process will open the shared memory and set counters + // concurrently in a loop. We'll use some pauses to + // mixup the scheduling. + + StatsTable table(kMPTableName, 0, 0); + StatsTable::set_current(&table); + StatsCounter zero_counter(kCounterZero); + StatsCounter lucky13_counter(kCounter1313); + StatsCounter increment_counter(kCounterIncrement); + StatsCounter decrement_counter(kCounterDecrement); + for (int index = 0; index < kThreadLoops; index++) { + zero_counter.Set(0); + lucky13_counter.Set(1313); + increment_counter.Increment(); + decrement_counter.Decrement(); + PlatformThread::Sleep(TimeDelta::FromMilliseconds(index % 10)); + } + return 0; +} + +// Create a few processes and have them poke on their counters. +// This test is slow and flaky http://crbug.com/10611 +TEST_F(StatsTableTest, DISABLED_MultipleProcesses) { + // Create a stats table. + const int kMaxProcs = 20; + const int kMaxCounter = 5; + DeleteShmem(kMPTableName); + StatsTable table(kMPTableName, kMaxProcs, kMaxCounter); + StatsTable::set_current(&table); + EXPECT_EQ(0, table.CountThreadsRegistered()); + + // Spin up a set of processes to go bang on the various counters. + // After we join the processes, we'll make sure the counters + // contain the values we expected. + ProcessHandle procs[kMaxProcs]; + + // Spawn the processes. + for (int16 index = 0; index < kMaxProcs; index++) { + procs[index] = this->SpawnChild("StatsTableMultipleProcessMain", false); + EXPECT_NE(kNullProcessHandle, procs[index]); + } + + // Wait for the processes to finish. + for (int index = 0; index < kMaxProcs; index++) { + EXPECT_TRUE(WaitForSingleProcess( + procs[index], base::TimeDelta::FromMinutes(1))); + CloseProcessHandle(procs[index]); + } + + StatsCounter zero_counter(kCounterZero); + StatsCounter lucky13_counter(kCounter1313); + StatsCounter increment_counter(kCounterIncrement); + StatsCounter decrement_counter(kCounterDecrement); + + // Verify the various counters are correct. + std::string name; + name = "c:" + kCounterZero; + EXPECT_EQ(0, table.GetCounterValue(name)); + name = "c:" + kCounter1313; + EXPECT_EQ(1313 * kMaxProcs, + table.GetCounterValue(name)); + name = "c:" + kCounterIncrement; + EXPECT_EQ(kMaxProcs * kThreadLoops, + table.GetCounterValue(name)); + name = "c:" + kCounterDecrement; + EXPECT_EQ(-kMaxProcs * kThreadLoops, + table.GetCounterValue(name)); + EXPECT_EQ(0, table.CountThreadsRegistered()); + + DeleteShmem(kMPTableName); +} + +class MockStatsCounter : public StatsCounter { + public: + explicit MockStatsCounter(const std::string& name) + : StatsCounter(name) {} + int* Pointer() { return GetPtr(); } +}; + +// Test some basic StatsCounter operations +TEST_F(StatsTableTest, StatsCounter) { + // Create a stats table. + const std::string kTableName = "StatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + DeleteShmem(kTableName); + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + StatsTable::set_current(&table); + + MockStatsCounter foo("foo"); + + // Test initial state. + EXPECT_TRUE(foo.Enabled()); + ASSERT_NE(foo.Pointer(), static_cast<int*>(0)); + EXPECT_EQ(0, *(foo.Pointer())); + EXPECT_EQ(0, table.GetCounterValue("c:foo")); + + // Test Increment. + while (*(foo.Pointer()) < 123) foo.Increment(); + EXPECT_EQ(123, table.GetCounterValue("c:foo")); + foo.Add(0); + EXPECT_EQ(123, table.GetCounterValue("c:foo")); + foo.Add(-1); + EXPECT_EQ(122, table.GetCounterValue("c:foo")); + + // Test Set. + foo.Set(0); + EXPECT_EQ(0, table.GetCounterValue("c:foo")); + foo.Set(100); + EXPECT_EQ(100, table.GetCounterValue("c:foo")); + foo.Set(-1); + EXPECT_EQ(-1, table.GetCounterValue("c:foo")); + foo.Set(0); + EXPECT_EQ(0, table.GetCounterValue("c:foo")); + + // Test Decrement. + foo.Subtract(1); + EXPECT_EQ(-1, table.GetCounterValue("c:foo")); + foo.Subtract(0); + EXPECT_EQ(-1, table.GetCounterValue("c:foo")); + foo.Subtract(-1); + EXPECT_EQ(0, table.GetCounterValue("c:foo")); + + DeleteShmem(kTableName); +} + +class MockStatsCounterTimer : public StatsCounterTimer { + public: + explicit MockStatsCounterTimer(const std::string& name) + : StatsCounterTimer(name) {} + + TimeTicks start_time() { return start_time_; } + TimeTicks stop_time() { return stop_time_; } +}; + +// Test some basic StatsCounterTimer operations +TEST_F(StatsTableTest, StatsCounterTimer) { + // Create a stats table. + const std::string kTableName = "StatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + DeleteShmem(kTableName); + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + StatsTable::set_current(&table); + + MockStatsCounterTimer bar("bar"); + + // Test initial state. + EXPECT_FALSE(bar.Running()); + EXPECT_TRUE(bar.start_time().is_null()); + EXPECT_TRUE(bar.stop_time().is_null()); + + const TimeDelta kDuration = TimeDelta::FromMilliseconds(100); + + // Do some timing. + bar.Start(); + PlatformThread::Sleep(kDuration); + bar.Stop(); + EXPECT_GT(table.GetCounterValue("t:bar"), 0); + EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:bar")); + + // Verify that timing again is additive. + bar.Start(); + PlatformThread::Sleep(kDuration); + bar.Stop(); + EXPECT_GT(table.GetCounterValue("t:bar"), 0); + EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:bar")); + DeleteShmem(kTableName); +} + +// Test some basic StatsRate operations +TEST_F(StatsTableTest, StatsRate) { + // Create a stats table. + const std::string kTableName = "StatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + DeleteShmem(kTableName); + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + StatsTable::set_current(&table); + + StatsRate baz("baz"); + + // Test initial state. + EXPECT_FALSE(baz.Running()); + EXPECT_EQ(0, table.GetCounterValue("c:baz")); + EXPECT_EQ(0, table.GetCounterValue("t:baz")); + + const TimeDelta kDuration = TimeDelta::FromMilliseconds(100); + + // Do some timing. + baz.Start(); + PlatformThread::Sleep(kDuration); + baz.Stop(); + EXPECT_EQ(1, table.GetCounterValue("c:baz")); + EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:baz")); + + // Verify that timing again is additive. + baz.Start(); + PlatformThread::Sleep(kDuration); + baz.Stop(); + EXPECT_EQ(2, table.GetCounterValue("c:baz")); + EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:baz")); + DeleteShmem(kTableName); +} + +// Test some basic StatsScope operations +TEST_F(StatsTableTest, StatsScope) { + // Create a stats table. + const std::string kTableName = "StatTable"; + const int kMaxThreads = 20; + const int kMaxCounter = 5; + DeleteShmem(kTableName); + StatsTable table(kTableName, kMaxThreads, kMaxCounter); + StatsTable::set_current(&table); + + StatsCounterTimer foo("foo"); + StatsRate bar("bar"); + + // Test initial state. + EXPECT_EQ(0, table.GetCounterValue("t:foo")); + EXPECT_EQ(0, table.GetCounterValue("t:bar")); + EXPECT_EQ(0, table.GetCounterValue("c:bar")); + + const TimeDelta kDuration = TimeDelta::FromMilliseconds(100); + + // Try a scope. + { + StatsScope<StatsCounterTimer> timer(foo); + StatsScope<StatsRate> timer2(bar); + PlatformThread::Sleep(kDuration); + } + EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:foo")); + EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:bar")); + EXPECT_EQ(1, table.GetCounterValue("c:bar")); + + // Try a second scope. + { + StatsScope<StatsCounterTimer> timer(foo); + StatsScope<StatsRate> timer2(bar); + PlatformThread::Sleep(kDuration); + } + EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:foo")); + EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:bar")); + EXPECT_EQ(2, table.GetCounterValue("c:bar")); + + DeleteShmem(kTableName); +} + +} // namespace base
diff --git a/src/base/move.h b/src/base/move.h new file mode 100644 index 0000000..b92f22e --- /dev/null +++ b/src/base/move.h
@@ -0,0 +1,207 @@ +// 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_MOVE_H_ +#define BASE_MOVE_H_ + +// Macro with the boilerplate that makes a type move-only in C++03. +// +// USAGE +// +// This macro should be used instead of DISALLOW_COPY_AND_ASSIGN to create +// a "move-only" type. Unlike DISALLOW_COPY_AND_ASSIGN, this macro should be +// the first line in a class declaration. +// +// A class using this macro must call .Pass() (or somehow be an r-value already) +// before it can be: +// +// * Passed as a function argument +// * Used as the right-hand side of an assignment +// * Returned from a function +// +// Each class will still need to define their own "move constructor" and "move +// operator=" to make this useful. Here's an example of the macro, the move +// constructor, and the move operator= from the scoped_ptr class: +// +// template <typename T> +// class scoped_ptr { +// MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) +// public: +// scoped_ptr(RValue& other) : ptr_(other.release()) { } +// scoped_ptr& operator=(RValue& other) { +// swap(other); +// return *this; +// } +// }; +// +// Note that the constructor must NOT be marked explicit. +// +// For consistency, the second parameter to the macro should always be RValue +// unless you have a strong reason to do otherwise. It is only exposed as a +// macro parameter so that the move constructor and move operator= don't look +// like they're using a phantom type. +// +// +// HOW THIS WORKS +// +// For a thorough explanation of this technique, see: +// +// http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Move_Constructor +// +// The summary is that we take advantage of 2 properties: +// +// 1) non-const references will not bind to r-values. +// 2) C++ can apply one user-defined conversion when initializing a +// variable. +// +// The first lets us disable the copy constructor and assignment operator +// by declaring private version of them with a non-const reference parameter. +// +// For l-values, direct initialization still fails like in +// DISALLOW_COPY_AND_ASSIGN because the copy constructor and assignment +// operators are private. +// +// For r-values, the situation is different. The copy constructor and +// assignment operator are not viable due to (1), so we are trying to call +// a non-existent constructor and non-existing operator= rather than a private +// one. Since we have not committed an error quite yet, we can provide an +// alternate conversion sequence and a constructor. We add +// +// * a private struct named "RValue" +// * a user-defined conversion "operator RValue()" +// * a "move constructor" and "move operator=" that take the RValue& as +// their sole parameter. +// +// Only r-values will trigger this sequence and execute our "move constructor" +// or "move operator=." L-values will match the private copy constructor and +// operator= first giving a "private in this context" error. This combination +// gives us a move-only type. +// +// For signaling a destructive transfer of data from an l-value, we provide a +// method named Pass() which creates an r-value for the current instance +// triggering the move constructor or move operator=. +// +// Other ways to get r-values is to use the result of an expression like a +// function call. +// +// Here's an example with comments explaining what gets triggered where: +// +// class Foo { +// MOVE_ONLY_TYPE_FOR_CPP_03(Foo, RValue); +// +// public: +// ... API ... +// Foo(RValue other); // Move constructor. +// Foo& operator=(RValue rhs); // Move operator= +// }; +// +// Foo MakeFoo(); // Function that returns a Foo. +// +// Foo f; +// Foo f_copy(f); // ERROR: Foo(Foo&) is private in this context. +// Foo f_assign; +// f_assign = f; // ERROR: operator=(Foo&) is private in this context. +// +// +// Foo f(MakeFoo()); // R-value so alternate conversion executed. +// Foo f_copy(f.Pass()); // R-value so alternate conversion executed. +// f = f_copy.Pass(); // R-value so alternate conversion executed. +// +// +// IMPLEMENTATION SUBTLETIES WITH RValue +// +// The RValue struct is just a container for a pointer back to the original +// object. It should only ever be created as a temporary, and no external +// class should ever declare it or use it in a parameter. +// +// It is tempting to want to use the RValue type in function parameters, but +// excluding the limited usage here for the move constructor and move +// operator=, doing so would mean that the function could take both r-values +// and l-values equially which is unexpected. See COMPARED To Boost.Move for +// more details. +// +// An alternate, and incorrect, implementation of the RValue class used by +// Boost.Move makes RValue a fieldless child of the move-only type. RValue& +// is then used in place of RValue in the various operators. The RValue& is +// "created" by doing *reinterpret_cast<RValue*>(this). This has the appeal +// of never creating a temproary RValue struct even with optimizations +// disabled. Also, by virtue of inheritance you can treat the RValue +// reference as if it were the move-only type itself. Unfortuantely, +// using the result of this reinterpret_cast<> is actually undefined behavior +// due to C++98 5.2.10.7. In certain compilers (eg., NaCl) the optimizer +// will generate non-working code. +// +// In optimized builds, both implementations generate the same assembly so we +// choose the one that adheres to the standard. +// +// +// COMPARED TO C++11 +// +// In C++11, you would implement this functionality using an r-value reference +// and our .Pass() method would be replaced with a call to std::move(). +// +// This emulation also has a deficiency where it uses up the single +// user-defined conversion allowed by C++ during initialization. This can +// cause problems in some API edge cases. For instance, in scoped_ptr, it is +// impossible to make an function "void Foo(scoped_ptr<Parent> p)" accept a +// value of type scoped_ptr<Child> even if you add a constructor to +// scoped_ptr<> that would make it look like it should work. C++11 does not +// have this deficiency. +// +// +// COMPARED TO Boost.Move +// +// Our implementation similar to Boost.Move, but we keep the RValue struct +// private to the move-only type, and we don't use the reinterpret_cast<> hack. +// +// In Boost.Move, RValue is the boost::rv<> template. This type can be used +// when writing APIs like: +// +// void MyFunc(boost::rv<Foo>& f) +// +// that can take advantage of rv<> to avoid extra copies of a type. However you +// would still be able to call this version of MyFunc with an l-value: +// +// Foo f; +// MyFunc(f); // Uh oh, we probably just destroyed |f| w/o calling Pass(). +// +// unless someone is very careful to also declare a parallel override like: +// +// void MyFunc(const Foo& f) +// +// that would catch the l-values first. This was declared unsafe in C++11 and +// a C++11 compiler will explicitly fail MyFunc(f). Unfortunately, we cannot +// ensure this in C++03. +// +// Since we have no need for writing such APIs yet, our implementation keeps +// RValue private and uses a .Pass() method to do the conversion instead of +// trying to write a version of "std::move()." Writing an API like std::move() +// would require the RValue structs to be public. +// +// +// CAVEATS +// +// If you include a move-only type as a field inside a class that does not +// explicitly declare a copy constructor, the containing class's implicit +// copy constructor will change from Containing(const Containing&) to +// Containing(Containing&). This can cause some unexpected errors. +// +// http://llvm.org/bugs/show_bug.cgi?id=11528 +// +// The workaround is to explicitly declare your copy constructor. +// +#define MOVE_ONLY_TYPE_FOR_CPP_03(type, rvalue_type) \ + private: \ + struct rvalue_type { \ + rvalue_type(type* object) : object(object) {} \ + type* object; \ + }; \ + type(type&); \ + void operator=(type&); \ + public: \ + operator rvalue_type() { return rvalue_type(this); } \ + type Pass() { return type(rvalue_type(this)); } \ + private: + +#endif // BASE_MOVE_H_
diff --git a/src/base/native_library.h b/src/base/native_library.h new file mode 100644 index 0000000..845989d --- /dev/null +++ b/src/base/native_library.h
@@ -0,0 +1,85 @@ +// 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. + +#ifndef BASE_NATIVE_LIBRARY_H_ +#define BASE_NATIVE_LIBRARY_H_ + +// This file defines a cross-platform "NativeLibrary" type which represents +// a loadable module. + +#include "base/base_export.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#elif defined(OS_MACOSX) +#import <CoreFoundation/CoreFoundation.h> +#endif // OS_* + +#include "base/string16.h" + +// Macro useful for writing cross-platform function pointers. +#if defined(OS_WIN) && !defined(CDECL) +#define CDECL __cdecl +#else +#define CDECL +#endif + +class FilePath; + +namespace base { + +#if defined(OS_WIN) +typedef HMODULE NativeLibrary; +#elif defined(OS_MACOSX) +enum NativeLibraryType { + BUNDLE, + DYNAMIC_LIB +}; +struct NativeLibraryStruct { + NativeLibraryType type; + CFBundleRefNum bundle_resource_ref; + union { + CFBundleRef bundle; + void* dylib; + }; +}; +typedef NativeLibraryStruct* NativeLibrary; +#elif defined(OS_POSIX) +typedef void* NativeLibrary; +#endif // OS_* + +// Loads a native library from disk. Release it with UnloadNativeLibrary when +// you're done. Returns NULL on failure. +// If |err| is not NULL, it may be filled in with an error message on +// error. +BASE_EXPORT NativeLibrary LoadNativeLibrary(const FilePath& library_path, + std::string* error); + +#if defined(OS_WIN) +// Loads a native library from disk. Release it with UnloadNativeLibrary when +// you're done. +// This function retrieves the LoadLibrary function exported from kernel32.dll +// and calls it instead of directly calling the LoadLibrary function via the +// import table. +BASE_EXPORT NativeLibrary LoadNativeLibraryDynamically( + const FilePath& library_path); +#endif // OS_WIN + +// Unloads a native library. +BASE_EXPORT void UnloadNativeLibrary(NativeLibrary library); + +// Gets a function pointer from a native library. +BASE_EXPORT void* GetFunctionPointerFromNativeLibrary(NativeLibrary library, + const char* name); + +// Returns the full platform specific name for a native library. +// For example: +// "mylib" returns "mylib.dll" on Windows, "libmylib.so" on Linux, +// "mylib.dylib" on Mac. +BASE_EXPORT string16 GetNativeLibraryName(const string16& name); + +} // namespace base + +#endif // BASE_NATIVE_LIBRARY_H_
diff --git a/src/base/native_library_mac.mm b/src/base/native_library_mac.mm new file mode 100644 index 0000000..9f7a967 --- /dev/null +++ b/src/base/native_library_mac.mm
@@ -0,0 +1,80 @@ +// 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/native_library.h" + +#include <dlfcn.h> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/string_util.h" +#include "base/threading/thread_restrictions.h" +#include "base/utf_string_conversions.h" + +namespace base { + +// static +NativeLibrary LoadNativeLibrary(const FilePath& library_path, + std::string* error) { + // dlopen() etc. open the file off disk. + if (library_path.Extension() == "dylib" || + !file_util::DirectoryExists(library_path)) { + void* dylib = dlopen(library_path.value().c_str(), RTLD_LAZY); + if (!dylib) + return NULL; + NativeLibrary native_lib = new NativeLibraryStruct(); + native_lib->type = DYNAMIC_LIB; + native_lib->dylib = dylib; + return native_lib; + } + base::mac::ScopedCFTypeRef<CFURLRef> url( + CFURLCreateFromFileSystemRepresentation( + kCFAllocatorDefault, + (const UInt8*)library_path.value().c_str(), + library_path.value().length(), + true)); + if (!url) + return NULL; + CFBundleRef bundle = CFBundleCreate(kCFAllocatorDefault, url.get()); + if (!bundle) + return NULL; + + NativeLibrary native_lib = new NativeLibraryStruct(); + native_lib->type = BUNDLE; + native_lib->bundle = bundle; + native_lib->bundle_resource_ref = CFBundleOpenBundleResourceMap(bundle); + return native_lib; +} + +// static +void UnloadNativeLibrary(NativeLibrary library) { + if (library->type == BUNDLE) { + CFBundleCloseBundleResourceMap(library->bundle, + library->bundle_resource_ref); + CFRelease(library->bundle); + } else { + dlclose(library->dylib); + } + delete library; +} + +// static +void* GetFunctionPointerFromNativeLibrary(NativeLibrary library, + const char* name) { + if (library->type == BUNDLE) { + base::mac::ScopedCFTypeRef<CFStringRef> symbol_name( + CFStringCreateWithCString(kCFAllocatorDefault, name, + kCFStringEncodingUTF8)); + return CFBundleGetFunctionPointerForName(library->bundle, symbol_name); + } + return dlsym(library->dylib, name); +} + +// static +string16 GetNativeLibraryName(const string16& name) { + return name + ASCIIToUTF16(".dylib"); +} + +} // namespace base
diff --git a/src/base/native_library_posix.cc b/src/base/native_library_posix.cc new file mode 100644 index 0000000..4b82ff4 --- /dev/null +++ b/src/base/native_library_posix.cc
@@ -0,0 +1,53 @@ +// 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/native_library.h" + +#include <dlfcn.h> + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" +#include "base/utf_string_conversions.h" + +namespace base { + +// static +NativeLibrary LoadNativeLibrary(const FilePath& library_path, + std::string* error) { + // dlopen() opens the file off disk. + base::ThreadRestrictions::AssertIOAllowed(); + + // We deliberately do not use RTLD_DEEPBIND. For the history why, please + // refer to the bug tracker. Some useful bug reports to read include: + // http://crbug.com/17943, http://crbug.com/17557, http://crbug.com/36892, + // and http://crbug.com/40794. + void* dl = dlopen(library_path.value().c_str(), RTLD_LAZY); + if (!dl && error) + *error = dlerror(); + + return dl; +} + +// static +void UnloadNativeLibrary(NativeLibrary library) { + int ret = dlclose(library); + if (ret < 0) { + DLOG(ERROR) << "dlclose failed: " << dlerror(); + NOTREACHED(); + } +} + +// static +void* GetFunctionPointerFromNativeLibrary(NativeLibrary library, + const char* name) { + return dlsym(library, name); +} + +// static +string16 GetNativeLibraryName(const string16& name) { + return ASCIIToUTF16("lib") + name + ASCIIToUTF16(".so"); +} + +} // namespace base
diff --git a/src/base/native_library_shell.cc b/src/base/native_library_shell.cc new file mode 100644 index 0000000..40cf8b2 --- /dev/null +++ b/src/base/native_library_shell.cc
@@ -0,0 +1,39 @@ +// 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/native_library.h" + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" +#include "base/utf_string_conversions.h" + +namespace base { + +// static +NativeLibrary LoadNativeLibrary(const FilePath& library_path, + std::string* error) { + NOTIMPLEMENTED(); + return 0; +} + +// static +void UnloadNativeLibrary(NativeLibrary library) { + NOTIMPLEMENTED(); +} + +// static +void* GetFunctionPointerFromNativeLibrary(NativeLibrary library, + const char* name) { + NOTIMPLEMENTED(); + return NULL; +} + +// static +string16 GetNativeLibraryName(const string16& name) { + NOTIMPLEMENTED(); + return string16(); +} + +} // namespace base
diff --git a/src/base/native_library_win.cc b/src/base/native_library_win.cc new file mode 100644 index 0000000..af00cd1 --- /dev/null +++ b/src/base/native_library_win.cc
@@ -0,0 +1,73 @@ +// 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/native_library.h" + +#include <windows.h> + +#include "base/file_util.h" +#include "base/threading/thread_restrictions.h" +#include "base/utf_string_conversions.h" + +namespace base { + +typedef HMODULE (WINAPI* LoadLibraryFunction)(const wchar_t* file_name); + +NativeLibrary LoadNativeLibraryHelper(const FilePath& library_path, + LoadLibraryFunction load_library_api) { + // LoadLibrary() opens the file off disk. + base::ThreadRestrictions::AssertIOAllowed(); + + // Switch the current directory to the library directory as the library + // may have dependencies on DLLs in this directory. + bool restore_directory = false; + FilePath current_directory; + if (file_util::GetCurrentDirectory(¤t_directory)) { + FilePath plugin_path = library_path.DirName(); + if (!plugin_path.empty()) { + file_util::SetCurrentDirectory(plugin_path); + restore_directory = true; + } + } + + HMODULE module = (*load_library_api)(library_path.value().c_str()); + if (restore_directory) + file_util::SetCurrentDirectory(current_directory); + + return module; +} + +// static +NativeLibrary LoadNativeLibrary(const FilePath& library_path, + std::string* error) { + return LoadNativeLibraryHelper(library_path, LoadLibraryW); +} + +NativeLibrary LoadNativeLibraryDynamically(const FilePath& library_path) { + typedef HMODULE (WINAPI* LoadLibraryFunction)(const wchar_t* file_name); + + LoadLibraryFunction load_library; + load_library = reinterpret_cast<LoadLibraryFunction>( + GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW")); + + return LoadNativeLibraryHelper(library_path, load_library); +} + +// static +void UnloadNativeLibrary(NativeLibrary library) { + FreeLibrary(library); +} + +// static +void* GetFunctionPointerFromNativeLibrary(NativeLibrary library, + const char* name) { + return GetProcAddress(library, name); +} + +// static +string16 GetNativeLibraryName(const string16& name) { + return name + ASCIIToUTF16(".dll"); +} + +} // namespace base
diff --git a/src/base/nix/mime_util_xdg.cc b/src/base/nix/mime_util_xdg.cc new file mode 100644 index 0000000..e58e03d --- /dev/null +++ b/src/base/nix/mime_util_xdg.cc
@@ -0,0 +1,678 @@ +// 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/nix/mime_util_xdg.h" + +#include <cstdlib> +#include <list> +#include <map> +#include <vector> + +#include "base/environment.h" +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/nix/xdg_util.h" +#include "base/string_split.h" +#include "base/string_util.h" +#include "base/synchronization/lock.h" +#include "base/third_party/xdg_mime/xdgmime.h" +#include "base/threading/thread_restrictions.h" +#include "base/time.h" + +#if defined(TOOLKIT_GTK) +#include <gtk/gtk.h> // NOLINT + +#include "base/message_loop.h" +#endif + +namespace { + +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; + +class MimeUtilConstants { + public: + typedef std::map<std::string, IconTheme*> IconThemeMap; + typedef std::map<FilePath, base::Time> IconDirMtimeMap; + typedef std::vector<std::string> IconFormats; + + // Specified by XDG icon theme specs. + static const int kUpdateIntervalInSeconds = 5; + + static const size_t kDefaultThemeNum = 4; + + static MimeUtilConstants* GetInstance() { + return Singleton<MimeUtilConstants>::get(); + } + + // Store icon directories and their mtimes. + IconDirMtimeMap icon_dirs_; + + // Store icon formats. + IconFormats icon_formats_; + + // Store loaded icon_theme. + IconThemeMap icon_themes_; + + // The default theme. + IconTheme* default_themes_[kDefaultThemeNum]; + + base::TimeTicks last_check_time_; + +#if defined(TOOLKIT_GTK) + // This is set by DetectGtkTheme(). We cache it so that we can access the + // theme name from threads that aren't allowed to call + // gtk_settings_get_default(). + std::string gtk_theme_name_; +#endif + + private: + MimeUtilConstants() { + icon_formats_.push_back(".png"); + icon_formats_.push_back(".svg"); + icon_formats_.push_back(".xpm"); + + for (size_t i = 0; i < kDefaultThemeNum; ++i) + default_themes_[i] = NULL; + } + ~MimeUtilConstants(); + + friend struct DefaultSingletonTraits<MimeUtilConstants>; + + DISALLOW_COPY_AND_ASSIGN(MimeUtilConstants); +}; + +// IconTheme represents an icon theme as defined by the xdg icon theme spec. +// Example themes on GNOME include 'Human' and 'Mist'. +// Example themes on KDE include 'crystalsvg' and 'kdeclassic'. +class IconTheme { + public: + // A theme consists of multiple sub-directories, like '32x32' and 'scalable'. + class SubDirInfo { + public: + // See spec for details. + enum Type { + Fixed, + Scalable, + Threshold + }; + SubDirInfo() + : size(0), + type(Threshold), + max_size(0), + min_size(0), + threshold(2) { + } + size_t size; // Nominal size of the icons in this directory. + Type type; // Type of the icon size. + size_t max_size; // Maximum size that the icons can be scaled to. + size_t min_size; // Minimum size that the icons can be scaled to. + size_t threshold; // Maximum difference from desired size. 2 by default. + }; + + explicit IconTheme(const std::string& name); + + ~IconTheme() {} + + // Returns the path to an icon with the name |icon_name| and a size of |size| + // pixels. If the icon does not exist, but |inherits| is true, then look for + // the icon in the parent theme. + FilePath GetIconPath(const std::string& icon_name, int size, bool inherits); + + // Load a theme with the name |theme_name| into memory. Returns null if theme + // is invalid. + static IconTheme* LoadTheme(const std::string& theme_name); + + private: + // Returns the path to an icon with the name |icon_name| in |subdir|. + FilePath GetIconPathUnderSubdir(const std::string& icon_name, + const std::string& subdir); + + // Whether the theme loaded properly. + bool IsValid() { + return index_theme_loaded_; + } + + // Read and parse |file| which is usually named 'index.theme' per theme spec. + bool LoadIndexTheme(const FilePath& file); + + // Checks to see if the icons in |info| matches |size| (in pixels). Returns + // 0 if they match, or the size difference in pixels. + size_t MatchesSize(SubDirInfo* info, size_t size); + + // Yet another function to read a line. + std::string ReadLine(FILE* fp); + + // Set directories to search for icons to the comma-separated list |dirs|. + bool SetDirectories(const std::string& dirs); + + bool index_theme_loaded_; // True if an instance is properly loaded. + // store the scattered directories of this theme. + std::list<FilePath> dirs_; + + // store the subdirs of this theme and array index of |info_array_|. + std::map<std::string, int> subdirs_; + scoped_array<SubDirInfo> info_array_; // List of sub-directories. + std::string inherits_; // Name of the theme this one inherits from. +}; + +IconTheme::IconTheme(const std::string& name) + : index_theme_loaded_(false), + info_array_(NULL) { + base::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; + FilePath theme_path; + MimeUtilConstants::IconDirMtimeMap* icon_dirs = + &MimeUtilConstants::GetInstance()->icon_dirs_; + for (iter = icon_dirs->begin(); iter != icon_dirs->end(); ++iter) { + theme_path = iter->first.Append(name); + if (!file_util::DirectoryExists(theme_path)) + continue; + FilePath theme_index = theme_path.Append("index.theme"); + if (!index_theme_loaded_ && file_util::PathExists(theme_index)) { + if (!LoadIndexTheme(theme_index)) + return; + index_theme_loaded_ = true; + } + dirs_.push_back(theme_path); + } +} + +FilePath IconTheme::GetIconPath(const std::string& icon_name, int size, + bool inherits) { + std::map<std::string, int>::iterator subdir_iter; + FilePath icon_path; + + for (subdir_iter = subdirs_.begin(); + subdir_iter != subdirs_.end(); + ++subdir_iter) { + SubDirInfo* info = &info_array_[subdir_iter->second]; + if (MatchesSize(info, size) == 0) { + icon_path = GetIconPathUnderSubdir(icon_name, subdir_iter->first); + if (!icon_path.empty()) + return icon_path; + } + } + // Now looking for the mostly matched. + size_t min_delta_seen = 9999; + + for (subdir_iter = subdirs_.begin(); + subdir_iter != subdirs_.end(); + ++subdir_iter) { + SubDirInfo* info = &info_array_[subdir_iter->second]; + size_t delta = MatchesSize(info, size); + if (delta < min_delta_seen) { + FilePath path = GetIconPathUnderSubdir(icon_name, subdir_iter->first); + if (!path.empty()) { + min_delta_seen = delta; + icon_path = path; + } + } + } + + if (!icon_path.empty() || !inherits || inherits_ == "") + return icon_path; + + IconTheme* theme = LoadTheme(inherits_); + // Inheriting from itself means the theme is buggy but we shouldn't crash. + if (theme && theme != this) + return theme->GetIconPath(icon_name, size, inherits); + else + return FilePath(); +} + +IconTheme* IconTheme::LoadTheme(const std::string& theme_name) { + scoped_ptr<IconTheme> theme; + MimeUtilConstants::IconThemeMap* icon_themes = + &MimeUtilConstants::GetInstance()->icon_themes_; + if (icon_themes->find(theme_name) != icon_themes->end()) { + theme.reset((*icon_themes)[theme_name]); + } else { + theme.reset(new IconTheme(theme_name)); + if (!theme->IsValid()) + theme.reset(); + (*icon_themes)[theme_name] = theme.get(); + } + return theme.release(); +} + +FilePath IconTheme::GetIconPathUnderSubdir(const std::string& icon_name, + const std::string& subdir) { + FilePath icon_path; + std::list<FilePath>::iterator dir_iter; + MimeUtilConstants::IconFormats* icon_formats = + &MimeUtilConstants::GetInstance()->icon_formats_; + for (dir_iter = dirs_.begin(); dir_iter != dirs_.end(); ++dir_iter) { + for (size_t i = 0; i < icon_formats->size(); ++i) { + icon_path = dir_iter->Append(subdir); + icon_path = icon_path.Append(icon_name + (*icon_formats)[i]); + if (file_util::PathExists(icon_path)) + return icon_path; + } + } + return FilePath(); +} + +bool IconTheme::LoadIndexTheme(const FilePath& file) { + FILE* fp = file_util::OpenFile(file, "r"); + SubDirInfo* current_info = NULL; + if (!fp) + return false; + + // Read entries. + while (!feof(fp) && !ferror(fp)) { + std::string buf = ReadLine(fp); + if (buf == "") + break; + + std::string entry; + TrimWhitespaceASCII(buf, TRIM_ALL, &entry); + if (entry.length() == 0 || entry[0] == '#') { + // Blank line or Comment. + continue; + } else if (entry[0] == '[' && info_array_.get()) { + current_info = NULL; + std::string subdir = entry.substr(1, entry.length() - 2); + if (subdirs_.find(subdir) != subdirs_.end()) + current_info = &info_array_[subdirs_[subdir]]; + } + + std::string key, value; + std::vector<std::string> r; + base::SplitStringDontTrim(entry, '=', &r); + if (r.size() < 2) + continue; + + TrimWhitespaceASCII(r[0], TRIM_ALL, &key); + for (size_t i = 1; i < r.size(); i++) + value.append(r[i]); + TrimWhitespaceASCII(value, TRIM_ALL, &value); + + if (current_info) { + if (key == "Size") { + current_info->size = atoi(value.c_str()); + } else if (key == "Type") { + if (value == "Fixed") + current_info->type = SubDirInfo::Fixed; + else if (value == "Scalable") + current_info->type = SubDirInfo::Scalable; + else if (value == "Threshold") + current_info->type = SubDirInfo::Threshold; + } else if (key == "MaxSize") { + current_info->max_size = atoi(value.c_str()); + } else if (key == "MinSize") { + current_info->min_size = atoi(value.c_str()); + } else if (key == "Threshold") { + current_info->threshold = atoi(value.c_str()); + } + } else { + if (key.compare("Directories") == 0 && !info_array_.get()) { + if (!SetDirectories(value)) break; + } else if (key.compare("Inherits") == 0) { + if (value != "hicolor") + inherits_ = value; + } + } + } + + file_util::CloseFile(fp); + return info_array_.get() != NULL; +} + +size_t IconTheme::MatchesSize(SubDirInfo* info, size_t size) { + if (info->type == SubDirInfo::Fixed) { + if (size > info->size) + return size - info->size; + else + return info->size - size; + } else if (info->type == SubDirInfo::Scalable) { + if (size < info->min_size) + return info->min_size - size; + if (size > info->max_size) + return size - info->max_size; + return 0; + } else { + if (size + info->threshold < info->size) + return info->size - size - info->threshold; + if (size > info->size + info->threshold) + return size - info->size - info->threshold; + return 0; + } +} + +std::string IconTheme::ReadLine(FILE* fp) { + if (!fp) + return ""; + + std::string result = ""; + const size_t kBufferSize = 100; + char buffer[kBufferSize]; + while ((fgets(buffer, kBufferSize - 1, fp)) != NULL) { + result += buffer; + size_t len = result.length(); + if (len == 0) + break; + char end = result[len - 1]; + if (end == '\n' || end == '\0') + break; + } + + return result; +} + +bool IconTheme::SetDirectories(const std::string& dirs) { + int num = 0; + std::string::size_type pos = 0, epos; + std::string dir; + while ((epos = dirs.find(',', pos)) != std::string::npos) { + TrimWhitespaceASCII(dirs.substr(pos, epos - pos), TRIM_ALL, &dir); + if (dir.length() == 0) { + DLOG(WARNING) << "Invalid index.theme: blank subdir"; + return false; + } + subdirs_[dir] = num++; + pos = epos + 1; + } + TrimWhitespaceASCII(dirs.substr(pos), TRIM_ALL, &dir); + if (dir.length() == 0) { + DLOG(WARNING) << "Invalid index.theme: blank subdir"; + return false; + } + subdirs_[dir] = num++; + info_array_.reset(new SubDirInfo[num]); + return true; +} + +bool CheckDirExistsAndGetMtime(const FilePath& dir, + base::Time* last_modified) { + if (!file_util::DirectoryExists(dir)) + return false; + base::PlatformFileInfo file_info; + if (!file_util::GetFileInfo(dir, &file_info)) + return false; + *last_modified = file_info.last_modified; + return true; +} + +// Make sure |dir| exists and add it to the list of icon directories. +void TryAddIconDir(const FilePath& dir) { + base::Time last_modified; + if (!CheckDirExistsAndGetMtime(dir, &last_modified)) + return; + MimeUtilConstants::GetInstance()->icon_dirs_[dir] = last_modified; +} + +// For a xdg directory |dir|, add the appropriate icon sub-directories. +void AddXDGDataDir(const FilePath& dir) { + if (!file_util::DirectoryExists(dir)) + return; + TryAddIconDir(dir.Append("icons")); + TryAddIconDir(dir.Append("pixmaps")); +} + +// Add all the xdg icon directories. +void InitIconDir() { + FilePath home = file_util::GetHomeDir(); + if (!home.empty()) { + FilePath legacy_data_dir(home); + legacy_data_dir = legacy_data_dir.AppendASCII(".icons"); + if (file_util::DirectoryExists(legacy_data_dir)) + TryAddIconDir(legacy_data_dir); + } + const char* env = getenv("XDG_DATA_HOME"); + if (env) { + AddXDGDataDir(FilePath(env)); + } else if (!home.empty()) { + FilePath local_data_dir(home); + local_data_dir = local_data_dir.AppendASCII(".local"); + local_data_dir = local_data_dir.AppendASCII("share"); + AddXDGDataDir(local_data_dir); + } + + env = getenv("XDG_DATA_DIRS"); + if (!env) { + AddXDGDataDir(FilePath("/usr/local/share")); + AddXDGDataDir(FilePath("/usr/share")); + } else { + std::string xdg_data_dirs = env; + std::string::size_type pos = 0, epos; + while ((epos = xdg_data_dirs.find(':', pos)) != std::string::npos) { + AddXDGDataDir(FilePath(xdg_data_dirs.substr(pos, epos - pos))); + pos = epos + 1; + } + AddXDGDataDir(FilePath(xdg_data_dirs.substr(pos))); + } +} + +void EnsureUpdated() { + MimeUtilConstants* constants = MimeUtilConstants::GetInstance(); + if (constants->last_check_time_.is_null()) { + constants->last_check_time_ = base::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_; + if (time_since_last_check.InSeconds() > constants->kUpdateIntervalInSeconds) { + constants->last_check_time_ += time_since_last_check; + + bool rescan_icon_dirs = false; + 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; + if (!CheckDirExistsAndGetMtime(iter->first, &last_modified) || + last_modified != iter->second) { + rescan_icon_dirs = true; + break; + } + } + + if (rescan_icon_dirs) { + constants->icon_dirs_.clear(); + constants->icon_themes_.clear(); + InitIconDir(); + } + } +} + +// Find a fallback icon if we cannot find it in the default theme. +FilePath LookupFallbackIcon(const std::string& icon_name) { + MimeUtilConstants* constants = MimeUtilConstants::GetInstance(); + MimeUtilConstants::IconDirMtimeMap::iterator iter; + MimeUtilConstants::IconDirMtimeMap* icon_dirs = &constants->icon_dirs_; + MimeUtilConstants::IconFormats* icon_formats = &constants->icon_formats_; + for (iter = icon_dirs->begin(); iter != icon_dirs->end(); ++iter) { + for (size_t i = 0; i < icon_formats->size(); ++i) { + FilePath icon = iter->first.Append(icon_name + (*icon_formats)[i]); + if (file_util::PathExists(icon)) + return icon; + } + } + return FilePath(); +} + +// Initialize the list of default themes. +void InitDefaultThemes() { + IconTheme** default_themes = + MimeUtilConstants::GetInstance()->default_themes_; + + scoped_ptr<base::Environment> env(base::Environment::Create()); + base::nix::DesktopEnvironment desktop_env = + base::nix::GetDesktopEnvironment(env.get()); + if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE3 || + desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE4) { + // KDE + std::string kde_default_theme; + std::string kde_fallback_theme; + + // TODO(thestig): Figure out how to get the current icon theme on KDE. + // Setting stored in ~/.kde/share/config/kdeglobals under Icons -> Theme. + default_themes[0] = NULL; + + // Try some reasonable defaults for KDE. + if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE3) { + // KDE 3 + kde_default_theme = "default.kde"; + kde_fallback_theme = "crystalsvg"; + } else { + // KDE 4 + kde_default_theme = "default.kde4"; + kde_fallback_theme = "oxygen"; + } + default_themes[1] = IconTheme::LoadTheme(kde_default_theme); + default_themes[2] = IconTheme::LoadTheme(kde_fallback_theme); + } else { +#if defined(TOOLKIT_GTK) + // Assume it's Gnome and use GTK to figure out the theme. + default_themes[1] = IconTheme::LoadTheme( + MimeUtilConstants::GetInstance()->gtk_theme_name_); + default_themes[2] = IconTheme::LoadTheme("gnome"); +#endif + } + // hicolor needs to be last per icon theme spec. + default_themes[3] = IconTheme::LoadTheme("hicolor"); + + for (size_t i = 0; i < MimeUtilConstants::kDefaultThemeNum; i++) { + if (default_themes[i] == NULL) + continue; + // NULL out duplicate pointers. + for (size_t j = i + 1; j < MimeUtilConstants::kDefaultThemeNum; j++) { + if (default_themes[j] == default_themes[i]) + default_themes[j] = NULL; + } + } +} + +// Try to find an icon with the name |icon_name| that's |size| pixels. +FilePath LookupIconInDefaultTheme(const std::string& icon_name, int size) { + EnsureUpdated(); + MimeUtilConstants* constants = MimeUtilConstants::GetInstance(); + MimeUtilConstants::IconThemeMap* icon_themes = &constants->icon_themes_; + if (icon_themes->empty()) + InitDefaultThemes(); + + FilePath icon_path; + IconTheme** default_themes = constants->default_themes_; + for (size_t i = 0; i < MimeUtilConstants::kDefaultThemeNum; i++) { + if (default_themes[i]) { + icon_path = default_themes[i]->GetIconPath(icon_name, size, true); + if (!icon_path.empty()) + return icon_path; + } + } + return LookupFallbackIcon(icon_name); +} + +MimeUtilConstants::~MimeUtilConstants() { + for (size_t i = 0; i < kDefaultThemeNum; i++) + delete default_themes_[i]; +} + +} // namespace + +namespace base { +namespace nix { + +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()); + 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()); + return xdg_mime_get_mime_type_for_data(data.data(), data.length(), NULL); +} + +#if defined(TOOLKIT_GTK) +void DetectGtkTheme() { + // If the theme name is already loaded, do nothing. Chrome doesn't respond + // to changes in the system theme, so we never need to set this more than + // once. + if (!MimeUtilConstants::GetInstance()->gtk_theme_name_.empty()) + return; + + // We should only be called on the UI thread. + DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); + + gchar* gtk_theme_name; + g_object_get(gtk_settings_get_default(), + "gtk-icon-theme-name", + >k_theme_name, NULL); + MimeUtilConstants::GetInstance()->gtk_theme_name_.assign(gtk_theme_name); + g_free(gtk_theme_name); +} +#endif + +FilePath GetMimeIcon(const std::string& mime_type, size_t size) { + base::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()); + const char *icon = xdg_mime_get_icon(mime_type.c_str()); + icon_name = std::string(icon ? icon : ""); + } + + if (icon_name.length()) + icon_names.push_back(icon_name); + + // For text/plain, try text-plain. + icon_name = mime_type; + for (size_t i = icon_name.find('/', 0); i != std::string::npos; + i = icon_name.find('/', i + 1)) { + icon_name[i] = '-'; + } + icon_names.push_back(icon_name); + // Also try gnome-mime-text-plain. + icon_names.push_back("gnome-mime-" + icon_name); + + // Try "deb" for "application/x-deb" in KDE 3. + size_t x_substr_pos = mime_type.find("/x-"); + if (x_substr_pos != std::string::npos) { + icon_name = mime_type.substr(x_substr_pos + 3); + icon_names.push_back(icon_name); + } + + // Try generic name like text-x-generic. + icon_name = mime_type.substr(0, mime_type.find('/')) + "-x-generic"; + icon_names.push_back(icon_name); + + // Last resort + icon_names.push_back("unknown"); + + for (size_t i = 0; i < icon_names.size(); i++) { + if (icon_names[i][0] == '/') { + icon_file = FilePath(icon_names[i]); + if (file_util::PathExists(icon_file)) + return icon_file; + } else { + icon_file = LookupIconInDefaultTheme(icon_names[i], size); + if (!icon_file.empty()) + return icon_file; + } + } + return FilePath(); +} + +} // namespace nix +} // namespace base
diff --git a/src/base/nix/mime_util_xdg.h b/src/base/nix/mime_util_xdg.h new file mode 100644 index 0000000..444ce4f --- /dev/null +++ b/src/base/nix/mime_util_xdg.h
@@ -0,0 +1,43 @@ +// 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. + +#ifndef BASE_NIX_MIME_UTIL_XDG_H_ +#define BASE_NIX_MIME_UTIL_XDG_H_ + +#include <string> + +#include "base/base_export.h" +#include "build/build_config.h" + +class FilePath; + +namespace base { +namespace nix { + +// Gets the mime type for a file based on its filename. The file path does not +// have to exist. Please note because it doesn't touch the disk, this does not +// work for directories. +// If the mime type is unknown, this will return application/octet-stream. +BASE_EXPORT std::string GetFileMimeType(const FilePath& filepath); + +// Get the mime type for a byte vector. +BASE_EXPORT std::string GetDataMimeType(const std::string& data); + +#if defined(TOOLKIT_GTK) +// This detects the current GTK theme by calling gtk_settings_get_default(). +// It should only be executed on the UI thread and must be called before +// GetMimeIcon(). +BASE_EXPORT void DetectGtkTheme(); +#endif + +// Gets the file name for an icon given the mime type and icon pixel size. +// Where an icon is a square image of |size| x |size|. +// This will try to find the closest matching icon. If that's not available, +// then a generic icon, and finally an empty FilePath if all else fails. +BASE_EXPORT FilePath GetMimeIcon(const std::string& mime_type, size_t size); + +} // namespace nix +} // namespace base + +#endif // BASE_NIX_MIME_UTIL_XDG_H_
diff --git a/src/base/nix/xdg_util.cc b/src/base/nix/xdg_util.cc new file mode 100644 index 0000000..10cbcaa --- /dev/null +++ b/src/base/nix/xdg_util.cc
@@ -0,0 +1,115 @@ +// 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/nix/xdg_util.h" + +#include <string> + +#include "base/environment.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/third_party/xdg_user_dirs/xdg_user_dir_lookup.h" + +namespace { + +// The KDE session version environment variable used in KDE 4. +const char kKDE4SessionEnvVar[] = "KDE_SESSION_VERSION"; + +} // namespace + +namespace base { +namespace nix { + +const char kDotConfigDir[] = ".config"; +const char kXdgConfigHomeEnvVar[] = "XDG_CONFIG_HOME"; + +FilePath GetXDGDirectory(Environment* env, const char* env_name, + const char* fallback_dir) { + FilePath path; + std::string env_value; + if (env->GetVar(env_name, &env_value) && !env_value.empty()) + path = FilePath(env_value); + else + path = file_util::GetHomeDir().Append(fallback_dir); + return path.StripTrailingSeparators(); +} + +FilePath GetXDGUserDirectory(const char* dir_name, const char* fallback_dir) { + FilePath path; + char* xdg_dir = xdg_user_dir_lookup(dir_name); + if (xdg_dir) { + path = FilePath(xdg_dir); + free(xdg_dir); + } else { + path = file_util::GetHomeDir().Append(fallback_dir); + } + return path.StripTrailingSeparators(); +} + +DesktopEnvironment GetDesktopEnvironment(Environment* env) { + // XDG_CURRENT_DESKTOP is the newest standard circa 2012. + std::string xdg_current_desktop; + if (env->GetVar("XDG_CURRENT_DESKTOP", &xdg_current_desktop)) { + // Not all desktop environments set this env var as of this writing. + if (xdg_current_desktop == "Unity") + return DESKTOP_ENVIRONMENT_UNITY; + else if (xdg_current_desktop == "GNOME") + return DESKTOP_ENVIRONMENT_GNOME; + } + + // DESKTOP_SESSION was what everyone used in 2010. + std::string desktop_session; + if (env->GetVar("DESKTOP_SESSION", &desktop_session)) { + if (desktop_session == "gnome") { + return DESKTOP_ENVIRONMENT_GNOME; + } else if (desktop_session == "kde4") { + return DESKTOP_ENVIRONMENT_KDE4; + } else if (desktop_session == "kde") { + // This may mean KDE4 on newer systems, so we have to check. + if (env->HasVar(kKDE4SessionEnvVar)) + return DESKTOP_ENVIRONMENT_KDE4; + return DESKTOP_ENVIRONMENT_KDE3; + } else if (desktop_session.find("xfce") != std::string::npos || + desktop_session == "xubuntu") { + return DESKTOP_ENVIRONMENT_XFCE; + } + } + + // Fall back on some older environment variables. + // Useful particularly in the DESKTOP_SESSION=default case. + if (env->HasVar("GNOME_DESKTOP_SESSION_ID")) { + return DESKTOP_ENVIRONMENT_GNOME; + } else if (env->HasVar("KDE_FULL_SESSION")) { + if (env->HasVar(kKDE4SessionEnvVar)) + return DESKTOP_ENVIRONMENT_KDE4; + return DESKTOP_ENVIRONMENT_KDE3; + } + + return DESKTOP_ENVIRONMENT_OTHER; +} + +const char* GetDesktopEnvironmentName(DesktopEnvironment env) { + switch (env) { + case DESKTOP_ENVIRONMENT_OTHER: + return NULL; + case DESKTOP_ENVIRONMENT_GNOME: + return "GNOME"; + case DESKTOP_ENVIRONMENT_KDE3: + return "KDE3"; + case DESKTOP_ENVIRONMENT_KDE4: + return "KDE4"; + case DESKTOP_ENVIRONMENT_UNITY: + return "UNITY"; + case DESKTOP_ENVIRONMENT_XFCE: + return "XFCE"; + } + return NULL; +} + +const char* GetDesktopEnvironmentName(Environment* env) { + return GetDesktopEnvironmentName(GetDesktopEnvironment(env)); +} + +} // namespace nix +} // namespace base
diff --git a/src/base/nix/xdg_util.h b/src/base/nix/xdg_util.h new file mode 100644 index 0000000..fc72663 --- /dev/null +++ b/src/base/nix/xdg_util.h
@@ -0,0 +1,75 @@ +// 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_NIX_XDG_UTIL_H_ +#define BASE_NIX_XDG_UTIL_H_ + +// XDG refers to http://en.wikipedia.org/wiki/Freedesktop.org . +// This file contains utilities found across free desktop environments. +// +// TODO(brettw) this file should be in app/x11, but is currently used by +// net. We should have a net API to allow the embedder to specify the behavior +// that it uses XDG for, and then move this file. + +#include "base/base_export.h" + +#ifdef nix +#error asdf +#endif + +class FilePath; + +namespace base { + +class Environment; + +namespace nix { + +// The default XDG config directory name. +BASE_EXPORT extern const char kDotConfigDir[]; + +// The XDG config directory environment variable. +BASE_EXPORT extern const char kXdgConfigHomeEnvVar[]; + +// Utility function for getting XDG directories. +// |env_name| is the name of an environment variable that we want to use to get +// a directory path. |fallback_dir| is the directory relative to $HOME that we +// use if |env_name| cannot be found or is empty. |fallback_dir| may be NULL. +// Examples of |env_name| are XDG_CONFIG_HOME and XDG_DATA_HOME. +BASE_EXPORT FilePath GetXDGDirectory(Environment* env, const char* env_name, + const char* fallback_dir); + +// Wrapper around xdg_user_dir_lookup() from src/base/third_party/xdg-user-dirs +// This looks up "well known" user directories like the desktop and music +// folder. Examples of |dir_name| are DESKTOP and MUSIC. +BASE_EXPORT FilePath GetXDGUserDirectory(const char* dir_name, + const char* fallback_dir); + +enum DesktopEnvironment { + DESKTOP_ENVIRONMENT_OTHER, + DESKTOP_ENVIRONMENT_GNOME, + // KDE3 and KDE4 are sufficiently different that we count + // them as two different desktop environments here. + DESKTOP_ENVIRONMENT_KDE3, + DESKTOP_ENVIRONMENT_KDE4, + DESKTOP_ENVIRONMENT_UNITY, + DESKTOP_ENVIRONMENT_XFCE, +}; + +// Return an entry from the DesktopEnvironment enum with a best guess +// of which desktop environment we're using. We use this to know when +// to attempt to use preferences from the desktop environment -- +// proxy settings, password manager, etc. +BASE_EXPORT DesktopEnvironment GetDesktopEnvironment(Environment* env); + +// Return a string representation of the given desktop environment. +// May return NULL in the case of DESKTOP_ENVIRONMENT_OTHER. +BASE_EXPORT const char* GetDesktopEnvironmentName(DesktopEnvironment env); +// Convenience wrapper that calls GetDesktopEnvironment() first. +BASE_EXPORT const char* GetDesktopEnvironmentName(Environment* env); + +} // namespace nix +} // namespace base + +#endif // BASE_NIX_XDG_UTIL_H_
diff --git a/src/base/nix/xdg_util_unittest.cc b/src/base/nix/xdg_util_unittest.cc new file mode 100644 index 0000000..2fc9d4c --- /dev/null +++ b/src/base/nix/xdg_util_unittest.cc
@@ -0,0 +1,76 @@ +// Copyright (c) 2010 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/nix/xdg_util.h" + +#include "base/environment.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::Return; +using ::testing::SetArgumentPointee; +using ::testing::StrEq; + +namespace base { +namespace nix { + +namespace { + +class MockEnvironment : public Environment { + public: + MOCK_METHOD2(GetVar, bool(const char*, std::string* result)); + MOCK_METHOD2(SetVar, bool(const char*, const std::string& new_value)); + MOCK_METHOD1(UnSetVar, bool(const char*)); +}; + +const char* kGnome = "gnome"; +const char* kKDE4 = "kde4"; +const char* kKDE = "kde"; +const char* kXFCE = "xfce"; + +} // namespace + +TEST(XDGUtilTest, GetDesktopEnvironmentGnome) { + MockEnvironment getter; + EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false)); + EXPECT_CALL(getter, GetVar(StrEq("DESKTOP_SESSION"), _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kGnome), Return(true))); + + EXPECT_EQ(DESKTOP_ENVIRONMENT_GNOME, + GetDesktopEnvironment(&getter)); +} + +TEST(XDGUtilTest, GetDesktopEnvironmentKDE4) { + MockEnvironment getter; + EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false)); + EXPECT_CALL(getter, GetVar(StrEq("DESKTOP_SESSION"), _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kKDE4), Return(true))); + + EXPECT_EQ(DESKTOP_ENVIRONMENT_KDE4, + GetDesktopEnvironment(&getter)); +} + +TEST(XDGUtilTest, GetDesktopEnvironmentKDE3) { + MockEnvironment getter; + EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false)); + EXPECT_CALL(getter, GetVar(StrEq("DESKTOP_SESSION"), _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kKDE), Return(true))); + + EXPECT_EQ(DESKTOP_ENVIRONMENT_KDE3, + GetDesktopEnvironment(&getter)); +} + +TEST(XDGUtilTest, GetDesktopEnvironmentXFCE) { + MockEnvironment getter; + EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false)); + EXPECT_CALL(getter, GetVar(StrEq("DESKTOP_SESSION"), _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kXFCE), Return(true))); + + EXPECT_EQ(DESKTOP_ENVIRONMENT_XFCE, + GetDesktopEnvironment(&getter)); +} + +} // namespace nix +} // namespace base
diff --git a/src/base/nullable_string16.h b/src/base/nullable_string16.h new file mode 100644 index 0000000..6f07183 --- /dev/null +++ b/src/base/nullable_string16.h
@@ -0,0 +1,29 @@ +// Copyright (c) 2010 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_NULLABLE_STRING16_H_ +#define BASE_NULLABLE_STRING16_H_ + +#include "base/string16.h" + +// This class is a simple wrapper for string16 which also contains a null +// state. This should be used only where the difference between null and +// empty is meaningful. +class NullableString16 { + public: + NullableString16() : is_null_(false) { } + explicit NullableString16(bool is_null) : is_null_(is_null) { } + NullableString16(const string16& string, bool is_null) + : string_(string), is_null_(is_null) { + } + + const string16& string() const { return string_; } + bool is_null() const { return is_null_; } + + private: + string16 string_; + bool is_null_; +}; + +#endif // BASE_NULLABLE_STRING16_H_
diff --git a/src/base/object_tracker.h b/src/base/object_tracker.h new file mode 100644 index 0000000..f3aca25 --- /dev/null +++ b/src/base/object_tracker.h
@@ -0,0 +1,188 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BASE_OBJECT_TRACKER_H_ +#define BASE_OBJECT_TRACKER_H_ + +#include <assert.h> + +#include <algorithm> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/stringprintf.h" +#include "lb_memory_debug.h" +#include "lb_memory_manager.h" + +#if defined(__LB_SHELL__) +#include "lb_mutex.h" +#else // defined(__LB_SHELL__) +#error "Have to include platform header for mutex" +#endif // defined(__LB_SHELL__) + +namespace base { + +// This class can be used to track and dump all objects belongs to a certain +// type. For example, we can use the following steps to dump all Nodes: +// 1. Make Node inherit from ObjectTracker<Node>. +// 2. Call "Node::Dump(log_cb);" on a particular event (like a key press). +// We can also return false in ShouldDump() to skip certain objects and +// implement DumpSpecificInfo() to dump information specific to the object like +// ref count, etc.. +// This class is meant to be used for debugging only. +template <typename Object, uint32 BacktraceLevel = 10> +class ObjectTracker { + public: +#if defined(__LB_SHELL__) + // We implement our own Lock class to remove the dependency on base::Lock, so + // we can track base::Lock. + class Lock { + public: + Lock() { + int rv = lb_shell_mutex_init(&mutex_); + assert(rv == 0); + UNREFERENCED_PARAMETER(rv); + } + ~Lock() { + int rv = lb_shell_mutex_destroy(&mutex_); + assert(rv == 0); + UNREFERENCED_PARAMETER(rv); + } + void Acquire() { + int rv = lb_shell_mutex_lock(&mutex_); + assert(rv == 0); + UNREFERENCED_PARAMETER(rv); + } + void Release() { + int rv = lb_shell_mutex_unlock(&mutex_); + assert(rv == 0); + UNREFERENCED_PARAMETER(rv); + } + + private: + lb_shell_mutex_t mutex_; + }; +#endif // defined(__LB_SHELL__) + + typedef void LogCB(const std::string&); + + static void Dump(LogCB log_cb) { + lock().Acquire(); + log_cb("============================ start ============================\n"); + // Sort the objects based on their addresses. + std::vector<ObjectTracker*> trackers; + trackers.reserve(16 * 1024); + ObjectTracker* current = head_; + while (current) { + trackers.push_back(current); + current = current->next_; + } + std::sort(trackers.begin(), trackers.end()); + + int skipped = 0; + + for (size_t i = 0; i < trackers.size(); ++i) { + std::string line; + current = trackers[i]; + if ((static_cast<Object*>(current))->ShouldDump()) { + line = base::StringPrintf("%" PRIxPTR " ", + reinterpret_cast<intptr_t>(current)); + line += (static_cast<Object*>(current))->DumpSpecificInfo(); + for (uint32 j = 0; j < BacktraceLevel; ++j) { + base::StringAppendF(&line, " %" PRIxPTR, current->back_trace_[j]); + } + line += "\n"; + log_cb(line); + } else { + ++skipped; + } + } + if (skipped != 0) { + log_cb(base::StringPrintf("Dumped %d items, skipped %d items\n", + static_cast<int>(trackers.size()), skipped)); + } else { + log_cb(base::StringPrintf("Dumped %d items\n", + static_cast<int>(trackers.size()))); + } + log_cb("============================= end =============================\n"); + lock().Release(); + } + + protected: + ObjectTracker() : next_(NULL), prev_(NULL) { + int valid_trace_count = 0; + if (LB::Memory::GetBacktraceEnabled()) { + valid_trace_count = + LB::Memory::Backtrace(0, BacktraceLevel, back_trace_, NULL); + } + if (valid_trace_count != BacktraceLevel) { + memset(back_trace_ + valid_trace_count, 0, + (BacktraceLevel - valid_trace_count) * sizeof(back_trace_[0])); + } + + lock().Acquire(); + next_ = head_; + if (next_) { + next_->prev_ = this; + } + head_ = this; + lock().Release(); + } + + ~ObjectTracker() { + lock().Acquire(); + if (next_) { + next_->prev_ = prev_; + } + if (prev_) { + prev_->next_ = next_; + } else { + head_ = next_; + } + lock().Release(); + } + + bool ShouldDump() { return true; } + std::string DumpSpecificInfo() { return ""; } + + private: + static ObjectTracker* head_; + static Lock& lock() { + static Lock* lock; + // This isn't thread safe. But it should be fine as ObjectTracker is + // supposed to be used only for debugging and this removed its dependency + // on LazyInstance which is in turn depended on AtExitManager. + if (lock == NULL) { + lock = new Lock; + } + return *lock; + } + + ObjectTracker* prev_; + ObjectTracker* next_; + uintptr_t back_trace_[BacktraceLevel]; +}; + +// static +template <typename Object, uint32 BacktraceLevel> +ObjectTracker<Object, BacktraceLevel>* + ObjectTracker<Object, BacktraceLevel>::head_; + +} // namespace base + +#endif // BASE_OBJECT_TRACKER_H_
diff --git a/src/base/object_watcher_shell.cc b/src/base/object_watcher_shell.cc new file mode 100644 index 0000000..811b9b9 --- /dev/null +++ b/src/base/object_watcher_shell.cc
@@ -0,0 +1,364 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "base/object_watcher_shell.h" + +#include <stack> + +#include "base/bind.h" +#include "base/logging.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" + +namespace base { + +struct Watch { + ObjectWatcher* watcher; // associated ObjectWatcher instance + int object; // the file descriptor being watched + int watch_handle; // used for uniquely identifying watches + MessageLoop* origin_loop; // the origin thread's message loop + ObjectWatcher::Delegate* delegate; // delegate to notify when signaled + bool did_signal; // set when DoneWaiting is called + MessagePumpShell::Mode mode; // callback on read, write or both? + + void Run() { + // The watcher may have already been torn down, in which case we need to + // just get out of dodge. + if (!watcher) + return; + + DCHECK(did_signal); + watcher->StopWatching(); + + delegate->OnObjectSignaled(object); + } +}; + +// To emulate the behavior of the former Task system: +static void WatchTask(Watch* watch) { + watch->Run(); + delete watch; +} + +ObjectWatchMultiplexer* ObjectWatchMultiplexer::s_instance = NULL; + +ObjectWatchMultiplexer::ObjectWatchMultiplexer() + : base::SimpleThread( + "ObjectWatchMultiplexer Thread", + base::SimpleThread::Options( + // TODO: Pass these into ObjectWatchMultiplexer. + 0 /* kObjectWatcherThreadStackSize */, + kThreadPriority_Default /* kObjectWatcherThreadPriority */, + kNoThreadAffinity /* kNetworkIOThreadAffinity */)), + non_empty_list_event_(true, false), + max_watch_handle_(0), + should_recompose_pollfd_array_(true), + exit_(false) { + DCHECK(!s_instance); + s_instance = this; + Start(); +} + +ObjectWatchMultiplexer::~ObjectWatchMultiplexer() { + DCHECK(s_instance); + Join(); + s_instance = NULL; +} + +// blocking call to exit the internal thread +void ObjectWatchMultiplexer::Join() { + { + // RecomposePollFDArray may reset the event, but can only be called + // while this lock is held. To break the race between these two, grab + // the lock before setting exit_ and signalling the event, and make sure + // that exit_ is checked before resetting the event. + base::AutoLock lock(add_watch_list_lock_); + exit_ = true; + // wake up the thread in case it's waiting on the empty list event + non_empty_list_event_.Signal(); + } + // in any event it should timeout eventually and see the exit flag.. + base::SimpleThread::Join(); +} + +void ObjectWatchMultiplexer::AddWatch(Watch* watch) { + DCHECK(watch); + DCHECK_EQ(watch->watch_handle, 0); + // grab the mutex to modify the watch map et al + base::AutoLock lock(add_watch_list_lock_); + should_recompose_pollfd_array_ = true; + watch->watch_handle = ++max_watch_handle_; + new_watch_map_.insert(WatchMap::value_type(watch->object, watch)); + non_empty_list_event_.Signal(); +} + +void ObjectWatchMultiplexer::RemoveWatch(Watch* watch) { + DCHECK(watch); + base::AutoLock lock(remove_watch_list_lock_); + WatchMap::iterator start = watch_map_.lower_bound(watch->object); + WatchMap::iterator end = watch_map_.upper_bound(watch->object); + WatchMap::iterator it; + // search within those elements having the same file descriptor + for (it = start; it != end; it++) + if (it->second->watch_handle == watch->watch_handle) + break; + if (it != end) { + watch_map_.erase(it); + should_recompose_pollfd_array_ = true; + } else { + base::AutoLock lock(add_watch_list_lock_); + // not found, search new_watch_map_ + WatchMap::iterator start = new_watch_map_.lower_bound(watch->object); + WatchMap::iterator end = new_watch_map_.upper_bound(watch->object); + for (it = start; it != end; it++) + if (it->second->watch_handle == watch->watch_handle) + break; + if (it != end) { + new_watch_map_.erase(it); + should_recompose_pollfd_array_ = true; + } + } +} + +void ObjectWatchMultiplexer::Run() { + // we keep a stack of pointers to watches needing a callback and + // subsequent removal from the map + std::stack<WatchMap::iterator> watch_removal_stack; + // separate stack of callbacks to process after releasing the + // WatchMap mutex + std::stack<Watch*> watch_callback_stack; + + while (!exit_) { + // We need both locks here. Order is important + remove_watch_list_lock_.Acquire(); + add_watch_list_lock_.Acquire(); + if (should_recompose_pollfd_array_) + RecomposePollFDArray(); + add_watch_list_lock_.Release(); + remove_watch_list_lock_.Release(); + + if (!pollfd_array_.empty()) { + // timeout on a 100ms to wake up and see if we need + // to modify the array of fds or exit.. + int event_count = + poll(&pollfd_array_.front(), pollfd_array_.size(), kPollTimeout); + // negative value returned means error... + if (event_count < 0) { + // Sleep for kPollTimeout, otherwise this thread could busy-wait until + // poll returns a non-error value + base::PlatformThread::Sleep( + base::TimeDelta::FromMilliseconds(kPollTimeout)); + } + + // we need the mutex to access the map. AddWatch() is on a different + // map object so we dont need the add_watch_list_lock_ here. + remove_watch_list_lock_.Acquire(); + DCHECK_LE(event_count, static_cast<int>(pollfd_array_.size())); + for (int j = 0; j < pollfd_array_.size() && event_count > 0; ++j) { + if (pollfd_array_[j].revents != 0) { + event_count--; + WatchMap::iterator start = + watch_map_.lower_bound(pollfd_array_[j].fd); + WatchMap::iterator stop = + watch_map_.upper_bound(pollfd_array_[j].fd); + for (WatchMap::iterator it = start; it != stop; it++) { + // oh STL, your capacity for obfuscation never ceases to amaze. + // this if/else block tries to match interest for each fd event + // to the associated watchers. If a watcher was watching for + // reads and a read happened, then we add it to the stack of + // callbacks to process. Else if it was watching for writes and + // a write happened then we do the same. + // note that signaled watches are going to become the property + // of their respectively owned MessageLoops and will be deleted + // once executed, and so will be removed from the hash_map + if ((pollfd_array_[j].revents & POLLIN) && + (it->second->mode & MessagePumpShell::WATCH_READ)) { + watch_removal_stack.push(it); + watch_callback_stack.push(it->second); + } else if ((pollfd_array_[j].revents & POLLOUT) && + (it->second->mode & MessagePumpShell::WATCH_WRITE)) { + watch_removal_stack.push(it); + watch_callback_stack.push(it->second); + } + } + } + } // for(int j = 0; j < pollfd_array_.size() && event_count > 0; ++j) + + // process all callbacks. this must unfortunately be done inside + // of the mutex to prevent the contention on watch objects being + // deleted due to a call to ObjectWatcher::StopWatching that could + // occur while the watch is on this callback stack + while (!watch_callback_stack.empty()) { + Watch* watch = watch_callback_stack.top(); + watch_callback_stack.pop(); + // signal this watch to run in the original thread context + // of the calling thread message loop. The watch will be + // posted as a task to the MessageLoop which will take + // ownership of the object and delete it + watch->did_signal = true; + watch->origin_loop->PostTask(FROM_HERE, base::Bind(WatchTask, watch)); + } + // if we're going to signal something we should recompose + // the array as we are going to be removing watches + if (!watch_removal_stack.empty()) + should_recompose_pollfd_array_ = true; + // now remove the callback elements from the current hash_map + while (!watch_removal_stack.empty()) { + watch_map_.erase(watch_removal_stack.top()); + watch_removal_stack.pop(); + } + + remove_watch_list_lock_.Release(); + } else { // if (!pollfd_array_.empty()) + // sleep until flagged that something has been added to the list + DCHECK(watch_map_.empty()); + // NOTE: You can't DCHECK(new_watch_map_.empty()) because we let go + // of the lock already and new stuff could have been added before we + // get here. If that happens, the event will have already been + // signaled and we won't have to wait. + non_empty_list_event_.Wait(); + } // if (!pollfd_array_.empty()) + } // while (!exit_) +} + +void ObjectWatchMultiplexer::RecomposePollFDArray() { + remove_watch_list_lock_.AssertAcquired(); + add_watch_list_lock_.AssertAcquired(); + // process new requests and move them into working map + WatchMap::iterator it; + for (it = new_watch_map_.begin(); it != new_watch_map_.end(); it++) { + watch_map_.insert(*it); + } + new_watch_map_.clear(); + // count the number of unique file descriptors in the map + int count = 0; + int last_fd = -1; + for (it = watch_map_.begin(); it != watch_map_.end(); it++) { + if (it->first != last_fd) { + last_fd = it->first; + count++; + } + } + pollfd_array_.resize(count); + + // init pollfd_array_ + if (!pollfd_array_.empty()) { + int j = -1; + last_fd = -1; + for (it = watch_map_.begin(); it != watch_map_.end(); it++) { + if (it->first != last_fd) { + // we keep j pointing at this current element for ORing in the watch + // flags + j++; + last_fd = it->first; + pollfd_array_[j].fd = last_fd; + // assume we care about both, we will filter based on mode on callback + pollfd_array_[j].events = 0; + if (it->second->mode & MessagePumpShell::WATCH_READ) + pollfd_array_[j].events |= POLLIN; + if (it->second->mode & MessagePumpShell::WATCH_WRITE) + pollfd_array_[j].events |= POLLOUT; + pollfd_array_[j].revents = 0; + } else { // if (it->first != last_fd) + // OR in the watch flags to the already initialized pollfd struct + if (it->second->mode & MessagePumpShell::WATCH_READ) + pollfd_array_[j].events |= POLLIN; + if (it->second->mode & MessagePumpShell::WATCH_WRITE) + pollfd_array_[j].events |= POLLOUT; + } // if (it->first != last_fd) + } // for (it = watch_map_.begin(); it != watch_map_.end(); it++) + } else { // if (!pollfd_array_.empty()) + // reset the empty event if we've completely emptied the watch list + DCHECK(watch_map_.empty()); + DCHECK(new_watch_map_.empty()); + // Join may have signalled the event because we are exitting. If so, + // don't reset the event. That would lead to an infinite wait on + // shutdown. + if (!exit_) + non_empty_list_event_.Reset(); + } // if (!pollfd_array_.empty()) + // reset flag + should_recompose_pollfd_array_ = false; +} + +ObjectWatcher::ObjectWatcher() : watch_(NULL) { +} + +ObjectWatcher::~ObjectWatcher() { + StopWatching(); +} + +bool ObjectWatcher::StartWatching(int object, + MessagePumpShell::Mode mode, + Delegate* delegate) { + DCHECK(ObjectWatchMultiplexer::GetInstance() != NULL); + if (watch_) { + NOTREACHED() << "Already watching an object"; + return false; + } + + watch_ = new Watch; + watch_->watcher = this; + watch_->object = object; + watch_->mode = mode; + watch_->origin_loop = MessageLoop::current(); + watch_->delegate = delegate; + watch_->did_signal = false; + watch_->watch_handle = 0; + + ObjectWatchMultiplexer::GetInstance()->AddWatch(watch_); + + // We need to know if the current message loop is going away so we can + // prevent the wait thread from trying to access a dead message loop. + MessageLoop::current()->AddDestructionObserver(this); + return true; +} + +bool ObjectWatcher::StopWatching() { + if (!watch_) + return false; + + DCHECK(ObjectWatchMultiplexer::GetInstance() != NULL); + + // make sure stop call happens on same thread as start call + DCHECK(watch_->origin_loop == MessageLoop::current()); + + // this will block until this watch has been removed from the mux + ObjectWatchMultiplexer::GetInstance()->RemoveWatch(watch_); + // let the watch know that the watcher has died, in case the watch is + // still sitting in a message loop. See Watch::Run() to see that it + // will bug out in that event. + watch_->watcher = NULL; + + // if the watch was signaled, then it is now property of the MessageLoop + // and will be deleted there. It has also been safely removed from the + // internal list of watches in the ObjectWatchMux. Therefore if it hasn't + // been signaled we delete it here to avoid leaking the watch + if (!watch_->did_signal) + delete watch_; + + watch_ = NULL; + + MessageLoop::current()->RemoveDestructionObserver(this); + return true; +} + +void ObjectWatcher::WillDestroyCurrentMessageLoop() { + // Need to shutdown the watch so that we don't try to access the MessageLoop + // after this point. + StopWatching(); +} + +} // namespace base
diff --git a/src/base/object_watcher_shell.h b/src/base/object_watcher_shell.h new file mode 100644 index 0000000..10519bc --- /dev/null +++ b/src/base/object_watcher_shell.h
@@ -0,0 +1,160 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef BASE_OBJECT_WATCHER_SHELL_H_ +#define BASE_OBJECT_WATCHER_SHELL_H_ + +#if defined(__LB_SHELL__) + +#include <sys/poll.h> + +#include <map> +#include <vector> + +#include "base/base_export.h" +#include "base/message_loop.h" +#include "base/message_pump_shell.h" +#include "base/threading/simple_thread.h" + +// overall interface inspired by base/win/object_watcher.h +// however internal implementation is driven by platform necessity +namespace base { + +// A class that provides a means to asynchronously wait for a file descriptor to +// become signaled. It uses an internal thread on a singleton object that +// blocks on poll() calls to an internal list of fds, each represented by an +// instance of the ObjectWatcher class. +// It provides a notification callback, OnObjectSignaled, that runs back on +// the origin thread (i.e., the thread that called StartWatching). +// +// +// Typical usage: +// +// class MyClass : public base::ObjectWatcher::Delegate { +// public: +// void DoStuffWhenSignaled(int object) { +// watcher_.StartWatching(object, this); +// } +// virtual void OnObjectSignaled(int object) { +// // OK, time to do stuff! +// } +// private: +// base::ObjectWatcher watcher_; +// }; +// +// In the above example, MyClass wants to "do stuff" when object becomes +// signaled. ObjectWatcher makes this task easy. When MyClass goes out of +// scope, the watcher_ will be destroyed, and there is no need to worry about +// OnObjectSignaled being called on a deleted MyClass pointer. Easy! +// + +struct Watch; + +// ----------------------------------------------------------------------------- +// ObjectWatchMultiplexer +// this object runs an internal thread to block on the aggregate of all watched +// objects and poll them for activity. the best emulation of callback-driven +// io I can muster on the PS3. +class ObjectWatchMultiplexer : public SimpleThread { + public: + ObjectWatchMultiplexer(); + ~ObjectWatchMultiplexer(); + + // blocking call to exit the internal thread + void Join(); + // although you can add different watches with the same file descriptor all + // day long, please don't add the same watch twice it will create an error. + void AddWatch(Watch* watch); + void RemoveWatch(Watch* watch); + static ObjectWatchMultiplexer* GetInstance() {return s_instance;} + + private: + // the internal thread runloop + void Run(); + void RecomposePollFDArray(); + + // used to let the thread block until it actually has _something_ to poll + base::WaitableEvent non_empty_list_event_; + +#if defined(__LB_WIIU__) + // 33ms is a good balance between quickly servicing new + // sockets to poll, and not overloading the IO pipeline. + static const int kPollTimeout = 33; +#else + static const int kPollTimeout = 100; +#endif + + // this lock protects watch_map_, should_recompose_pollfd_array_, and + // max_watch_handle_ which is used to generate unique watch handles to + // disambiguate watches on the same file descriptor + base::Lock add_watch_list_lock_; + base::Lock remove_watch_list_lock_; + typedef std::multimap<int, Watch*> WatchMap; + WatchMap watch_map_; // current working watches + WatchMap new_watch_map_; // new watches + int max_watch_handle_; + std::vector<pollfd> pollfd_array_; + bool should_recompose_pollfd_array_; + bool exit_; + static ObjectWatchMultiplexer* s_instance; + DISALLOW_COPY_AND_ASSIGN(ObjectWatchMultiplexer); +}; + +class BASE_EXPORT ObjectWatcher : public MessageLoop::DestructionObserver { + public: + class BASE_EXPORT Delegate { + public: + virtual ~Delegate() {} + virtual void OnObjectSignaled(int object) = 0; + }; + + ObjectWatcher(); + ~ObjectWatcher(); + + // When the object is signaled with mode, the given delegate is notified on + // the thread where StartWatching is called. The ObjectWatcher is not + // responsible for deleting the delegate. + // + // Returns true if the watch was started. Otherwise, false is returned. + // + bool StartWatching(int object, + MessagePumpShell::Mode mode, + Delegate* delegate); + + // Stops watching. Does nothing if the watch has already completed. If the + // watch is still active, then it is canceled, and the associated delegate is + // not notified. + // + // Returns true if the watch was canceled. Otherwise, false is returned. + // + bool StopWatching(); + + // Returns the fd of the object being watched, or -1 if the object + // watcher is stopped. + int GetWatchedObject(); + + private: + // MessageLoop::DestructionObserver implementation: + virtual void WillDestroyCurrentMessageLoop(); + + struct Watch* watch_; + + DISALLOW_COPY_AND_ASSIGN(ObjectWatcher); +}; + +} // namespace base + +#endif // defined(__LB_SHELL__) +#endif // BASE_OBJECT_WATCHER_SHELL_H_
diff --git a/src/base/observer_list.h b/src/base/observer_list.h new file mode 100644 index 0000000..ca33bff --- /dev/null +++ b/src/base/observer_list.h
@@ -0,0 +1,219 @@ +// 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. + +#ifndef BASE_OBSERVER_LIST_H__ +#define BASE_OBSERVER_LIST_H__ + +#include <algorithm> +#include <limits> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/weak_ptr.h" + +/////////////////////////////////////////////////////////////////////////////// +// +// OVERVIEW: +// +// A container for a list of observers. Unlike a normal STL vector or list, +// this container can be modified during iteration without invalidating the +// iterator. So, it safely handles the case of an observer removing itself +// or other observers from the list while observers are being notified. +// +// TYPICAL USAGE: +// +// class MyWidget { +// public: +// ... +// +// class Observer { +// public: +// virtual void OnFoo(MyWidget* w) = 0; +// virtual void OnBar(MyWidget* w, int x, int y) = 0; +// }; +// +// void AddObserver(Observer* obs) { +// observer_list_.AddObserver(obs); +// } +// +// void RemoveObserver(Observer* obs) { +// observer_list_.RemoveObserver(obs); +// } +// +// void NotifyFoo() { +// FOR_EACH_OBSERVER(Observer, observer_list_, OnFoo(this)); +// } +// +// void NotifyBar(int x, int y) { +// FOR_EACH_OBSERVER(Observer, observer_list_, OnBar(this, x, y)); +// } +// +// private: +// ObserverList<Observer> observer_list_; +// }; +// +// +/////////////////////////////////////////////////////////////////////////////// + +template <typename ObserverType> +class ObserverListThreadSafe; + +template <class ObserverType> +class ObserverListBase + : public base::SupportsWeakPtr<ObserverListBase<ObserverType> > { + public: + // Enumeration of which observers are notified. + enum NotificationType { + // Specifies that any observers added during notification are notified. + // This is the default type if non type is provided to the constructor. + NOTIFY_ALL, + + // Specifies that observers added while sending out notification are not + // notified. + NOTIFY_EXISTING_ONLY + }; + + // An iterator class that can be used to access the list of observers. See + // also the FOR_EACH_OBSERVER macro defined below. + class Iterator { + public: + Iterator(ObserverListBase<ObserverType>& list) + : list_(list.AsWeakPtr()), + index_(0), + max_index_(list.type_ == NOTIFY_ALL ? + std::numeric_limits<size_t>::max() : + list.observers_.size()) { + ++list_->notify_depth_; + } + + ~Iterator() { + if (list_ && --list_->notify_depth_ == 0) + list_->Compact(); + } + + ObserverType* GetNext() { + if (!list_) + return NULL; + ListType& observers = list_->observers_; + // Advance if the current element is null + size_t max_index = std::min(max_index_, observers.size()); + while (index_ < max_index && !observers[index_]) + ++index_; + return index_ < max_index ? observers[index_++] : NULL; + } + + private: + base::WeakPtr<ObserverListBase<ObserverType> > list_; + size_t index_; + size_t max_index_; + }; + + ObserverListBase() : notify_depth_(0), type_(NOTIFY_ALL) {} + explicit ObserverListBase(NotificationType type) + : notify_depth_(0), type_(type) {} + + // Add an observer to the list. An observer should not be added to + // the same list more than once. + void AddObserver(ObserverType* obs) { + if (std::find(observers_.begin(), observers_.end(), obs) + != observers_.end()) { + NOTREACHED() << "Observers can only be added once!"; + return; + } + observers_.push_back(obs); + } + + // Remove an observer from the list if it is in the list. + void RemoveObserver(ObserverType* obs) { + typename ListType::iterator it = + std::find(observers_.begin(), observers_.end(), obs); + if (it != observers_.end()) { + if (notify_depth_) { + *it = 0; + } else { + observers_.erase(it); + } + } + } + + bool HasObserver(ObserverType* observer) const { + for (size_t i = 0; i < observers_.size(); ++i) { + if (observers_[i] == observer) + return true; + } + return false; + } + + void Clear() { + if (notify_depth_) { + for (typename ListType::iterator it = observers_.begin(); + it != observers_.end(); ++it) { + *it = 0; + } + } else { + observers_.clear(); + } + } + + size_t size() const { return observers_.size(); } + + protected: + void Compact() { + observers_.erase( + std::remove(observers_.begin(), observers_.end(), + static_cast<ObserverType*>(NULL)), observers_.end()); + } + + private: + friend class ObserverListThreadSafe<ObserverType>; + + typedef std::vector<ObserverType*> ListType; + + ListType observers_; + int notify_depth_; + NotificationType type_; + +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) + // friend is not needed for nested classes in modern compilers. + friend class ObserverListBase::Iterator; +#endif + + DISALLOW_COPY_AND_ASSIGN(ObserverListBase); +}; + +template <class ObserverType, bool check_empty = false> +class ObserverList : public ObserverListBase<ObserverType> { + public: + typedef typename ObserverListBase<ObserverType>::NotificationType + NotificationType; + + ObserverList() {} + explicit ObserverList(NotificationType type) + : ObserverListBase<ObserverType>(type) {} + + ~ObserverList() { + // When check_empty is true, assert that the list is empty on destruction. + if (check_empty) { + ObserverListBase<ObserverType>::Compact(); + DCHECK_EQ(ObserverListBase<ObserverType>::size(), 0U); + } + } + + bool might_have_observers() const { + return ObserverListBase<ObserverType>::size() != 0; + } +}; + +#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; \ + } \ + } while (0) + +#endif // BASE_OBSERVER_LIST_H__
diff --git a/src/base/observer_list_threadsafe.h b/src/base/observer_list_threadsafe.h new file mode 100644 index 0000000..7322a1b --- /dev/null +++ b/src/base/observer_list_threadsafe.h
@@ -0,0 +1,297 @@ +// 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_OBSERVER_LIST_THREADSAFE_H_ +#define BASE_OBSERVER_LIST_THREADSAFE_H_ + +#include <algorithm> +#include <map> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/observer_list.h" +#include "base/threading/platform_thread.h" + +/////////////////////////////////////////////////////////////////////////////// +// +// OVERVIEW: +// +// A thread-safe container for a list of observers. +// This is similar to the observer_list (see observer_list.h), but it +// is more robust for multi-threaded situations. +// +// The following use cases are supported: +// * Observers can register for notifications from any thread. +// Callbacks to the observer will occur on the same thread where +// the observer initially called AddObserver() from. +// * Any thread may trigger a notification via Notify(). +// * Observers can remove themselves from the observer list inside +// of a callback. +// * If one thread is notifying observers concurrently with an observer +// removing itself from the observer list, the notifications will +// be silently dropped. +// +// The drawback of the threadsafe observer list is that notifications +// are not as real-time as the non-threadsafe version of this class. +// Notifications will always be done via PostTask() to another thread, +// whereas with the non-thread-safe observer_list, notifications happen +// synchronously and immediately. +// +// IMPLEMENTATION NOTES +// The ObserverListThreadSafe maintains an ObserverList for each thread +// which uses the ThreadSafeObserver. When Notifying the observers, +// we simply call PostTask to each registered thread, and then each thread +// will notify its regular ObserverList. +// +/////////////////////////////////////////////////////////////////////////////// + +// Forward declaration for ObserverListThreadSafeTraits. +template <class ObserverType> +class ObserverListThreadSafe; + +// An UnboundMethod is a wrapper for a method where the actual object is +// provided at Run dispatch time. +template <class T, class Method, class Params> +class UnboundMethod { + public: + UnboundMethod(Method m, const Params& p) : m_(m), p_(p) { + COMPILE_ASSERT( + (base::internal::ParamsUseScopedRefptrCorrectly<Params>::value), + badunboundmethodparams); + } + void Run(T* obj) const { + DispatchToMethod(obj, m_, p_); + } + private: + Method m_; + Params p_; +}; + +// This class is used to work around VS2005 not accepting: +// +// friend class +// base::RefCountedThreadSafe<ObserverListThreadSafe<ObserverType> >; +// +// Instead of friending the class, we could friend the actual function +// which calls delete. However, this ends up being +// RefCountedThreadSafe::DeleteInternal(), which is private. So we +// define our own templated traits class so we can friend it. +template <class T> +struct ObserverListThreadSafeTraits { + static void Destruct(const ObserverListThreadSafe<T>* x) { + delete x; + } +}; + +template <class ObserverType> +class ObserverListThreadSafe + : public base::RefCountedThreadSafe< + ObserverListThreadSafe<ObserverType>, + ObserverListThreadSafeTraits<ObserverType> > { + public: + typedef typename ObserverList<ObserverType>::NotificationType + NotificationType; + + ObserverListThreadSafe() + : type_(ObserverListBase<ObserverType>::NOTIFY_ALL) {} + explicit ObserverListThreadSafe(NotificationType type) : type_(type) {} + + // Add an observer to the list. An observer should not be added to + // the same list more than once. + void AddObserver(ObserverType* obs) { + // If there is not a current MessageLoop, it is impossible to notify on it, + // so do not add the observer. + if (!MessageLoop::current()) + return; + + ObserverList<ObserverType>* list = NULL; + base::PlatformThreadId thread_id = base::PlatformThread::CurrentId(); + { + base::AutoLock lock(list_lock_); + if (observer_lists_.find(thread_id) == observer_lists_.end()) + observer_lists_[thread_id] = new ObserverListContext(type_); + list = &(observer_lists_[thread_id]->list); + } + list->AddObserver(obs); + } + + // Remove an observer from the list if it is in the list. + // If there are pending notifications in-transit to the observer, they will + // be aborted. + // If the observer to be removed is in the list, RemoveObserver MUST + // be called from the same thread which called AddObserver. + void RemoveObserver(ObserverType* obs) { + ObserverListContext* context = NULL; + ObserverList<ObserverType>* list = NULL; + base::PlatformThreadId thread_id = base::PlatformThread::CurrentId(); + { + base::AutoLock lock(list_lock_); + typename ObserversListMap::iterator it = observer_lists_.find(thread_id); + if (it == observer_lists_.end()) { + // This will happen if we try to remove an observer on a thread + // we never added an observer for. + return; + } + context = it->second; + list = &context->list; + + // If we're about to remove the last observer from the list, + // then we can remove this observer_list entirely. + if (list->HasObserver(obs) && list->size() == 1) + observer_lists_.erase(it); + } + list->RemoveObserver(obs); + + // If RemoveObserver is called from a notification, the size will be + // nonzero. Instead of deleting here, the NotifyWrapper will delete + // when it finishes iterating. + if (list->size() == 0) + delete context; + } + + // Verifies that the list is currently empty (i.e. there are no observers). + void AssertEmpty() const { + base::AutoLock lock(list_lock_); + DCHECK(observer_lists_.empty()); + } + + // Notify methods. + // Make a thread-safe callback to each Observer in the list. + // Note, these calls are effectively asynchronous. You cannot assume + // that at the completion of the Notify call that all Observers have + // been Notified. The notification may still be pending delivery. + template <class Method> + void Notify(Method m) { + UnboundMethod<ObserverType, Method, Tuple0> method(m, MakeTuple()); + Notify<Method, Tuple0>(method); + } + + template <class Method, class A> + void Notify(Method m, const A& a) { + UnboundMethod<ObserverType, Method, Tuple1<A> > method(m, MakeTuple(a)); + Notify<Method, Tuple1<A> >(method); + } + + template <class Method, class A, class B> + void Notify(Method m, const A& a, const B& b) { + UnboundMethod<ObserverType, Method, Tuple2<A, B> > method( + m, MakeTuple(a, b)); + Notify<Method, Tuple2<A, B> >(method); + } + + template <class Method, class A, class B, class C> + void Notify(Method m, const A& a, const B& b, const C& c) { + UnboundMethod<ObserverType, Method, Tuple3<A, B, C> > method( + m, MakeTuple(a, b, c)); + Notify<Method, Tuple3<A, B, C> >(method); + } + + template <class Method, class A, class B, class C, class D> + void Notify(Method m, const A& a, const B& b, const C& c, const D& d) { + UnboundMethod<ObserverType, Method, Tuple4<A, B, C, D> > method( + m, MakeTuple(a, b, c, d)); + Notify<Method, Tuple4<A, B, C, D> >(method); + } + + // TODO(mbelshe): Add more wrappers for Notify() with more arguments. + + private: + // See comment above ObserverListThreadSafeTraits' definition. + friend struct ObserverListThreadSafeTraits<ObserverType>; + + struct ObserverListContext { + explicit ObserverListContext(NotificationType type) + : loop(base::MessageLoopProxy::current()), + list(type) { + } + + scoped_refptr<base::MessageLoopProxy> loop; + ObserverList<ObserverType> list; + + DISALLOW_COPY_AND_ASSIGN(ObserverListContext); + }; + + ~ObserverListThreadSafe() { + typename ObserversListMap::const_iterator it; + for (it = observer_lists_.begin(); it != observer_lists_.end(); ++it) + delete (*it).second; + observer_lists_.clear(); + } + + template <class Method, class Params> + void Notify(const UnboundMethod<ObserverType, Method, Params>& method) { + base::AutoLock lock(list_lock_); + typename ObserversListMap::iterator it; + for (it = observer_lists_.begin(); it != observer_lists_.end(); ++it) { + ObserverListContext* context = (*it).second; + context->loop->PostTask( + FROM_HERE, + base::Bind(&ObserverListThreadSafe<ObserverType>:: + template NotifyWrapper<Method, Params>, this, context, method)); + } + } + + // Wrapper which is called to fire the notifications for each thread's + // ObserverList. This function MUST be called on the thread which owns + // the unsafe ObserverList. + template <class Method, class Params> + void NotifyWrapper(ObserverListContext* context, + const UnboundMethod<ObserverType, Method, Params>& method) { + + // Check that this list still needs notifications. + { + base::AutoLock lock(list_lock_); + typename ObserversListMap::iterator it = + observer_lists_.find(base::PlatformThread::CurrentId()); + + // The ObserverList could have been removed already. In fact, it could + // have been removed and then re-added! If the master list's loop + // does not match this one, then we do not need to finish this + // notification. + if (it == observer_lists_.end() || it->second != context) + return; + } + + { + typename ObserverList<ObserverType>::Iterator it(context->list); + ObserverType* obs; + while ((obs = it.GetNext()) != NULL) + method.Run(obs); + } + + // If there are no more observers on the list, we can now delete it. + if (context->list.size() == 0) { + { + base::AutoLock lock(list_lock_); + // Remove |list| if it's not already removed. + // This can happen if multiple observers got removed in a notification. + // See http://crbug.com/55725. + typename ObserversListMap::iterator it = + observer_lists_.find(base::PlatformThread::CurrentId()); + if (it != observer_lists_.end() && it->second == context) + observer_lists_.erase(it); + } + delete context; + } + } + + // Key by PlatformThreadId because in tests, clients can attempt to remove + // observers without a MessageLoop. If this were keyed by MessageLoop, that + // operation would be silently ignored, leaving garbage in the ObserverList. + typedef std::map<base::PlatformThreadId, ObserverListContext*> + ObserversListMap; + + mutable base::Lock list_lock_; // Protects the observer_lists_. + ObserversListMap observer_lists_; + const NotificationType type_; + + DISALLOW_COPY_AND_ASSIGN(ObserverListThreadSafe); +}; + +#endif // BASE_OBSERVER_LIST_THREADSAFE_H_
diff --git a/src/base/observer_list_unittest.cc b/src/base/observer_list_unittest.cc new file mode 100644 index 0000000..baa8f3a --- /dev/null +++ b/src/base/observer_list_unittest.cc
@@ -0,0 +1,561 @@ +// 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/observer_list.h" +#include "base/observer_list_threadsafe.h" + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop.h" +#include "base/threading/platform_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::PlatformThread; +using base::Time; + +namespace { + +class Foo { + public: + virtual void Observe(int x) = 0; + virtual ~Foo() {} +}; + +class Adder : public Foo { + public: + explicit Adder(int scaler) : total(0), scaler_(scaler) {} + virtual void Observe(int x) OVERRIDE { + total += x * scaler_; + } + virtual ~Adder() {} + int total; + + private: + int scaler_; +}; + +class Disrupter : public Foo { + public: + Disrupter(ObserverList<Foo>* list, Foo* doomed) + : list_(list), + doomed_(doomed) { + } + virtual ~Disrupter() {} + virtual void Observe(int x) OVERRIDE { + list_->RemoveObserver(doomed_); + } + + private: + ObserverList<Foo>* list_; + Foo* doomed_; +}; + +class ThreadSafeDisrupter : public Foo { + public: + ThreadSafeDisrupter(ObserverListThreadSafe<Foo>* list, Foo* doomed) + : list_(list), + doomed_(doomed) { + } + virtual ~ThreadSafeDisrupter() {} + virtual void Observe(int x) OVERRIDE { + list_->RemoveObserver(doomed_); + } + + private: + ObserverListThreadSafe<Foo>* list_; + Foo* doomed_; +}; + +template <typename ObserverListType> +class AddInObserve : public Foo { + public: + explicit AddInObserve(ObserverListType* observer_list) + : added(false), + observer_list(observer_list), + adder(1) { + } + + virtual void Observe(int x) OVERRIDE { + if (!added) { + added = true; + observer_list->AddObserver(&adder); + } + } + + bool added; + ObserverListType* observer_list; + Adder adder; +}; + + +static const int kThreadRunTime = 2000; // ms to run the multi-threaded test. + +// A thread for use in the ThreadSafeObserver test +// which will add and remove itself from the notification +// list repeatedly. +class AddRemoveThread : public PlatformThread::Delegate, + public Foo { + public: + AddRemoveThread(ObserverListThreadSafe<Foo>* list, bool notify) + : list_(list), + loop_(NULL), + in_list_(false), + start_(Time::Now()), + count_observes_(0), + count_addtask_(0), + do_notifies_(notify), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { + } + + virtual ~AddRemoveThread() { + } + + virtual void ThreadMain() OVERRIDE { + loop_ = new MessageLoop(); // Fire up a message loop. + loop_->PostTask( + FROM_HERE, + base::Bind(&AddRemoveThread::AddTask, weak_factory_.GetWeakPtr())); + loop_->Run(); + //LOG(ERROR) << "Loop 0x" << std::hex << loop_ << " done. " << + // count_observes_ << ", " << count_addtask_; + delete loop_; + loop_ = reinterpret_cast<MessageLoop*>(0xdeadbeef); + delete this; + } + + bool Started() { + return loop_ != NULL; + } + + // This task just keeps posting to itself in an attempt + // to race with the notifier. + void AddTask() { + count_addtask_++; + + if ((Time::Now() - start_).InMilliseconds() > kThreadRunTime) { + VLOG(1) << "DONE!"; + return; + } + + if (!in_list_) { + list_->AddObserver(this); + in_list_ = true; + } + + if (do_notifies_) { + list_->Notify(&Foo::Observe, 10); + } + + loop_->PostTask( + FROM_HERE, + base::Bind(&AddRemoveThread::AddTask, weak_factory_.GetWeakPtr())); + } + + void Quit() { + loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure()); + } + + virtual void Observe(int x) OVERRIDE { + count_observes_++; + + // If we're getting called after we removed ourselves from + // the list, that is very bad! + DCHECK(in_list_); + + // This callback should fire on the appropriate thread + EXPECT_EQ(loop_, MessageLoop::current()); + + list_->RemoveObserver(this); + in_list_ = false; + } + + private: + ObserverListThreadSafe<Foo>* list_; + MessageLoop* loop_; + bool in_list_; // Are we currently registered for notifications. + // in_list_ is only used on |this| thread. + Time start_; // The time we started the test. + + int count_observes_; // Number of times we observed. + int count_addtask_; // Number of times thread AddTask was called + bool do_notifies_; // Whether these threads should do notifications. + + base::WeakPtrFactory<AddRemoveThread> weak_factory_; +}; + +TEST(ObserverListTest, BasicTest) { + ObserverList<Foo> observer_list; + Adder a(1), b(-1), c(1), d(-1), e(-1); + Disrupter evil(&observer_list, &c); + + observer_list.AddObserver(&a); + observer_list.AddObserver(&b); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(10)); + + observer_list.AddObserver(&evil); + observer_list.AddObserver(&c); + observer_list.AddObserver(&d); + + // Removing an observer not in the list should do nothing. + observer_list.RemoveObserver(&e); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(10)); + + EXPECT_EQ(20, a.total); + EXPECT_EQ(-20, b.total); + EXPECT_EQ(0, c.total); + EXPECT_EQ(-10, d.total); + EXPECT_EQ(0, e.total); +} + +TEST(ObserverListThreadSafeTest, BasicTest) { + MessageLoop loop; + + scoped_refptr<ObserverListThreadSafe<Foo> > observer_list( + new ObserverListThreadSafe<Foo>); + Adder a(1); + Adder b(-1); + Adder c(1); + Adder d(-1); + ThreadSafeDisrupter evil(observer_list.get(), &c); + + observer_list->AddObserver(&a); + observer_list->AddObserver(&b); + + observer_list->Notify(&Foo::Observe, 10); + loop.RunUntilIdle(); + + observer_list->AddObserver(&evil); + observer_list->AddObserver(&c); + observer_list->AddObserver(&d); + + observer_list->Notify(&Foo::Observe, 10); + loop.RunUntilIdle(); + + EXPECT_EQ(20, a.total); + EXPECT_EQ(-20, b.total); + EXPECT_EQ(0, c.total); + EXPECT_EQ(-10, d.total); +} + +TEST(ObserverListThreadSafeTest, RemoveObserver) { + MessageLoop loop; + + scoped_refptr<ObserverListThreadSafe<Foo> > observer_list( + new ObserverListThreadSafe<Foo>); + Adder a(1), b(1); + + // A workaround for the compiler bug. See http://crbug.com/121960. + EXPECT_NE(&a, &b); + + // Should do nothing. + observer_list->RemoveObserver(&a); + observer_list->RemoveObserver(&b); + + observer_list->Notify(&Foo::Observe, 10); + loop.RunUntilIdle(); + + EXPECT_EQ(0, a.total); + EXPECT_EQ(0, b.total); + + observer_list->AddObserver(&a); + + // Should also do nothing. + observer_list->RemoveObserver(&b); + + observer_list->Notify(&Foo::Observe, 10); + loop.RunUntilIdle(); + + EXPECT_EQ(10, a.total); + EXPECT_EQ(0, b.total); +} + +TEST(ObserverListThreadSafeTest, WithoutMessageLoop) { + scoped_refptr<ObserverListThreadSafe<Foo> > observer_list( + new ObserverListThreadSafe<Foo>); + + Adder a(1), b(1), c(1); + + // No MessageLoop, so these should not be added. + observer_list->AddObserver(&a); + observer_list->AddObserver(&b); + + { + // Add c when there's a loop. + MessageLoop loop; + observer_list->AddObserver(&c); + + observer_list->Notify(&Foo::Observe, 10); + loop.RunUntilIdle(); + + EXPECT_EQ(0, a.total); + EXPECT_EQ(0, b.total); + EXPECT_EQ(10, c.total); + + // Now add a when there's a loop. + observer_list->AddObserver(&a); + + // Remove c when there's a loop. + observer_list->RemoveObserver(&c); + + // Notify again. + observer_list->Notify(&Foo::Observe, 20); + loop.RunUntilIdle(); + + EXPECT_EQ(20, a.total); + EXPECT_EQ(0, b.total); + EXPECT_EQ(10, c.total); + } + + // Removing should always succeed with or without a loop. + observer_list->RemoveObserver(&a); + + // Notifying should not fail but should also be a no-op. + MessageLoop loop; + observer_list->AddObserver(&b); + observer_list->Notify(&Foo::Observe, 30); + loop.RunUntilIdle(); + + EXPECT_EQ(20, a.total); + EXPECT_EQ(30, b.total); + EXPECT_EQ(10, c.total); +} + +class FooRemover : public Foo { + public: + explicit FooRemover(ObserverListThreadSafe<Foo>* list) : list_(list) {} + virtual ~FooRemover() {} + + void AddFooToRemove(Foo* foo) { + foos_.push_back(foo); + } + + virtual void Observe(int x) OVERRIDE { + std::vector<Foo*> tmp; + tmp.swap(foos_); + for (std::vector<Foo*>::iterator it = tmp.begin(); + it != tmp.end(); ++it) { + list_->RemoveObserver(*it); + } + } + + private: + const scoped_refptr<ObserverListThreadSafe<Foo> > list_; + std::vector<Foo*> foos_; +}; + +TEST(ObserverListThreadSafeTest, RemoveMultipleObservers) { + MessageLoop loop; + scoped_refptr<ObserverListThreadSafe<Foo> > observer_list( + new ObserverListThreadSafe<Foo>); + + FooRemover a(observer_list); + Adder b(1); + + observer_list->AddObserver(&a); + observer_list->AddObserver(&b); + + a.AddFooToRemove(&a); + a.AddFooToRemove(&b); + + observer_list->Notify(&Foo::Observe, 1); + loop.RunUntilIdle(); +} + +// A test driver for a multi-threaded notification loop. Runs a number +// of observer threads, each of which constantly adds/removes itself +// from the observer list. Optionally, if cross_thread_notifies is set +// to true, the observer threads will also trigger notifications to +// all observers. +static void ThreadSafeObserverHarness(int num_threads, + bool cross_thread_notifies) { + MessageLoop loop; + + const int kMaxThreads = 15; + num_threads = num_threads > kMaxThreads ? kMaxThreads : num_threads; + + scoped_refptr<ObserverListThreadSafe<Foo> > observer_list( + new ObserverListThreadSafe<Foo>); + Adder a(1); + Adder b(-1); + Adder c(1); + Adder d(-1); + + observer_list->AddObserver(&a); + observer_list->AddObserver(&b); + + AddRemoveThread* threaded_observer[kMaxThreads]; + base::PlatformThreadHandle threads[kMaxThreads]; + for (int index = 0; index < num_threads; index++) { + threaded_observer[index] = new AddRemoveThread(observer_list.get(), false); + EXPECT_TRUE(PlatformThread::Create(0, + threaded_observer[index], &threads[index])); + } + + // Wait for all threads to start do avoid NULL ptr dereference + // when calling Quit() + for (int index = 0; index < num_threads; ++index) { + while (!threaded_observer[index]->Started()) { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); + } + } + + Time start = Time::Now(); + while (true) { + if ((Time::Now() - start).InMilliseconds() > kThreadRunTime) + break; + + observer_list->Notify(&Foo::Observe, 10); + + loop.RunUntilIdle(); + } + + for (int index = 0; index < num_threads; index++) { + threaded_observer[index]->Quit(); + PlatformThread::Join(threads[index]); + } +} + +TEST(ObserverListThreadSafeTest, CrossThreadObserver) { + // Use 7 observer threads. Notifications only come from + // the main thread. + ThreadSafeObserverHarness(7, false); +} + +TEST(ObserverListThreadSafeTest, CrossThreadNotifications) { + // Use 3 observer threads. Notifications will fire from + // the main thread and all 3 observer threads. + ThreadSafeObserverHarness(3, true); +} + +TEST(ObserverListThreadSafeTest, OutlivesMessageLoop) { + MessageLoop* loop = new MessageLoop; + scoped_refptr<ObserverListThreadSafe<Foo> > observer_list( + new ObserverListThreadSafe<Foo>); + + Adder a(1); + observer_list->AddObserver(&a); + delete loop; + // Test passes if we don't crash here. + observer_list->Notify(&Foo::Observe, 1); +} + +TEST(ObserverListTest, Existing) { + ObserverList<Foo> observer_list(ObserverList<Foo>::NOTIFY_EXISTING_ONLY); + Adder a(1); + AddInObserve<ObserverList<Foo> > b(&observer_list); + + observer_list.AddObserver(&a); + observer_list.AddObserver(&b); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(1)); + + EXPECT_TRUE(b.added); + // B's adder should not have been notified because it was added during + // notificaiton. + EXPECT_EQ(0, b.adder.total); + + // Notify again to make sure b's adder is notified. + FOR_EACH_OBSERVER(Foo, observer_list, Observe(1)); + EXPECT_EQ(1, b.adder.total); +} + +// Same as above, but for ObserverListThreadSafe +TEST(ObserverListThreadSafeTest, Existing) { + MessageLoop loop; + scoped_refptr<ObserverListThreadSafe<Foo> > observer_list( + new ObserverListThreadSafe<Foo>(ObserverList<Foo>::NOTIFY_EXISTING_ONLY)); + Adder a(1); + AddInObserve<ObserverListThreadSafe<Foo> > b(observer_list.get()); + + observer_list->AddObserver(&a); + observer_list->AddObserver(&b); + + observer_list->Notify(&Foo::Observe, 1); + loop.RunUntilIdle(); + + EXPECT_TRUE(b.added); + // B's adder should not have been notified because it was added during + // notificaiton. + EXPECT_EQ(0, b.adder.total); + + // Notify again to make sure b's adder is notified. + observer_list->Notify(&Foo::Observe, 1); + loop.RunUntilIdle(); + EXPECT_EQ(1, b.adder.total); +} + +class AddInClearObserve : public Foo { + public: + explicit AddInClearObserve(ObserverList<Foo>* list) + : list_(list), added_(false), adder_(1) {} + + virtual void Observe(int /* x */) OVERRIDE { + list_->Clear(); + list_->AddObserver(&adder_); + added_ = true; + } + + bool added() const { return added_; } + const Adder& adder() const { return adder_; } + + private: + ObserverList<Foo>* const list_; + + bool added_; + Adder adder_; +}; + +TEST(ObserverListTest, ClearNotifyAll) { + ObserverList<Foo> observer_list; + AddInClearObserve a(&observer_list); + + observer_list.AddObserver(&a); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(1)); + EXPECT_TRUE(a.added()); + EXPECT_EQ(1, a.adder().total) + << "Adder should observe once and have sum of 1."; +} + +TEST(ObserverListTest, ClearNotifyExistingOnly) { + ObserverList<Foo> observer_list(ObserverList<Foo>::NOTIFY_EXISTING_ONLY); + AddInClearObserve a(&observer_list); + + observer_list.AddObserver(&a); + + FOR_EACH_OBSERVER(Foo, observer_list, Observe(1)); + EXPECT_TRUE(a.added()); + EXPECT_EQ(0, a.adder().total) + << "Adder should not observe, so sum should still be 0."; +} + +class ListDestructor : public Foo { + public: + explicit ListDestructor(ObserverList<Foo>* list) : list_(list) {} + virtual ~ListDestructor() {} + + virtual void Observe(int x) OVERRIDE { + delete list_; + } + + private: + ObserverList<Foo>* list_; +}; + + +TEST(ObserverListTest, IteratorOutlivesList) { + ObserverList<Foo>* observer_list = new ObserverList<Foo>; + ListDestructor a(observer_list); + observer_list->AddObserver(&a); + + FOR_EACH_OBSERVER(Foo, *observer_list, Observe(0)); + // If this test fails, there'll be Valgrind errors when this function goes out + // of scope. +} + +} // namespace
diff --git a/src/base/optional.cc b/src/base/optional.cc new file mode 100644 index 0000000..59100ad --- /dev/null +++ b/src/base/optional.cc
@@ -0,0 +1,24 @@ +/* + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "optional.h" + +namespace base { + +const nullopt_t nullopt; +const in_place_t in_place; + +} // namespace base
diff --git a/src/base/optional.h b/src/base/optional.h new file mode 100644 index 0000000..c516d7a --- /dev/null +++ b/src/base/optional.h
@@ -0,0 +1,391 @@ +/* + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BASE_OPTIONAL_H_ +#define BASE_OPTIONAL_H_ + +#include <iosfwd> + +#include "base/base_export.h" +#include "base/hash_tables.h" +#include "base/logging.h" +#include "base/memory/aligned_memory.h" + +namespace base { + +// This class is based off of std::experimental::optional: +// http://en.cppreference.com/w/cpp/experimental/optional +// +// It is a template class where instances parameterized by type T contain +// memory for an instance of type T, but it may or may not be constructed. +// If it is not constructed, it cannot be accessed, and if it is, it can +// be accessed. This allows one to check if the inner object exists or not +// before using it, and is useful for functions that may or may not return +// a value. Note that the memory for the object is stored internally, so +// no heap allocations are made over the course of construction and destruction +// of the internal object (unless the internal object allocates memory within +// its constructor). +// +// Some functionality is left out. For example, most C++11 functionality +// is not implemented, since we would like this to be friendly to non-C++11 +// compilers. +// +// In the future, if C++11 functionality is needed, it can be implemented +// and surrounded by preprocessor guards to maintain compatibility with non +// C++11 compilers. +// + +// The nullopt_t type is used as a signal for an empty optional. If any +// optional is assigned the value of nullopt, it will be disengaged. +// For example, +// base::optional<int> my_int_optional(5); +// EXPECT_FALSE(!my_int_optional); +// my_int_optional = base::nullopt; +// EXPECT_TRUE(!my_int_optional); +// +struct nullopt_t { + nullopt_t() {} +}; +extern const nullopt_t nullopt; + +// The in_place_t type is used to signal in-place construction of the internal +// object. This is used by the in place constructor of optional, which forwards +// its parameters to the internal object's constructor. +// For example, +// class Foo { +// public: +// Foo(int x, int y) { x_ = x; y_ = y; } +// int x() const { return x_; } +// int y() const { return y_; } +// +// private: +// int x_; +// int y_; +// }; +// +// ... +// +// base::optional<Foo> my_foo(base::in_place, 2, 3); +// EXPECT_FALSE(!my_foo); +// EXPECT_EQ(2, my_foo->x()); +// EXPECT_EQ(3, my_foo->y()); +// +struct in_place_t { + in_place_t() {} +}; +extern const in_place_t in_place; + +template <typename T> +class BASE_EXPORT optional { + public: + // Construction via the default constructor results in an optional that is + // not engaged. + optional() { InitializeAsDisengaged(); } + + optional(nullopt_t) { InitializeAsDisengaged(); } + + // This non-explicit singleton constructor is provided so users can pass in a + // T wherever a optional<T> is expected. + optional(const T& value) { SetValue(value); } + + optional(const optional<T>& other) { + if (other.engaged_) { + SetValue(other.value()); + } else { + InitializeAsDisengaged(); + } + } + + // Destruct contained object upon optional's destruction. + ~optional() { EnsureDisengaged(); } + + // Disengages the optional, calling the destructor of the contained object + // if it is engaged. + optional<T>& operator=(nullopt_t) { + EnsureDisengaged(); + return *this; + } + + // Reassigns the underlying optional to value passed in on the right hand + // side. This will destruct the lhs contained object first if it exists. + template <typename U> + optional<T>& operator=(const U& other) { + if (engaged_) { + value() = other; + } else { + SetValue(other); + } + return *this; + } + + // Copy assignment + optional<T>& operator=(const optional<T>& other) { + if (engaged_ && other.engaged_) { + value() = other.value(); + } else if (!engaged_ && other.engaged_) { + SetValue(other.value()); + } else if (engaged_ && !other.engaged_) { + EnsureDisengaged(); + } + // Do nothing if lhs and rhs are both not engaged. + return *this; + } + + // Overloaded conversion to bool operator for determining whether the optional + // is engaged or not. It returns true if the optional is engaged, and false + // otherwise. +#if defined(_MSC_VER) && _MSC_VER < 1800 + // MSVC 2012 does not support explicit cast operators. + // http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx + + // For any compiler that doesn't support explicit bool operators, we instead + // use the Safe Bool Idiom: http://www.artima.com/cppsource/safebool.html + private: + // The type of SafeBoolIdiomType (pointer to data member of a private type) is + // limited in functionality so much that the only thing a user can do with it + // is test for null, or apply to operator==/operator!=. Since both operators + // == and != are already overloaded for optional, this leaves null tests, + // which we use for boolean testing. + class PrivateSafeBoolIdiomFakeMemberType; + typedef PrivateSafeBoolIdiomFakeMemberType optional::*SafeBoolIdiomType; + public: + operator const SafeBoolIdiomType() const { + // If we wish to return true, we cast engaged_ to our private type giving + // a non-null pointer to data member. Otherwise, we return NULL. The + // only thing the user can do with the return type is test for NULL. + return engaged_ ? + reinterpret_cast<const SafeBoolIdiomType>(&optional::engaged_) : + NULL; + } +#else + explicit operator bool() const { return engaged_; } +#endif + + // Dereferences the internal object. + const T* operator->() const { return &(value()); } + + T* operator->() { return &(value()); } + + const T& operator*() const { return value(); } + + T& operator*() { return value(); } + + // Dereferences and returns the internal object. + const T& value() const { + DCHECK(engaged_) << "Attempted to access object in a disengaged optional."; + return *static_cast<const T*>(value_memory_.void_data()); + } + + T& value() { + DCHECK(engaged_) << "Attempted to access object in a disengaged optional."; + return *static_cast<T*>(value_memory_.void_data()); + } + + template <typename U> + T value_or(const U& value) const { + if (engaged_) { + return this->value(); + } else { + return value; + } + } + + // Swaps the values of two optionals. + void swap(optional<T>& other) { + if (engaged_ && other.engaged_) { + // Swap the value contents with each other. + std::swap(value(), other.value()); + } else if (engaged_) { + other.SetValue(value()); + EnsureDisengaged(); + } else if (other.engaged_) { + SetValue(other.value()); + other.EnsureDisengaged(); + } + // If both the lhs and rhs are not engaged, we do nothing. + } + +// include the pump.py-generated declaration and impelmentation for +// forwarding constructor and emplace. +#include "optional_internal.h" + + private: + // Sets a non-engaged optional to a specified value, and marks it as engaged. + template <typename U> + void SetValue(const U& value) { + new (value_memory_.void_data()) T(value); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + + // If an optional is engaged, it destructs the wrapped value and marks the + // optional as disengaged. Does nothing to a disengaged optional. + void EnsureDisengaged() { + if (engaged_) { + static_cast<T*>(value_memory_.void_data())->~T(); + engaged_ = false; +#if !defined(NDEBUG) + value_ptr_ = NULL; +#endif + } + } + + // Called upon object construction to initialize the object into a disengaged + // state. + void InitializeAsDisengaged() { + engaged_ = false; +#if !defined(NDEBUG) + value_ptr_ = NULL; +#endif + } + + // The actual memory reserved for the object that may or may not exist. + base::AlignedMemory<sizeof(T), ALIGNOF(T)> value_memory_; + // This boolean tracks whether or not the object is constructed yet or not. + bool engaged_; +#if !defined(NDEBUG) + // In debug builds, this member makes it easy to inspect the value contained + // in the optional via a debugger. + const T* value_ptr_; +#endif +}; + +// Comparison between 2 optionals +template <typename T> +inline bool operator==(const optional<T>& lhs, const optional<T>& rhs) { + if (!lhs) { + return !rhs; + } + + return rhs == lhs.value(); +} + +template <typename T> +inline bool operator<(const optional<T>& lhs, const optional<T>& rhs) { + if (lhs && rhs) { + return lhs.value() < rhs.value(); + } else { + // Handle all other cases simply in terms of whether the optionals are + // engaged or not. + return static_cast<bool>(lhs) < static_cast<bool>(rhs); + } +} + +// Comparison with nullopt_t +template <typename T> +inline bool operator==(nullopt_t, const optional<T>& rhs) { + return !rhs; +} + +template <typename T> +inline bool operator==(const optional<T>& lhs, nullopt_t rhs) { + return rhs == lhs; +} + +template <typename T> +inline bool operator<(const optional<T>& /* lhs */, nullopt_t) { + return false; +} + +template <typename T> +inline bool operator<(nullopt_t, const optional<T>& rhs) { + return static_cast<bool>(rhs); +} + +// Comparison between an optional and a value +template <typename T> +inline bool operator==(const optional<T>& lhs, const T& rhs) { + return (!lhs ? false : lhs.value() == rhs); +} + +template <typename T> +inline bool operator==(const T& lhs, const optional<T>& rhs) { + return rhs == lhs; +} + +template <typename T> +inline bool operator<(const T& lhs, const optional<T>& rhs) { + return rhs && lhs < rhs.value(); +} + +template <typename T> +inline bool operator<(const optional<T>& lhs, const T& rhs) { + return !lhs || lhs.value() < rhs; +} + +// This is a convenient but non-standard method, do not rely on it if you expect +// the compatibility with upcoming C++ versions. +template <typename T> +inline std::ostream& operator<<(std::ostream& stream, + const optional<T>& maybe_value) { + if (maybe_value) { + stream << *maybe_value; + } else { + stream << "nullopt"; + } + return stream; +} + +template <typename T> +optional<T> make_optional(const T& value) { + return optional<T>(value); +} + +} // namespace base + +namespace BASE_HASH_NAMESPACE { + +#if defined(BASE_HASH_USE_HASH_STRUCT) +template <typename T> +struct hash<base::optional<T> > { + public: + size_t operator()(const base::optional<T>& value) const { + if (!value) { + return 0; + } else { + return value_hash_(value.value()); + } + } + + private: + hash<T> value_hash_; +}; + +#else +template <typename T> +inline size_t hash_value(const base::optional<T>& value) { + if (!value) { + return 0; + } else { + return hash_value(value.value()); + } +} + +#endif // defined(BASE_HASH_USE_HASH_STRUCT) +} // namespace BASE_HASH_NAMESPACE + +namespace std { + +template <typename T> +void swap(base::optional<T>& lhs, base::optional<T>& rhs) { + lhs.swap(rhs); +} + +} // namespace std + +#endif // BASE_OPTIONAL_H_
diff --git a/src/base/optional_internal.h b/src/base/optional_internal.h new file mode 100644 index 0000000..2af1f64 --- /dev/null +++ b/src/base/optional_internal.h
@@ -0,0 +1,178 @@ +// This file was GENERATED by command: +// pump.py optional_internal.h.pump +// DO NOT EDIT BY HAND!!! + +// +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Begin forwarding constructor definitions //////////////////////////////////// + explicit optional(in_place_t) { + InitializeAsDisengaged(); + new (value_memory_.void_data()) T(); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + template <typename P1> + explicit optional(in_place_t, const P1& p1) { + InitializeAsDisengaged(); + new (value_memory_.void_data()) T(p1); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + template <typename P1, typename P2> + explicit optional(in_place_t, const P1& p1, const P2& p2) { + InitializeAsDisengaged(); + new (value_memory_.void_data()) T(p1, p2); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + template <typename P1, typename P2, typename P3> + explicit optional(in_place_t, const P1& p1, const P2& p2, const P3& p3) { + InitializeAsDisengaged(); + new (value_memory_.void_data()) T(p1, p2, p3); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + template <typename P1, typename P2, typename P3, typename P4> + explicit optional(in_place_t, const P1& p1, const P2& p2, const P3& p3, + const P4& p4) { + InitializeAsDisengaged(); + new (value_memory_.void_data()) T(p1, p2, p3, p4); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + template <typename P1, typename P2, typename P3, typename P4, typename P5> + explicit optional(in_place_t, const P1& p1, const P2& p2, const P3& p3, + const P4& p4, const P5& p5) { + InitializeAsDisengaged(); + new (value_memory_.void_data()) T(p1, p2, p3, p4, p5); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + template <typename P1, typename P2, typename P3, typename P4, typename P5, + typename P6> + explicit optional(in_place_t, const P1& p1, const P2& p2, const P3& p3, + const P4& p4, const P5& p5, const P6& p6) { + InitializeAsDisengaged(); + new (value_memory_.void_data()) T(p1, p2, p3, p4, p5, p6); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + template <typename P1, typename P2, typename P3, typename P4, typename P5, + typename P6, typename P7> + explicit optional(in_place_t, const P1& p1, const P2& p2, const P3& p3, + const P4& p4, const P5& p5, const P6& p6, const P7& p7) { + InitializeAsDisengaged(); + new (value_memory_.void_data()) T(p1, p2, p3, p4, p5, p6, p7); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } +// End forwarding constructor definitions ////////////////////////////////////// + +// Begin emplace(...) definitions ////////////////////////////////////////////// + void emplace() { + EnsureDisengaged(); + new (value_memory_.void_data()) T(); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + template <typename P1> + void emplace(const P1& p1) { + EnsureDisengaged(); + new (value_memory_.void_data()) T(p1); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + template <typename P1, typename P2> + void emplace(const P1& p1, const P2& p2) { + EnsureDisengaged(); + new (value_memory_.void_data()) T(p1, p2); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + template <typename P1, typename P2, typename P3> + void emplace(const P1& p1, const P2& p2, const P3& p3) { + EnsureDisengaged(); + new (value_memory_.void_data()) T(p1, p2, p3); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + template <typename P1, typename P2, typename P3, typename P4> + void emplace(const P1& p1, const P2& p2, const P3& p3, const P4& p4) { + EnsureDisengaged(); + new (value_memory_.void_data()) T(p1, p2, p3, p4); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + template <typename P1, typename P2, typename P3, typename P4, typename P5> + void emplace(const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5) { + EnsureDisengaged(); + new (value_memory_.void_data()) T(p1, p2, p3, p4, p5); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + template <typename P1, typename P2, typename P3, typename P4, typename P5, + typename P6> + void emplace(const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5, const P6& p6) { + EnsureDisengaged(); + new (value_memory_.void_data()) T(p1, p2, p3, p4, p5, p6); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + template <typename P1, typename P2, typename P3, typename P4, typename P5, + typename P6, typename P7> + void emplace(const P1& p1, const P2& p2, const P3& p3, const P4& p4, + const P5& p5, const P6& p6, const P7& p7) { + EnsureDisengaged(); + new (value_memory_.void_data()) T(p1, p2, p3, p4, p5, p6, p7); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } +// End emplace(...) definitions ////////////////////////////////////////////////
diff --git a/src/base/optional_internal.h.pump b/src/base/optional_internal.h.pump new file mode 100644 index 0000000..f11d2b2 --- /dev/null +++ b/src/base/optional_internal.h.pump
@@ -0,0 +1,72 @@ +// +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +$$ This is a pump file for generating the interface and implementation of +$$ optional's forwarding constructor and emplace() method. 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 +$$ + +$var MAX_ARITY = 7 + +$range ARITY 0..MAX_ARITY + +// Begin forwarding constructor definitions //////////////////////////////////// + +$for ARITY [[ + +$range ARG 1..ARITY + +$if ARITY > 0 [[ + template <$for ARG , [[typename P$(ARG)]]> + +]] + explicit optional(in_place_t$if ARITY > 0 [[, ]] + $for ARG , [[const P$(ARG)& p$(ARG)]]) { + InitializeAsDisengaged(); + new (value_memory_.void_data()) T($for ARG , [[p$(ARG)]]); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + +]] +// End forwarding constructor definitions ////////////////////////////////////// + +// Begin emplace(...) definitions ////////////////////////////////////////////// + +$for ARITY [[ + +$range ARG 1..ARITY + +$if ARITY > 0 [[ + template <$for ARG , [[typename P$(ARG)]]> + +]] + void emplace($for ARG , [[const P$(ARG)& p$(ARG)]]) { + EnsureDisengaged(); + new (value_memory_.void_data()) T($for ARG , [[p$(ARG)]]); + engaged_ = true; +#if !defined(NDEBUG) + value_ptr_ = static_cast<const T*>(value_memory_.void_data()); +#endif + } + +]] +// End emplace(...) definitions ////////////////////////////////////////////////
diff --git a/src/base/optional_unittest.cc b/src/base/optional_unittest.cc new file mode 100644 index 0000000..3fe99c8 --- /dev/null +++ b/src/base/optional_unittest.cc
@@ -0,0 +1,730 @@ +/* + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <set> +#include <string> +#include <vector> + +#include "base/hash_tables.h" +#include "base/optional.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::InSequence; + +typedef base::optional<int> OptionalInt; + +TEST(OptionalTest, EnsureDefaultConstructorGivesDisengagedOptional) { + OptionalInt test; + EXPECT_TRUE(!test); +} + +TEST(OptionalTest, EnsureNullOptConstructorGivesDisengagedOptional) { + OptionalInt test(base::nullopt); + EXPECT_TRUE(!test); +} + +TEST(OptionalTest, InitializeConstructor) { + OptionalInt test(2); + EXPECT_FALSE(!test); + EXPECT_EQ(2, test.value()); +} + +TEST(OptionalTest, BoolCastOperator) { + OptionalInt test; + EXPECT_FALSE(static_cast<bool>(test)); + + test = 5; + EXPECT_TRUE(static_cast<bool>(test)); +} + +TEST(OptionalTest, InitializeAssign) { + OptionalInt test = 2; + EXPECT_FALSE(!test); + EXPECT_EQ(2, test.value()); +} + +TEST(OptionalTest, ReassignValue) { + OptionalInt test(2); + test = 5; + EXPECT_FALSE(!test); + EXPECT_EQ(5, test.value()); +} + +TEST(OptionalTest, CopyAssignment) { + OptionalInt a; + OptionalInt b = 2; + OptionalInt c = 3; + a = b; + EXPECT_FALSE(!a); + EXPECT_EQ(2, a.value()); + + a = c; + EXPECT_FALSE(!a); + EXPECT_EQ(3, a.value()); +} + +TEST(OptionalTest, ClearAssignment) { + OptionalInt test(2); + test = OptionalInt(); + EXPECT_FALSE(test); +} + +TEST(OptionalTest, EnsureAssignmentOfNullOptResultsInDisengagement) { + OptionalInt test(2); + test = base::nullopt; + EXPECT_FALSE(test); +} + +TEST(OptionalTest, EnsureAssignmentOfDefaultOptionalResultsInDisengagement) { + OptionalInt test(2); + test = OptionalInt(); + EXPECT_FALSE(test); +} + +TEST(OptionalTest, CopyConstruction) { + OptionalInt test1(2); + OptionalInt test2(test1); + + EXPECT_FALSE(!test2); + EXPECT_EQ(2, test2.value()); +} + +TEST(OptionalTest, Swap) { + OptionalInt test1(1); + OptionalInt test2(2); + + // Swap two engaged optionals. + test1.swap(test2); + EXPECT_FALSE(!test1); + EXPECT_EQ(2, test1.value()); + EXPECT_EQ(1, test2.value()); + + // Swap two optionals where only one is engaged. + test1 = base::nullopt; + test1.swap(test2); + EXPECT_FALSE(test2); + EXPECT_FALSE(!test1); + EXPECT_EQ(1, test1.value()); + + // Swap two optionals where only one is engaged, except the other way around. + test1.swap(test2); + EXPECT_FALSE(test1); + EXPECT_FALSE(!test2); + EXPECT_EQ(1, test2.value()); + + // Swap two disengaged optionals. + test2 = base::nullopt; + test1.swap(test2); + EXPECT_FALSE(test1); + EXPECT_FALSE(test2); +} + +TEST(OptionalTest, StdSwap) { + OptionalInt test1(1); + OptionalInt test2(2); + + // Swap two engaged optionals. + std::swap(test1, test2); + EXPECT_FALSE(!test1); + EXPECT_EQ(2, test1.value()); + EXPECT_EQ(1, test2.value()); + + // Swap two optionals where only one is engaged. + test1 = base::nullopt; + std::swap(test1, test2); + EXPECT_FALSE(test2); + EXPECT_FALSE(!test1); + EXPECT_EQ(1, test1.value()); + + // Swap two optionals where only one is engaged, except the other way around. + std::swap(test1, test2); + EXPECT_FALSE(test1); + EXPECT_FALSE(!test2); + EXPECT_EQ(1, test2.value()); + + // Swap two disengaged optionals. + test2 = base::nullopt; + std::swap(test1, test2); + EXPECT_FALSE(test1); + EXPECT_FALSE(test2); +} + +TEST(OptionalTest, SwapWithSelf) { + OptionalInt test(1); + + test.swap(test); + EXPECT_EQ(1, test.value()); + + std::swap(test, test); + EXPECT_EQ(1, test.value()); +} + +TEST(OptionalTest, AsteriskDereference) { + OptionalInt test(2); + EXPECT_EQ(2, *test); + + *test = 5; + EXPECT_EQ(5, test.value()); +} + +namespace { +struct TestStruct { + int foobar; +}; +} // namespace + +TEST(OptionalTest, ArrowDereference) { + TestStruct a; + a.foobar = 2; + + base::optional<TestStruct> test(a); + EXPECT_EQ(2, test->foobar); + + test->foobar = 5; + EXPECT_EQ(5, test.value().foobar); + + // Test const arrow dereference. + const base::optional<TestStruct> test_const(a); + EXPECT_EQ(2, test_const->foobar); +} + +namespace { +class NoDefaultTest { + public: + explicit NoDefaultTest(int i) { foobar_ = i; } + int foobar() const { return foobar_; } + + private: + NoDefaultTest(); + + int foobar_; +}; +} // namespace + +TEST(OptionalTest, NoDefaultConstructorIsSupported) { + // First test with an object passed in upon construction + base::optional<NoDefaultTest> test1(NoDefaultTest(2)); + EXPECT_EQ(2, test1.value().foobar()); + + // Now test with an object assignment after construction + base::optional<NoDefaultTest> test2; + test2 = NoDefaultTest(5); + EXPECT_EQ(5, test2.value().foobar()); +} + +TEST(OptionalTest, TestEquivalenceComparisons) { + OptionalInt test1 = 1; + OptionalInt test2 = 2; + OptionalInt test3; + OptionalInt test4 = 2; + + EXPECT_FALSE(test1 == test2); + EXPECT_FALSE(test2 == test1); + EXPECT_FALSE(test1 == test3); + EXPECT_FALSE(test3 == test1); + EXPECT_FALSE(2 == test1); + EXPECT_FALSE(test1 == 2); + EXPECT_EQ(1, test1); + EXPECT_EQ(test1, 1); + EXPECT_EQ(test4, test2); + EXPECT_EQ(test2, test4); + + // Test nullopt comparisons + EXPECT_TRUE(test3 == base::nullopt); + EXPECT_TRUE(base::nullopt == test3); + EXPECT_FALSE(test1 == base::nullopt); + EXPECT_FALSE(base::nullopt == test1); +} + +TEST(OptionalTest, TestLessThanComparisons) { + OptionalInt test1 = 1; + OptionalInt test2 = 2; + OptionalInt test3; + OptionalInt test4 = 2; + + EXPECT_TRUE(test1 < test2); + EXPECT_FALSE(test2 < test1); + EXPECT_FALSE(test1 < test1); + EXPECT_TRUE(test3 < test1); + EXPECT_FALSE(test1 < test3); + EXPECT_FALSE(test3 < test3); + + EXPECT_TRUE(base::nullopt < test1); + EXPECT_FALSE(test1 < base::nullopt); + + EXPECT_TRUE(test1 < 2); + EXPECT_FALSE(2 < test1); +} + +TEST(OptionalTest, EnsureOptionalWorksWithFunnyAlignments) { + struct { + char c; + base::optional<uint64_t> number; + } foo; + + EXPECT_FALSE(!!foo.number); + foo.number = 1; + EXPECT_TRUE(!!foo.number); + EXPECT_EQ(1, foo.number.value()); +} + +namespace { + +class DestructorCallTester { + public: + DestructorCallTester() {}; + DestructorCallTester(const DestructorCallTester&) {}; + ~DestructorCallTester() { Die(); } + MOCK_METHOD0(Die, void()); +}; + +} // namespace + +TEST(OptionalTest, EnsureDestructorIsCalled) { + { + // Ensure destructor is called upon optional destruction + base::optional<DestructorCallTester> test(base::in_place); + EXPECT_CALL(test.value(), Die()); + } + + { + // Ensure destructor is called upon assignment to null + base::optional<DestructorCallTester> test1(base::in_place); + base::optional<DestructorCallTester> test2(base::in_place); + { + InSequence s; + EXPECT_CALL(test1.value(), Die()); + EXPECT_CALL(test2.value(), Die()); + } + test1 = base::nullopt; + } +} + +namespace { + +// This class counts all calls to the set of methods declared in the class. +// It can be used to verify that certain methods are indeed called. +class MethodCallCounter { + public: + MethodCallCounter() { + ResetCounts(); + ++default_constructor_calls_; + } + + MethodCallCounter(const MethodCallCounter& other) { + // A very non-standard copy constructor, since this is a test object + // intended to count method calls on this object and only this object. + ResetCounts(); + ++copy_constructor_calls_; + } + + MethodCallCounter& operator=(const MethodCallCounter& other) { + ++operator_equals_calls_; + return *this; + } + + int default_constructor_calls() const { return default_constructor_calls_; } + int copy_constructor_calls() const { return copy_constructor_calls_; } + int operator_equals_calls() const { return operator_equals_calls_; } + + private: + void ResetCounts() { + default_constructor_calls_ = 0; + copy_constructor_calls_ = 0; + operator_equals_calls_ = 0; + } + + int default_constructor_calls_; + int copy_constructor_calls_; + int operator_equals_calls_; +}; + +} // namespace + +TEST(OptionalTest, CopyConstructorIsCalledByValueCopyConstructor) { + MethodCallCounter original_counter; + base::optional<MethodCallCounter> test(original_counter); + EXPECT_EQ(0, test->default_constructor_calls()); + EXPECT_EQ(1, test->copy_constructor_calls()); + EXPECT_EQ(0, test->operator_equals_calls()); +} + +TEST(OptionalTest, CopyConstructorIsCalledByOptionalCopyConstructor) { + MethodCallCounter original_counter; + base::optional<MethodCallCounter> test1(original_counter); + base::optional<MethodCallCounter> test2(test1); + EXPECT_EQ(0, test2->default_constructor_calls()); + EXPECT_EQ(1, test2->copy_constructor_calls()); + EXPECT_EQ(0, test2->operator_equals_calls()); +} + +TEST(OptionalTest, CopyConstructorIsCalledByValueAssignment) { + MethodCallCounter original_counter; + base::optional<MethodCallCounter> test; + test = original_counter; + EXPECT_EQ(0, test->default_constructor_calls()); + EXPECT_EQ(1, test->copy_constructor_calls()); + EXPECT_EQ(0, test->operator_equals_calls()); +} + +TEST(OptionalTest, CopyConstructorIsCalledByOptionalAssignment) { + MethodCallCounter original_counter; + base::optional<MethodCallCounter> test1(original_counter); + base::optional<MethodCallCounter> test2; + test2 = test1; + EXPECT_EQ(0, test2->default_constructor_calls()); + EXPECT_EQ(1, test2->copy_constructor_calls()); + EXPECT_EQ(0, test2->operator_equals_calls()); +} + +TEST(OptionalTest, AssignmentIsCalledByValueAssignment) { + MethodCallCounter original_counter; + base::optional<MethodCallCounter> test(original_counter); + test = original_counter; + EXPECT_EQ(0, test->default_constructor_calls()); + EXPECT_EQ(1, test->copy_constructor_calls()); + EXPECT_EQ(1, test->operator_equals_calls()); +} + +TEST(OptionalTest, AssignmentIsCalledByOptionalAssignment) { + MethodCallCounter original_counter; + base::optional<MethodCallCounter> test1(original_counter); + base::optional<MethodCallCounter> test2(original_counter); + test2 = test1; + EXPECT_EQ(0, test2->default_constructor_calls()); + EXPECT_EQ(1, test2->copy_constructor_calls()); + EXPECT_EQ(1, test2->operator_equals_calls()); +} + +TEST(OptionalTest, EnsureSelfAssignmentIsOkay) { + // Ensure that values are as we expect them to be. + OptionalInt test1(5); + + test1 = test1; + + EXPECT_FALSE(!test1); + EXPECT_EQ(5, test1.value()); + + // Ensure that the methods we expect to be called are actually called. + base::optional<MethodCallCounter> test2(base::in_place); + + test2 = test2; + + EXPECT_EQ(1, test2->default_constructor_calls()); + EXPECT_EQ(0, test2->copy_constructor_calls()); + EXPECT_EQ(1, test2->operator_equals_calls()); +} + +namespace { + +// Helper classes to ensure that we can assign different values to optionals +// so long as the wrapped value can be assigned and constructed from the other. +struct XType { + XType(int number) { number_ = number; } + + int number_; +}; + +struct YType { + explicit YType(int number) { number_ = number; } + + explicit YType(const XType& x_type) { number_ = x_type.number_; } + + YType& operator=(const XType& x_type) { + number_ = x_type.number_; + return *this; + } + + int number_; +}; + +} // namespace + +TEST(OptionalTest, CopyConstructorIsCalledByValueAssignmentFromOtherType) { + XType x_type(1); + base::optional<YType> test; + test = x_type; + EXPECT_FALSE(!test); + EXPECT_EQ(1, test->number_); +} + +TEST(OptionalTest, AssignmentIsCalledByValueAssignmentFromOtherType) { + XType x_type(1); + base::optional<YType> test(base::in_place, 2); + test = x_type; + EXPECT_FALSE(!test); + EXPECT_EQ(1, test->number_); +} + +TEST(OptionalTest, TestMakeOptional) { + OptionalInt test = base::make_optional(5); + + EXPECT_FALSE(!test); + EXPECT_EQ(5, test.value()); +} + +TEST(OptionalTest, ValueOrTest) { + OptionalInt test; + + EXPECT_EQ(4, test.value_or(4)); + + test = 2; + + EXPECT_EQ(2, test.value_or(4)); +} + +TEST(OptionalTest, ConstOptionalsTest) { + const OptionalInt test1(5); + + EXPECT_FALSE(!test1); + EXPECT_EQ(5, test1.value()); + EXPECT_EQ(5, *test1); + + const OptionalInt test2(test1); + + EXPECT_FALSE(!test2); + EXPECT_EQ(5, test2.value()); + EXPECT_EQ(5, *test2); + + OptionalInt test3; + test3 = test2; + + EXPECT_FALSE(!test3); + EXPECT_EQ(5, test3.value()); + EXPECT_EQ(5, *test3); +} + +TEST(OptionalTest, EmplaceInt) { + OptionalInt test; + + test.emplace(5); + EXPECT_FALSE(!test); + EXPECT_EQ(5, test.value()); +} + +TEST(OptionalTest, EmplaceWithDefaultConstructor) { + base::optional<MethodCallCounter> test; + test.emplace(); + + EXPECT_FALSE(!test); + EXPECT_EQ(1, test->default_constructor_calls()); + EXPECT_EQ(0, test->copy_constructor_calls()); + EXPECT_EQ(0, test->operator_equals_calls()); +} + +namespace { + +class NoDefaultOrCopyConstructor { + public: + NoDefaultOrCopyConstructor(int x, int y) { + x_ = x; + y_ = y; + } + + int x() const { return x_; } + int y() const { return y_; } + + private: + NoDefaultOrCopyConstructor(); + NoDefaultOrCopyConstructor(const NoDefaultOrCopyConstructor&); + + int x_; + int y_; +}; + +} // namespace + +TEST(OptionalTest, EmplaceObjectWithNoDefaultOrCopyConstructor) { + base::optional<NoDefaultOrCopyConstructor> test; + test.emplace(1, 2); + EXPECT_FALSE(!test); + EXPECT_EQ(1, test->x()); + EXPECT_EQ(2, test->y()); +} + +namespace { + +class NonConstPointerInConstructor { + public: + explicit NonConstPointerInConstructor(int* param) { param_ = param; } + + void SetParam(int value) { *param_ = value; } + + private: + int* param_; +}; + +} // namespace + +TEST(OptionalTest, EmplaceObjectWithNonConstPointer) { + int value = 0; + base::optional<NonConstPointerInConstructor> test; + test.emplace(&value); + test->SetParam(5); + EXPECT_EQ(5, value); +} + +TEST(OptionalTest, ForwardingConstructorInt) { + OptionalInt test(base::in_place, 5); + EXPECT_FALSE(!test); + EXPECT_EQ(5, test.value()); +} + +TEST(OptionalTest, ForwardingConstructorWithDefaultConstructor) { + base::optional<MethodCallCounter> test(base::in_place); + EXPECT_FALSE(!test); + EXPECT_EQ(1, test->default_constructor_calls()); + EXPECT_EQ(0, test->copy_constructor_calls()); + EXPECT_EQ(0, test->operator_equals_calls()); +} + +TEST(OptionalTest, ForwardingConstructorObjectWithNoDefaultOrCopyConstructor) { + base::optional<NoDefaultOrCopyConstructor> test(base::in_place, 1, 2); + EXPECT_FALSE(!test); + EXPECT_EQ(1, test->x()); + EXPECT_EQ(2, test->y()); +} + +TEST(OptionalTest, ForwardingConstructorObjectWithNonConstPointer) { + int value = 0; + base::optional<NonConstPointerInConstructor> test(base::in_place, &value); + test->SetParam(5); + EXPECT_EQ(5, value); +} + +namespace { +typedef base::optional<std::string> OptionalString; +} // namespace + +TEST(OptionalTest, OptionalStringTest) { + { + OptionalString test; + EXPECT_TRUE(!test); + + test = std::string("foo"); + EXPECT_STREQ("foo", test->c_str()); + } + + { + OptionalString test(std::string("foo")); + EXPECT_FALSE(!test); + + EXPECT_STREQ("foo", test->c_str()); + OptionalString test2(test); + EXPECT_STREQ("foo", test2->c_str()); + } +} + +TEST(OptionalTest, OptionalStringInSetTest) { + // Optional strings in map test + std::set<OptionalString> optional_string_set; + + optional_string_set.insert(std::string("foo")); + optional_string_set.insert(std::string("bar")); + + EXPECT_TRUE(optional_string_set.find(std::string("foo")) != + optional_string_set.end()); + EXPECT_TRUE(optional_string_set.find(std::string("bar")) != + optional_string_set.end()); + EXPECT_FALSE(optional_string_set.find(OptionalString()) != + optional_string_set.end()); + + optional_string_set.insert(OptionalString()); + + EXPECT_TRUE(optional_string_set.find(std::string("foo")) != + optional_string_set.end()); + EXPECT_TRUE(optional_string_set.find(std::string("bar")) != + optional_string_set.end()); + EXPECT_TRUE(optional_string_set.find(OptionalString()) != + optional_string_set.end()); +} + +TEST(OptionalTest, StdVectorOfOptionalsWorksFine) { + std::vector<OptionalInt> test_vector; + + test_vector.reserve(test_vector.capacity() + 1); + test_vector.resize(test_vector.capacity()); + + // Make sure all current optionals are disengaged. + for (std::vector<OptionalInt>::const_iterator iter = test_vector.begin(); + iter != test_vector.end(); + ++iter) { + EXPECT_TRUE(!*iter); + } + EXPECT_TRUE(!test_vector[0]); + + test_vector.clear(); + test_vector.resize(test_vector.capacity() + 1, 5); + for (std::vector<OptionalInt>::const_iterator iter = test_vector.begin(); + iter != test_vector.end(); + ++iter) { + ASSERT_FALSE(!*iter); + EXPECT_EQ(5, **iter); + } + ASSERT_FALSE(!test_vector[0]); + EXPECT_EQ(5, *test_vector[0]); + + test_vector.push_back(8); + ASSERT_FALSE(!test_vector.back()); + EXPECT_EQ(8, *test_vector.back()); +} + +TEST(OptionalTest, OptionalStringInHashSetTest) { + // Optional strings in map test + base::hash_set<OptionalString> optional_string_set; + + optional_string_set.insert(std::string("foo")); + optional_string_set.insert(std::string("bar")); + + EXPECT_TRUE(optional_string_set.find(std::string("foo")) != + optional_string_set.end()); + EXPECT_TRUE(optional_string_set.find(std::string("bar")) != + optional_string_set.end()); + EXPECT_FALSE(optional_string_set.find(OptionalString()) != + optional_string_set.end()); + + optional_string_set.insert(OptionalString()); + + EXPECT_TRUE(optional_string_set.find(std::string("foo")) != + optional_string_set.end()); + EXPECT_TRUE(optional_string_set.find(std::string("bar")) != + optional_string_set.end()); + EXPECT_TRUE(optional_string_set.find(OptionalString()) != + optional_string_set.end()); +} + +namespace { + +OptionalInt ConditionallyMakeOptional(bool should_make, int value) { + if (should_make) { + return OptionalInt(value); + } else { + return base::nullopt; + } +} + +} // namespace + +TEST(OptionalTest, OptionalCanBeReturnedFromFunction) { + OptionalInt test1 = ConditionallyMakeOptional(true, 5); + ASSERT_FALSE(!test1); + EXPECT_EQ(5, test1.value()); + + OptionalInt test2 = ConditionallyMakeOptional(false, 5); + EXPECT_TRUE(!test2); +}
diff --git a/src/base/os_compat_android.cc b/src/base/os_compat_android.cc new file mode 100644 index 0000000..d434c5b --- /dev/null +++ b/src/base/os_compat_android.cc
@@ -0,0 +1,155 @@ +// 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/os_compat_android.h" + +#include <errno.h> +#include <math.h> +#include <sys/stat.h> +#include <time64.h> + +#include "base/rand_util.h" +#include "base/string_piece.h" +#include "base/stringprintf.h" + +// There is no futimes() avaiable in Bionic, so we provide our own +// implementation until it is there. +extern "C" { + +int futimes(int fd, const struct timeval tv[2]) { + const std::string fd_path = StringPrintf("/proc/self/fd/%d", fd); + return utimes(fd_path.c_str(), tv); +} + +// Android has only timegm64() and no timegm(). +// We replicate the behaviour of timegm() when the result overflows time_t. +time_t timegm(struct tm* const t) { + // time_t is signed on Android. + static const time_t kTimeMax = ~(1 << (sizeof(time_t) * CHAR_BIT - 1)); + static const time_t kTimeMin = (1 << (sizeof(time_t) * CHAR_BIT - 1)); + time64_t result = timegm64(t); + if (result < kTimeMin || result > kTimeMax) + return -1; + return result; +} + +// The following is only needed when building with GCC 4.6 or higher +// (i.e. not with Android GCC 4.4.3, nor with Clang). +// +// GCC is now capable of optimizing successive calls to sin() and cos() into +// a single call to sincos(). This means that source code that looks like: +// +// double c, s; +// c = cos(angle); +// s = sin(angle); +// +// Will generate machine code that looks like: +// +// double c, s; +// sincos(angle, &s, &c); +// +// Unfortunately, sincos() and friends are not part of the Android libm.so +// library provided by the NDK for API level 9. When the optimization kicks +// in, it makes the final build fail with a puzzling message (puzzling +// because 'sincos' doesn't appear anywhere in the sources!). +// +// To solve this, we provide our own implementation of the sincos() function +// and related friends. Note that we must also explicitely tell GCC to disable +// optimizations when generating these. Otherwise, the generated machine code +// for each function would simply end up calling itself, resulting in a +// runtime crash due to stack overflow. +// +#if defined(__GNUC__) && !defined(__clang__) + +// For the record, Clang does not support the 'optimize' attribute. +// In the unlikely event that it begins performing this optimization too, +// we'll have to find a different way to achieve this. NOTE: Tested with O1 +// which still performs the optimization. +// +#define GCC_NO_OPTIMIZE __attribute__((optimize("O0"))) + +GCC_NO_OPTIMIZE +void sincos(double angle, double* s, double *c) { + *c = cos(angle); + *s = sin(angle); +} + +GCC_NO_OPTIMIZE +void sincosf(float angle, float* s, float* c) { + *c = cosf(angle); + *s = sinf(angle); +} + +#endif // __GNUC__ && !__clang__ + +// An implementation of mkdtemp, since it is not exposed by the NDK +// for native API level 9 that we target. +// +// For any changes in the mkdtemp function, you should manually run the unittest +// OsCompatAndroidTest.DISABLED_TestMkdTemp in your local machine to check if it +// passes. Please don't enable it, since it creates a directory and may be +// source of flakyness. +char* mkdtemp(char* path) { + if (path == NULL) { + errno = EINVAL; + return NULL; + } + + const int path_len = strlen(path); + + // The last six characters of 'path' must be XXXXXX. + const base::StringPiece kSuffix("XXXXXX"); + const int kSuffixLen = kSuffix.length(); + if (!base::StringPiece(path, path_len).ends_with(kSuffix)) { + errno = EINVAL; + return NULL; + } + + // If the path contains a directory, as in /tmp/foo/XXXXXXXX, make sure + // that /tmp/foo exists, otherwise we're going to loop a really long + // time for nothing below + char* dirsep = strrchr(path, '/'); + if (dirsep != NULL) { + struct stat st; + int ret; + + *dirsep = '\0'; // Terminating directory path temporarily + + ret = stat(path, &st); + + *dirsep = '/'; // Restoring directory separator + if (ret < 0) // Directory probably does not exist + return NULL; + if (!S_ISDIR(st.st_mode)) { // Not a directory + errno = ENOTDIR; + return NULL; + } + } + + // Max number of tries using different random suffixes. + const int kMaxTries = 100; + + // Now loop until we CAN create a directory by that name or we reach the max + // number of tries. + for (int i = 0; i < kMaxTries; ++i) { + // Fill the suffix XXXXXX with a random string composed of a-z chars. + for (int pos = 0; pos < kSuffixLen; ++pos) { + char rand_char = static_cast<char>(base::RandInt('a', 'z')); + path[path_len - kSuffixLen + pos] = rand_char; + } + if (mkdir(path, 0700) == 0) { + // We just created the directory succesfully. + return path; + } + if (errno != EEXIST) { + // The directory doesn't exist, but an error occured + return NULL; + } + } + + // We reached the max number of tries. + return NULL; +} + +} // extern "C"
diff --git a/src/base/os_compat_android.h b/src/base/os_compat_android.h new file mode 100644 index 0000000..0f25444 --- /dev/null +++ b/src/base/os_compat_android.h
@@ -0,0 +1,28 @@ +// 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_OS_COMPAT_ANDROID_H_ +#define BASE_OS_COMPAT_ANDROID_H_ + +#include <fcntl.h> +#include <sys/types.h> +#include <utime.h> + +// Not implemented in Bionic. +extern "C" int futimes(int fd, const struct timeval tv[2]); + +// Not exposed or implemented in Bionic. +extern "C" char* mkdtemp(char* path); + +// Android has no timegm(). +extern "C" time_t timegm(struct tm* const t); + +// The lockf() function is not available on Android; we translate to flock(). +#define F_LOCK LOCK_EX +#define F_ULOCK LOCK_UN +inline int lockf(int fd, int cmd, off_t ignored_len) { + return flock(fd, cmd); +} + +#endif // BASE_OS_COMPAT_ANDROID_H_
diff --git a/src/base/os_compat_android_unittest.cc b/src/base/os_compat_android_unittest.cc new file mode 100644 index 0000000..c749b6a --- /dev/null +++ b/src/base/os_compat_android_unittest.cc
@@ -0,0 +1,41 @@ +// 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/os_compat_android.h" + +#include "base/file_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +typedef testing::Test OsCompatAndroidTest; + +// Keep this Unittest DISABLED_ , because it actually creates a directory in the +// device and it may be source of flakyness. For any changes in the mkdtemp +// function, you should run this unittest in your local machine to check if it +// passes. +TEST_F(OsCompatAndroidTest, DISABLED_TestMkdTemp) { + FilePath tmp_dir; + EXPECT_TRUE(file_util::GetTempDir(&tmp_dir)); + + // Not six XXXXXX at the suffix of the path. + FilePath sub_dir = tmp_dir.Append("XX"); + std::string sub_dir_string = sub_dir.value(); + // this should be OK since mkdtemp just replaces characters in place + char* buffer = const_cast<char*>(sub_dir_string.c_str()); + EXPECT_EQ(NULL, mkdtemp(buffer)); + + // Directory does not exist + char invalid_path2[] = "doesntoexist/foobarXXXXXX"; + EXPECT_EQ(NULL, mkdtemp(invalid_path2)); + + // Successfully create a tmp dir. + FilePath sub_dir2 = tmp_dir.Append("XXXXXX"); + std::string sub_dir2_string = sub_dir2.value(); + // this should be OK since mkdtemp just replaces characters in place + char* buffer2 = const_cast<char*>(sub_dir2_string.c_str()); + EXPECT_TRUE(mkdtemp(buffer2) != NULL); +} + +} // namespace base
diff --git a/src/base/os_compat_nacl.cc b/src/base/os_compat_nacl.cc new file mode 100644 index 0000000..58fe93e --- /dev/null +++ b/src/base/os_compat_nacl.cc
@@ -0,0 +1,30 @@ +// 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/os_compat_nacl.h" + +#include <stdlib.h> +#include <time.h> + +#if !defined (__GLIBC__) + +extern "C" { +// Native Client has no timegm(). +time_t timegm(struct tm* tm) { + time_t ret; + char* tz; + tz = getenv("TZ"); + setenv("TZ", "", 1); + tzset(); + ret = mktime(tm); + if (tz) + setenv("TZ", tz, 1); + else + unsetenv("TZ"); + tzset(); + return ret; +} +} // extern "C" + +#endif // !defined (__GLIBC__)
diff --git a/src/base/os_compat_nacl.h b/src/base/os_compat_nacl.h new file mode 100644 index 0000000..13e0e3f --- /dev/null +++ b/src/base/os_compat_nacl.h
@@ -0,0 +1,16 @@ +// 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_OS_COMPAT_NACL_H_ +#define BASE_OS_COMPAT_NACL_H_ + +#include <sys/types.h> + +#if !defined (__GLIBC__) +// NaCl has no timegm(). +extern "C" time_t timegm(struct tm* const t); +#endif // !defined (__GLIBC__) + +#endif // BASE_OS_COMPAT_NACL_H_ +
diff --git a/src/base/path_service.cc b/src/base/path_service.cc new file mode 100644 index 0000000..a514916 --- /dev/null +++ b/src/base/path_service.cc
@@ -0,0 +1,346 @@ +// 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/path_service.h" + +#ifdef OS_WIN +#include <windows.h> +#include <shellapi.h> +#include <shlobj.h> +#endif + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/hash_tables.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/synchronization/lock.h" + +namespace base { + bool PathProvider(int key, FilePath* result); +#if defined(OS_WIN) + bool PathProviderWin(int key, FilePath* result); +#elif defined(OS_STARBOARD) + bool PathProviderStarboard(int key, FilePath* result); +#elif defined(__LB_SHELL__) + bool PathProviderShell(int key, FilePath* result); +#elif defined(OS_MACOSX) + bool PathProviderMac(int key, FilePath* result); +#elif defined(OS_ANDROID) + bool PathProviderAndroid(int key, FilePath* result); +#elif defined(OS_POSIX) + // PathProviderPosix is the default path provider on POSIX OSes other than + // Mac and Android. + bool PathProviderPosix(int key, FilePath* result); +#endif +} + +namespace { + +typedef base::hash_map<int, FilePath> PathMap; + +// We keep a linked list of providers. In a debug build we ensure that no two +// providers claim overlapping keys. +struct Provider { + PathService::ProviderFunc func; + struct Provider* next; +#ifndef NDEBUG + int key_start; + int key_end; +#endif + bool is_static; +}; + +Provider base_provider = { + base::PathProvider, + NULL, +#ifndef NDEBUG + base::PATH_START, + base::PATH_END, +#endif + true +}; + +#if defined(OS_WIN) +Provider base_provider_win = { + base::PathProviderWin, + &base_provider, +#ifndef NDEBUG + base::PATH_WIN_START, + base::PATH_WIN_END, +#endif + true +}; +#endif + +#if defined(OS_MACOSX) +Provider base_provider_mac = { + base::PathProviderMac, + &base_provider, +#ifndef NDEBUG + base::PATH_MAC_START, + base::PATH_MAC_END, +#endif + true +}; +#endif + +#if defined(OS_ANDROID) +Provider base_provider_android = { + base::PathProviderAndroid, + &base_provider, +#ifndef NDEBUG + base::PATH_ANDROID_START, + base::PATH_ANDROID_END, +#endif + true +}; +#endif + +#if defined(OS_STARBOARD) +Provider base_provider_starboard = { + base::PathProviderStarboard, + &base_provider, +#ifndef NDEBUG + base::PATH_STARBOARD_START, + base::PATH_STARBOARD_END, +#endif + true +}; +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) +Provider base_provider_posix = { +#if defined(__LB_SHELL__) + base::PathProviderShell, +#else + base::PathProviderPosix, +#endif + &base_provider, +#ifndef NDEBUG + base::PATH_POSIX_START, + base::PATH_POSIX_END, +#endif + true +}; +#endif + + +struct PathData { + base::Lock lock; + PathMap cache; // Cache mappings from path key to path value. + PathMap overrides; // Track path overrides. + Provider* providers; // Linked list of path service providers. + + PathData() { +#if defined(OS_WIN) + providers = &base_provider_win; +#elif defined(OS_MACOSX) + providers = &base_provider_mac; +#elif defined(OS_ANDROID) + providers = &base_provider_android; +#elif defined(OS_POSIX) + providers = &base_provider_posix; +#elif defined(OS_STARBOARD) + providers = &base_provider_starboard; +#endif + } + + ~PathData() { + Provider* p = providers; + while (p) { + Provider* next = p->next; + if (!p->is_static) + delete p; + p = next; + } + } +}; + +static base::LazyInstance<PathData> g_path_data = LAZY_INSTANCE_INITIALIZER; + +static PathData* GetPathData() { + return g_path_data.Pointer(); +} + +// Tries to find |key| in the cache. |path_data| should be locked by the caller! +bool LockedGetFromCache(int key, const PathData* path_data, FilePath* result) { + // check for a cached version + PathMap::const_iterator it = path_data->cache.find(key); + if (it != path_data->cache.end()) { + *result = it->second; + return true; + } + return false; +} + +// Tries to find |key| in the overrides map. |path_data| should be locked by the +// caller! +bool LockedGetFromOverrides(int key, PathData* path_data, FilePath* result) { + // check for an overridden version. + PathMap::const_iterator it = path_data->overrides.find(key); + if (it != path_data->overrides.end()) { + path_data->cache[key] = it->second; + *result = it->second; + return true; + } + return false; +} + +} // namespace + +// TODO(brettw): this function does not handle long paths (filename > MAX_PATH) +// characters). This isn't supported very well by Windows right now, so it is +// moot, but we should keep this in mind for the future. +// static +bool PathService::Get(int key, FilePath* result) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + DCHECK(result); + DCHECK_GE(key, base::DIR_CURRENT); + + // special case the current directory because it can never be cached + if (key == base::DIR_CURRENT) { +#if defined(OS_STARBOARD) + NOTREACHED() << "DIR_CURRENT not supported in Starboard."; + return false; +#else + return file_util::GetCurrentDirectory(result); +#endif + } + + Provider* provider = NULL; + { + base::AutoLock scoped_lock(path_data->lock); + if (LockedGetFromCache(key, path_data, result)) + return true; + + if (LockedGetFromOverrides(key, path_data, result)) + return true; + + // Get the beginning of the list while it is still locked. + provider = path_data->providers; + } + + FilePath path; + + // Iterating does not need the lock because only the list head might be + // modified on another thread. + while (provider) { + if (provider->func(key, &path)) + break; + DCHECK(path.empty()) << "provider should not have modified path"; + provider = provider->next; + } + + if (path.empty()) + return false; + + *result = path; + + base::AutoLock scoped_lock(path_data->lock); + path_data->cache[key] = path; + + return true; +} + +// static +bool PathService::Override(int key, const FilePath& path) { + // Just call the full function with true for the value of |create|. + return OverrideAndCreateIfNeeded(key, path, true); +} + +// static +bool PathService::OverrideAndCreateIfNeeded(int key, + const FilePath& path, + bool create) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + DCHECK_GT(key, base::DIR_CURRENT) << "invalid path key"; + + FilePath file_path = path; + + // For some locations this will fail if called from inside the sandbox there- + // fore we protect this call with a flag. + if (create) { + // Make sure the directory exists. We need to do this before we translate + // this to the absolute path because on POSIX, AbsolutePath fails if called + // on a non-existent path. + if (!file_util::PathExists(file_path) && + !file_util::CreateDirectory(file_path)) + return false; + } + + // We need to have an absolute path, as extensions and plugins don't like + // relative paths, and will gladly crash the browser in CHECK()s if they get a + // relative path. +#if defined(OS_STARBOARD) + if (!file_path.IsAbsolute()) + return false; +#else + if (!file_util::AbsolutePath(&file_path)) + return false; +#endif + + base::AutoLock scoped_lock(path_data->lock); + + // Clear the cache now. Some of its entries could have depended + // on the value we are overriding, and are now out of sync with reality. + path_data->cache.clear(); + + path_data->overrides[key] = file_path; + + return true; +} + +// static +bool PathService::RemoveOverride(int key) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + + base::AutoLock scoped_lock(path_data->lock); + + if (path_data->overrides.find(key) == path_data->overrides.end()) + return false; + + // Clear the cache now. Some of its entries could have depended on the value + // we are going to remove, and are now out of sync. + path_data->cache.clear(); + + path_data->overrides.erase(key); + + return true; +} + +// static +void PathService::RegisterProvider(ProviderFunc func, int key_start, + int key_end) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + DCHECK_GT(key_end, key_start); + + Provider* p; + + p = new Provider; + p->is_static = false; + p->func = func; +#ifndef NDEBUG + p->key_start = key_start; + p->key_end = key_end; +#endif + + base::AutoLock scoped_lock(path_data->lock); + +#ifndef NDEBUG + Provider *iter = path_data->providers; + while (iter) { + DCHECK(key_start >= iter->key_end || key_end <= iter->key_start) << + "path provider collision"; + iter = iter->next; + } +#endif + + p->next = path_data->providers; + path_data->providers = p; +}
diff --git a/src/base/path_service.h b/src/base/path_service.h new file mode 100644 index 0000000..94b0db3 --- /dev/null +++ b/src/base/path_service.h
@@ -0,0 +1,80 @@ +// 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_PATH_SERVICE_H_ +#define BASE_PATH_SERVICE_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/base_paths.h" +#include "base/gtest_prod_util.h" +#include "build/build_config.h" + +class FilePath; + +namespace base { +class ScopedPathOverride; +} // namespace + +// The path service is a global table mapping keys to file system paths. It is +// OK to use this service from multiple threads. +// +class BASE_EXPORT PathService { + public: + // Retrieves a path to a special directory or file and places it into the + // string pointed to by 'path'. If you ask for a directory it is guaranteed + // to NOT have a path separator at the end. For example, "c:\windows\temp" + // Directories are also guaranteed to exist when this function succeeds. + // + // Returns true if the directory or file was successfully retrieved. On + // failure, 'path' will not be changed. + static bool Get(int key, FilePath* path); + + // Overrides the path to a special directory or file. This cannot be used to + // change the value of DIR_CURRENT, but that should be obvious. Also, if the + // path specifies a directory that does not exist, the directory will be + // created by this method. This method returns true if successful. + // + // If the given path is relative, then it will be resolved against + // DIR_CURRENT. + // + // WARNING: Consumers of PathService::Get may expect paths to be constant + // over the lifetime of the app, so this method should be used with caution. + static bool Override(int key, const FilePath& path); + + // This function does the same as PathService::Override but it takes an extra + // parameter |create| which guides whether the directory to be overriden must + // be created in case it doesn't exist already. + static bool OverrideAndCreateIfNeeded(int key, + const FilePath& path, + bool create); + + // To extend the set of supported keys, you can register a path provider, + // which is just a function mirroring PathService::Get. The ProviderFunc + // returns false if it cannot provide a non-empty path for the given key. + // Otherwise, true is returned. + // + // WARNING: This function could be called on any thread from which the + // PathService is used, so a the ProviderFunc MUST BE THREADSAFE. + // + typedef bool (*ProviderFunc)(int, FilePath*); + + // Call to register a path provider. You must specify the range "[key_start, + // key_end)" of supported path keys. + static void RegisterProvider(ProviderFunc provider, + int key_start, + int key_end); + + private: + friend class base::ScopedPathOverride; + FRIEND_TEST_ALL_PREFIXES(PathServiceTest, RemoveOverride); + + // Removes an override for a special directory or file. Returns true if there + // was an override to remove or false if none was present. + // NOTE: This function is intended to be used by tests only! + static bool RemoveOverride(int key); +}; + +#endif // BASE_PATH_SERVICE_H_
diff --git a/src/base/path_service_unittest.cc b/src/base/path_service_unittest.cc new file mode 100644 index 0000000..51e5ccc --- /dev/null +++ b/src/base/path_service_unittest.cc
@@ -0,0 +1,218 @@ +// 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/path_service.h" + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/string_util.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest-spi.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +#if defined(OS_WIN) +#include <userenv.h> +#include "base/win/windows_version.h" +// userenv.dll is required for GetDefaultUserProfileDirectory(). +#pragma comment(lib, "userenv.lib") +#endif + +namespace { + +// Returns true if PathService::Get returns true and sets the path parameter +// to non-empty for the given PathService::DirType enumeration value. +bool ReturnsValidPath(int dir_type) { + FilePath path; + bool result = PathService::Get(dir_type, &path); + // Some paths might not exist on some platforms in which case confirming + // |result| is true and !path.empty() is the best we can do. + bool check_path_exists = true; +#if defined(__LB_SHELL__) + if (dir_type == base::DIR_USER_DESKTOP) + check_path_exists = false; +#endif +#if defined(OS_POSIX) + // If chromium has never been started on this account, the cache path may not + // exist. + if (dir_type == base::DIR_CACHE) + check_path_exists = false; +#endif +#if defined(OS_LINUX) + // On the linux try-bots: a path is returned (e.g. /home/chrome-bot/Desktop), + // but it doesn't exist. + if (dir_type == base::DIR_USER_DESKTOP) + check_path_exists = false; +#endif +#if defined(OS_WIN) + if (dir_type == base::DIR_DEFAULT_USER_QUICK_LAUNCH) { + // On Windows XP, the Quick Launch folder for the "Default User" doesn't + // exist by default. At least confirm that the path returned begins with the + // Default User's profile path. + if (base::win::GetVersion() < base::win::VERSION_VISTA) { + wchar_t default_profile_path[MAX_PATH]; + DWORD size = arraysize(default_profile_path); + return (result && + ::GetDefaultUserProfileDirectory(default_profile_path, &size) && + StartsWith(path.value(), default_profile_path, false)); + } + } else if (dir_type == base::DIR_TASKBAR_PINS) { + // There is no pinned-to-taskbar shortcuts prior to Win7. + if (base::win::GetVersion() < base::win::VERSION_WIN7) + check_path_exists = false; + } +#endif + return result && !path.empty() && (!check_path_exists || + file_util::PathExists(path)); +} + +#if defined(OS_WIN) +// Function to test any directory keys that are not supported on some versions +// of Windows. Checks that the function fails and that the returned path is +// empty. +bool ReturnsInvalidPath(int dir_type) { + FilePath path; + bool result = PathService::Get(dir_type, &path); + return !result && path.empty(); +} +#endif + +} // namespace + +// On the Mac this winds up using some autoreleased objects, so we need to +// be a PlatformTest. +typedef PlatformTest PathServiceTest; + +// Test that all PathService::Get calls return a value and a true result +// in the development environment. (This test was created because a few +// later changes to Get broke the semantics of the function and yielded the +// correct value while returning false.) +TEST_F(PathServiceTest, Get) { + for (int key = base::PATH_START + 1; key < base::PATH_END; ++key) { +#if defined(OS_ANDROID) + if (key == base::FILE_MODULE || key == base::DIR_USER_DESKTOP) + continue; // Android doesn't implement FILE_MODULE and DIR_USER_DESKTOP; +#elif defined(OS_IOS) + if (key == base::DIR_USER_DESKTOP) + continue; // iOS doesn't implement DIR_USER_DESKTOP; +#elif defined(__LB_SHELL__) + if (key == base::FILE_MODULE || key == base::FILE_EXE || + key == base::DIR_USER_DESKTOP) + continue; // lb_shell doesn't implement FILE_MODULE, FILE_EXE and + // DIR_USER_DESKTOP; +#elif defined(OS_STARBOARD) + if (key == base::FILE_MODULE || key == base::DIR_USER_DESKTOP || + key == base::DIR_CURRENT) + continue; +#endif + EXPECT_PRED1(ReturnsValidPath, key); + } +#if defined(OS_WIN) + for (int key = base::PATH_WIN_START + 1; key < base::PATH_WIN_END; ++key) { + bool valid = true; + switch(key) { + case base::DIR_LOCAL_APP_DATA_LOW: + // DIR_LOCAL_APP_DATA_LOW is not supported prior Vista and is expected + // to fail. + valid = base::win::GetVersion() >= base::win::VERSION_VISTA; + break; + case base::DIR_APP_SHORTCUTS: + // DIR_APP_SHORTCUTS is not supported prior Windows 8 and is expected to + // fail. + valid = base::win::GetVersion() >= base::win::VERSION_WIN8; + break; + } + + if (valid) + EXPECT_TRUE(ReturnsValidPath(key)) << key; + else + EXPECT_TRUE(ReturnsInvalidPath(key)) << key; + } +#elif defined(OS_MACOSX) + for (int key = base::PATH_MAC_START + 1; key < base::PATH_MAC_END; ++key) { + EXPECT_PRED1(ReturnsValidPath, key); + } +#elif defined(OS_ANDROID) + for (int key = base::PATH_ANDROID_START + 1; key < base::PATH_ANDROID_END; + ++key) { + EXPECT_PRED1(ReturnsValidPath, key); + } +#elif defined(OS_POSIX) + for (int key = base::PATH_POSIX_START + 1; key < base::PATH_POSIX_END; + ++key) { + EXPECT_PRED1(ReturnsValidPath, key); + } +#endif +} + +// test that all versions of the Override function of PathService do what they +// are supposed to do. +TEST_F(PathServiceTest, Override) { + int my_special_key = 666; + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath fake_cache_dir(temp_dir.path().AppendASCII("cache")); + // PathService::Override should always create the path provided if it doesn't + // exist. + EXPECT_TRUE(PathService::Override(my_special_key, fake_cache_dir)); + EXPECT_TRUE(file_util::PathExists(fake_cache_dir)); + + FilePath fake_cache_dir2(temp_dir.path().AppendASCII("cache2")); + // PathService::OverrideAndCreateIfNeeded should obey the |create| parameter. + PathService::OverrideAndCreateIfNeeded(my_special_key, + fake_cache_dir2, + false); + EXPECT_FALSE(file_util::PathExists(fake_cache_dir2)); + EXPECT_TRUE(PathService::OverrideAndCreateIfNeeded(my_special_key, + fake_cache_dir2, + true)); + EXPECT_TRUE(file_util::PathExists(fake_cache_dir2)); +} + +// Check if multiple overrides can co-exist. +TEST_F(PathServiceTest, OverrideMultiple) { + int my_special_key = 666; + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath fake_cache_dir1(temp_dir.path().AppendASCII("1")); + EXPECT_TRUE(PathService::Override(my_special_key, fake_cache_dir1)); + EXPECT_TRUE(file_util::PathExists(fake_cache_dir1)); + ASSERT_EQ(1, file_util::WriteFile(fake_cache_dir1.AppendASCII("t1"), ".", 1)); + + FilePath fake_cache_dir2(temp_dir.path().AppendASCII("2")); + EXPECT_TRUE(PathService::Override(my_special_key + 1, fake_cache_dir2)); + EXPECT_TRUE(file_util::PathExists(fake_cache_dir2)); + ASSERT_EQ(1, file_util::WriteFile(fake_cache_dir2.AppendASCII("t2"), ".", 1)); + + FilePath result; + EXPECT_TRUE(PathService::Get(my_special_key, &result)); + // Override might have changed the path representation but our test file + // should be still there. + EXPECT_TRUE(file_util::PathExists(result.AppendASCII("t1"))); + EXPECT_TRUE(PathService::Get(my_special_key + 1, &result)); + EXPECT_TRUE(file_util::PathExists(result.AppendASCII("t2"))); +} + +TEST_F(PathServiceTest, RemoveOverride) { + // Before we start the test we have to call RemoveOverride at least once to + // clear any overrides that might have been left from other tests. + PathService::RemoveOverride(base::DIR_TEMP); + + FilePath original_user_data_dir; + EXPECT_TRUE(PathService::Get(base::DIR_TEMP, &original_user_data_dir)); + EXPECT_FALSE(PathService::RemoveOverride(base::DIR_TEMP)); + + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + EXPECT_TRUE(PathService::Override(base::DIR_TEMP, temp_dir.path())); + FilePath new_user_data_dir; + EXPECT_TRUE(PathService::Get(base::DIR_TEMP, &new_user_data_dir)); + EXPECT_NE(original_user_data_dir, new_user_data_dir); + + EXPECT_TRUE(PathService::RemoveOverride(base::DIR_TEMP)); + EXPECT_TRUE(PathService::Get(base::DIR_TEMP, &new_user_data_dir)); + EXPECT_EQ(original_user_data_dir, new_user_data_dir); +}
diff --git a/src/base/pending_task.cc b/src/base/pending_task.cc new file mode 100644 index 0000000..b288f28 --- /dev/null +++ b/src/base/pending_task.cc
@@ -0,0 +1,60 @@ +// 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/pending_task.h" + +#include "base/tracked_objects.h" + +namespace base { + +#if _MSC_VER >= 1700 +// This a temporary fix for compiling on VS2012. http://crbug.com/154744 +PendingTask::PendingTask() : sequence_num(-1), nestable(false) { +} +#endif + +PendingTask::PendingTask(const tracked_objects::Location& posted_from, + const base::Closure& task) + : base::TrackingInfo(posted_from, TimeTicks()), + task(task), + posted_from(posted_from), + sequence_num(0), + nestable(true) { +} + +PendingTask::PendingTask(const tracked_objects::Location& posted_from, + const base::Closure& task, + TimeTicks delayed_run_time, + bool nestable) + : base::TrackingInfo(posted_from, delayed_run_time), + task(task), + posted_from(posted_from), + sequence_num(0), + nestable(nestable) { +} + +PendingTask::~PendingTask() { +} + +bool PendingTask::operator<(const PendingTask& other) const { + // Since the top of a priority queue is defined as the "greatest" element, we + // need to invert the comparison here. We want the smaller time to be at the + // top of the heap. + + if (delayed_run_time < other.delayed_run_time) + return false; + + if (delayed_run_time > other.delayed_run_time) + return true; + + // If the times happen to match, then we use the sequence number to decide. + // Compare the difference to support integer roll-over. + return (sequence_num - other.sequence_num) > 0; +} + +void TaskQueue::Swap(TaskQueue* queue) { + c.swap(queue->c); // Calls std::deque::swap. +} + +} // namespace base
diff --git a/src/base/pending_task.h b/src/base/pending_task.h new file mode 100644 index 0000000..6e4a2dd --- /dev/null +++ b/src/base/pending_task.h
@@ -0,0 +1,60 @@ +// 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. + +#ifndef PENDING_TASK_H_ +#define PENDING_TASK_H_ + +#include <queue> + +#include "base/base_export.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/time.h" +#include "base/tracking_info.h" + +namespace base { + +// Contains data about a pending task. Stored in TaskQueue and DelayedTaskQueue +// for use by classes that queue and execute tasks. +struct BASE_EXPORT PendingTask : public TrackingInfo { +#if _MSC_VER >= 1700 + PendingTask(); +#endif + PendingTask(const tracked_objects::Location& posted_from, + const Closure& task); + PendingTask(const tracked_objects::Location& posted_from, + const Closure& task, + TimeTicks delayed_run_time, + bool nestable); + ~PendingTask(); + + // Used to support sorting. + bool operator<(const PendingTask& other) const; + + // The task to run. + Closure task; + + // The site this PendingTask was posted from. + tracked_objects::Location posted_from; + + // Secondary sort key for run time. + int sequence_num; + + // OK to dispatch from a nested loop. + bool nestable; +}; + +// Wrapper around std::queue specialized for PendingTask which adds a Swap +// helper method. +class BASE_EXPORT TaskQueue : public std::queue<PendingTask> { + public: + void Swap(TaskQueue* queue); +}; + +// PendingTasks are sorted by their |delayed_run_time| property. +typedef std::priority_queue<base::PendingTask> DelayedTaskQueue; + +} // namespace base + +#endif // PENDING_TASK_H_
diff --git a/src/base/perftimer.cc b/src/base/perftimer.cc new file mode 100644 index 0000000..4c64c5e --- /dev/null +++ b/src/base/perftimer.cc
@@ -0,0 +1,45 @@ +// Copyright (c) 2006-2008 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/perftimer.h" + +#include <stdio.h> +#include <string> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" + +static FILE* perf_log_file = NULL; + +bool InitPerfLog(const FilePath& log_file) { + if (perf_log_file) { + // trying to initialize twice + NOTREACHED(); + return false; + } + + perf_log_file = file_util::OpenFile(log_file, "w"); + return perf_log_file != NULL; +} + +void FinalizePerfLog() { + if (!perf_log_file) { + // trying to cleanup without initializing + NOTREACHED(); + return; + } + file_util::CloseFile(perf_log_file); +} + +void LogPerfResult(const char* test_name, double value, const char* units) { + if (!perf_log_file) { + NOTREACHED(); + return; + } + + fprintf(perf_log_file, "%s\t%g\t%s\n", test_name, value, units); + printf("%s\t%g\t%s\n", test_name, value, units); +}
diff --git a/src/base/perftimer.h b/src/base/perftimer.h new file mode 100644 index 0000000..1ac1a7d --- /dev/null +++ b/src/base/perftimer.h
@@ -0,0 +1,83 @@ +// Copyright (c) 2006-2008 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_PERFTIMER_H_ +#define BASE_PERFTIMER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/time.h" + +class FilePath; + +// ---------------------------------------------------------------------- +// Initializes and finalizes the perf log. These functions should be +// called at the beginning and end (respectively) of running all the +// performance tests. The init function returns true on success. +// ---------------------------------------------------------------------- +bool InitPerfLog(const FilePath& log_path); +void FinalizePerfLog(); + +// ---------------------------------------------------------------------- +// LogPerfResult +// Writes to the perf result log the given 'value' resulting from the +// named 'test'. The units are to aid in reading the log by people. +// ---------------------------------------------------------------------- +void LogPerfResult(const char* test_name, double value, const char* units); + +// ---------------------------------------------------------------------- +// PerfTimer +// A simple wrapper around Now() +// ---------------------------------------------------------------------- +class PerfTimer { + public: + PerfTimer() { + begin_ = base::TimeTicks::Now(); + } + + // Returns the time elapsed since object construction + base::TimeDelta Elapsed() const { + return base::TimeTicks::Now() - begin_; + } + + private: + base::TimeTicks begin_; +}; + +// ---------------------------------------------------------------------- +// PerfTimeLogger +// Automates calling LogPerfResult for the common case where you want +// to measure the time that something took. Call Done() when the test +// is complete if you do extra work after the test or there are stack +// objects with potentially expensive constructors. Otherwise, this +// class with automatically log on destruction. +// ---------------------------------------------------------------------- +class PerfTimeLogger { + public: + explicit PerfTimeLogger(const char* test_name) + : logged_(false), + test_name_(test_name) { + } + + ~PerfTimeLogger() { + if (!logged_) + Done(); + } + + void Done() { + // we use a floating-point millisecond value because it is more + // intuitive than microseconds and we want more precision than + // integer milliseconds + LogPerfResult(test_name_.c_str(), timer_.Elapsed().InMillisecondsF(), "ms"); + logged_ = true; + } + + private: + bool logged_; + std::string test_name_; + PerfTimer timer_; +}; + +#endif // BASE_PERFTIMER_H_
diff --git a/src/base/perftimer_starboard.cc b/src/base/perftimer_starboard.cc new file mode 100644 index 0000000..1226e5f --- /dev/null +++ b/src/base/perftimer_starboard.cc
@@ -0,0 +1,64 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Written for Starboard, but should work for any platform that implements +// base::PlatformFile. + +#include "base/perftimer.h" + +#include <string> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/platform_file.h" +#include "base/stringprintf.h" + +static base::PlatformFile perf_log_file = base::kInvalidPlatformFileValue; + +bool InitPerfLog(const FilePath& log_file) { + if (perf_log_file != base::kInvalidPlatformFileValue) { + // Avoid double initialization. + NOTREACHED(); + return false; + } + + perf_log_file = base::CreatePlatformFile( + log_file, base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_WRITE, + NULL, NULL); + return perf_log_file != base::kInvalidPlatformFileValue; +} + +void FinalizePerfLog() { + if (perf_log_file == base::kInvalidPlatformFileValue) { + // The caller is trying to cleanup without initializing. + NOTREACHED(); + return; + } + base::ClosePlatformFile(perf_log_file); +} + +void LogPerfResult(const char* test_name, double value, const char* units) { + if (perf_log_file == base::kInvalidPlatformFileValue) { + NOTREACHED(); + return; + } + + std::string message = + base::StringPrintf("%s\t%g\t%s\n", test_name, value, units); + base::WritePlatformFileAtCurrentPos(perf_log_file, message.c_str(), + message.length()); + DLOG(INFO) << message; +}
diff --git a/src/base/pickle.cc b/src/base/pickle.cc new file mode 100644 index 0000000..43121c9 --- /dev/null +++ b/src/base/pickle.cc
@@ -0,0 +1,378 @@ +// 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/pickle.h" + +#include <algorithm> // for max() + +// Must come after <algorithm> +#if defined(OS_STARBOARD) +#include "starboard/memory.h" +#define realloc SbMemoryReallocate +#define free SbMemoryFree +#define memcpy SbMemoryCopy +#else +#include <stdlib.h> +#endif + +//------------------------------------------------------------------------------ + +// static +const int Pickle::kPayloadUnit = 64; + +static const size_t kCapacityReadOnly = static_cast<size_t>(-1); + +PickleIterator::PickleIterator(const Pickle& pickle) + : read_ptr_(pickle.payload()), + read_end_ptr_(pickle.end_of_payload()) { +} + +template <typename Type> +inline bool PickleIterator::ReadBuiltinType(Type* result) { + const char* read_from = GetReadPointerAndAdvance<Type>(); + if (!read_from) + return false; + if (sizeof(Type) > sizeof(uint32)) + memcpy(result, read_from, sizeof(*result)); + else + *result = *reinterpret_cast<const Type*>(read_from); + return true; +} + +template<typename Type> +inline const char* PickleIterator::GetReadPointerAndAdvance() { + const char* current_read_ptr = read_ptr_; + if (read_ptr_ + sizeof(Type) > read_end_ptr_) + return NULL; + if (sizeof(Type) < sizeof(uint32)) + read_ptr_ += AlignInt(sizeof(Type), sizeof(uint32)); + else + read_ptr_ += sizeof(Type); + return current_read_ptr; +} + +const char* PickleIterator::GetReadPointerAndAdvance(int num_bytes) { + if (num_bytes < 0 || read_end_ptr_ - read_ptr_ < num_bytes) + return NULL; + const char* current_read_ptr = read_ptr_; + read_ptr_ += AlignInt(num_bytes, sizeof(uint32)); + return current_read_ptr; +} + +inline const char* PickleIterator::GetReadPointerAndAdvance(int num_elements, + size_t size_element) { + // Check for int32 overflow. + int64 num_bytes = static_cast<int64>(num_elements) * size_element; + int num_bytes32 = static_cast<int>(num_bytes); + if (num_bytes != static_cast<int64>(num_bytes32)) + return NULL; + return GetReadPointerAndAdvance(num_bytes32); +} + +bool PickleIterator::ReadBool(bool* result) { +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) + // Endian-agnostic method + int value; + bool status = ReadBuiltinType(&value); + if (status && result) + *result = (value != 0); + return status; +#else + return ReadBuiltinType(result); +#endif +} + +bool PickleIterator::ReadInt(int* result) { + return ReadBuiltinType(result); +} + +bool PickleIterator::ReadLong(long* result) { + return ReadBuiltinType(result); +} + +bool PickleIterator::ReadUInt16(uint16* result) { + return ReadBuiltinType(result); +} + +bool PickleIterator::ReadUInt32(uint32* result) { + return ReadBuiltinType(result); +} + +bool PickleIterator::ReadInt64(int64* result) { + return ReadBuiltinType(result); +} + +bool PickleIterator::ReadUInt64(uint64* result) { + return ReadBuiltinType(result); +} + +bool PickleIterator::ReadFloat(float* result) { + return ReadBuiltinType(result); +} + +bool PickleIterator::ReadString(std::string* result) { + int len; + if (!ReadInt(&len)) + return false; + const char* read_from = GetReadPointerAndAdvance(len); + if (!read_from) + return false; + + result->assign(read_from, len); + return true; +} + +bool PickleIterator::ReadWString(std::wstring* result) { + int len; + if (!ReadInt(&len)) + return false; + const char* read_from = GetReadPointerAndAdvance(len, sizeof(wchar_t)); + if (!read_from) + return false; + + result->assign(reinterpret_cast<const wchar_t*>(read_from), len); + return true; +} + +bool PickleIterator::ReadString16(string16* result) { + int len; + if (!ReadInt(&len)) + return false; + const char* read_from = GetReadPointerAndAdvance(len, sizeof(char16)); + if (!read_from) + return false; + + result->assign(reinterpret_cast<const char16*>(read_from), len); + return true; +} + +bool PickleIterator::ReadData(const char** data, int* length) { + *length = 0; + *data = 0; + + if (!ReadInt(length)) + return false; + + return ReadBytes(data, *length); +} + +bool PickleIterator::ReadBytes(const char** data, int length) { + const char* read_from = GetReadPointerAndAdvance(length); + if (!read_from) + return false; + *data = read_from; + return true; +} + +// Payload is uint32 aligned. + +Pickle::Pickle() + : header_(NULL), + header_size_(sizeof(Header)), + capacity_(0), + variable_buffer_offset_(0) { + Resize(kPayloadUnit); + header_->payload_size = 0; +} + +Pickle::Pickle(int header_size) + : header_(NULL), + header_size_(AlignInt(header_size, sizeof(uint32))), + capacity_(0), + variable_buffer_offset_(0) { + DCHECK_GE(static_cast<size_t>(header_size), sizeof(Header)); + DCHECK_LE(header_size, kPayloadUnit); + Resize(kPayloadUnit); + header_->payload_size = 0; +} + +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) { + if (data_len >= static_cast<int>(sizeof(Header))) + header_size_ = data_len - header_->payload_size; + + if (header_size_ > static_cast<unsigned int>(data_len)) + header_size_ = 0; + + if (header_size_ != AlignInt(header_size_, sizeof(uint32))) + header_size_ = 0; + + // If there is anything wrong with the data, we're not going to use it. + if (!header_size_) + header_ = NULL; +} + +Pickle::Pickle(const Pickle& other) + : header_(NULL), + header_size_(other.header_size_), + capacity_(0), + variable_buffer_offset_(other.variable_buffer_offset_) { + size_t payload_size = header_size_ + other.header_->payload_size; + bool resized = Resize(payload_size); + CHECK(resized); // Realloc failed. + memcpy(header_, other.header_, payload_size); +} + +Pickle::~Pickle() { + if (capacity_ != kCapacityReadOnly) + free(header_); +} + +Pickle& Pickle::operator=(const Pickle& other) { + if (this == &other) { + NOTREACHED(); + return *this; + } + if (capacity_ == kCapacityReadOnly) { + header_ = NULL; + capacity_ = 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. + memcpy(header_, other.header_, + other.header_size_ + other.header_->payload_size); + variable_buffer_offset_ = other.variable_buffer_offset_; + return *this; +} + +bool Pickle::WriteString(const std::string& value) { + if (!WriteInt(static_cast<int>(value.size()))) + return false; + + return WriteBytes(value.data(), static_cast<int>(value.size())); +} + +bool Pickle::WriteWString(const std::wstring& value) { + if (!WriteInt(static_cast<int>(value.size()))) + return false; + + return WriteBytes(value.data(), + static_cast<int>(value.size() * sizeof(wchar_t))); +} + +bool Pickle::WriteString16(const string16& value) { + if (!WriteInt(static_cast<int>(value.size()))) + return false; + + return WriteBytes(value.data(), + static_cast<int>(value.size()) * sizeof(char16)); +} + +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); + 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; + +#ifdef ARCH_CPU_64_BITS + DCHECK_LE(length, kuint32max); +#endif + + header_->payload_size = static_cast<uint32>(new_size); + return payload() + offset; +} + +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) { + new_capacity = AlignInt(new_capacity, kPayloadUnit); + + CHECK_NE(capacity_, kCapacityReadOnly); + void* p = realloc(header_, new_capacity); + if (!p) + return false; + + header_ = reinterpret_cast<Header*>(p); + capacity_ = new_capacity; + return true; +} + +// static +const char* Pickle::FindNext(size_t header_size, + const char* start, + const char* end) { + 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)) + 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) + return NULL; + + return (payload_end > end) ? NULL : payload_end; +}
diff --git a/src/base/pickle.h b/src/base/pickle.h new file mode 100644 index 0000000..cd587de --- /dev/null +++ b/src/base/pickle.h
@@ -0,0 +1,350 @@ +// 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_PICKLE_H__ +#define BASE_PICKLE_H__ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/logging.h" +#include "base/string16.h" + +class Pickle; + +// PickleIterator reads data from a Pickle. The Pickle object must remain valid +// while the PickleIterator object is in use. +class BASE_EXPORT PickleIterator { + public: + PickleIterator() : read_ptr_(NULL), read_end_ptr_(NULL) {} + explicit PickleIterator(const Pickle& pickle); + + // Methods for reading the payload of the Pickle. To read from the start of + // the Pickle, create a PickleIterator from a Pickle. If successful, these + // methods return true. Otherwise, false is returned to indicate that the + // result could not be extracted. + bool ReadBool(bool* result) WARN_UNUSED_RESULT; + bool ReadInt(int* result) WARN_UNUSED_RESULT; + bool ReadLong(long* result) WARN_UNUSED_RESULT; + bool ReadUInt16(uint16* result) WARN_UNUSED_RESULT; + bool ReadUInt32(uint32* result) WARN_UNUSED_RESULT; + bool ReadInt64(int64* result) WARN_UNUSED_RESULT; + bool ReadUInt64(uint64* result) WARN_UNUSED_RESULT; + 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 ReadData(const char** data, int* length) WARN_UNUSED_RESULT; + bool ReadBytes(const char** data, int length) WARN_UNUSED_RESULT; + + // Safer version of ReadInt() checks for the result not being negative. + // Use it for reading the object sizes. + bool ReadLength(int* result) WARN_UNUSED_RESULT { + return ReadInt(result) && *result >= 0; + } + + // Skips bytes in the read buffer and returns true if there are at least + // num_bytes available. Otherwise, does nothing and returns false. + bool SkipBytes(int num_bytes) WARN_UNUSED_RESULT { + return !!GetReadPointerAndAdvance(num_bytes); + } + + private: + // Aligns 'i' by rounding it up to the next multiple of 'alignment' + static size_t AlignInt(size_t i, int alignment) { + return i + (alignment - (i % alignment)) % alignment; + } + + // Read Type from Pickle. + template <typename Type> + inline bool ReadBuiltinType(Type* result); + + // Get read pointer for Type and advance read pointer. + template<typename Type> + inline const char* GetReadPointerAndAdvance(); + + // Get read pointer for |num_bytes| and advance read pointer. This method + // checks num_bytes for negativity and wrapping. + const char* GetReadPointerAndAdvance(int num_bytes); + + // Get read pointer for (num_elements * size_element) bytes and advance read + // pointer. This method checks for int overflow, negativity and wrapping. + inline const char* GetReadPointerAndAdvance(int num_elements, + size_t size_element); + + // Pointers to the Pickle data. + const char* read_ptr_; + const char* read_end_ptr_; + + FRIEND_TEST_ALL_PREFIXES(PickleTest, GetReadPointerAndAdvance); +}; + +// This class provides facilities for basic binary value packing and unpacking. +// +// The Pickle class supports appending primitive values (ints, strings, etc.) +// to a pickle instance. The Pickle instance grows its internal memory buffer +// dynamically to hold the sequence of primitive values. The internal memory +// buffer is exposed as the "data" of the Pickle. This "data" can be passed +// to a Pickle object to initialize it for reading. +// +// When reading from a Pickle object, it is important for the consumer to know +// what value types to read and in what order to read them as the Pickle does +// not keep track of the type of data written to it. +// +// The Pickle's data has a header which contains the size of the Pickle's +// payload. It can optionally support additional space in the header. That +// space is controlled by the header_size parameter passed to the Pickle +// constructor. +// +class BASE_EXPORT Pickle { + public: + // Initialize a Pickle object using the default header size. + Pickle(); + + // Initialize a Pickle object with the specified header size in bytes, which + // must be greater-than-or-equal-to sizeof(Pickle::Header). The header size + // will be rounded up to ensure that the header size is 32bit-aligned. + explicit Pickle(int header_size); + + // Initializes a Pickle from a const block of data. The data is not copied; + // instead the data is merely referenced by this Pickle. Only const methods + // should be used on the Pickle when initialized this way. The header + // padding size is deduced from the data length. + Pickle(const char* data, int data_len); + + // Initializes a Pickle as a deep copy of another Pickle. + Pickle(const Pickle& other); + + // Note: There are no virtual methods in this class. This destructor is + // virtual as an element of defensive coding. Other classes have derived from + // this class, and there is a *chance* that they will cast into this base + // class before destruction. At least one such class does have a virtual + // destructor, suggesting at least some need to call more derived destructors. + virtual ~Pickle(); + + // Performs a deep copy. + Pickle& operator=(const Pickle& other); + + // Returns the size of the Pickle's data. + size_t size() const { return header_size_ + header_->payload_size; } + + // Returns the data for this Pickle. + const void* data() const { return header_; } + + // 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 { + return iter->ReadBool(result); + } + bool ReadInt(PickleIterator* iter, int* result) const { + return iter->ReadInt(result); + } + bool ReadLong(PickleIterator* iter, long* result) const { + return iter->ReadLong(result); + } + bool ReadUInt16(PickleIterator* iter, uint16* result) const { + return iter->ReadUInt16(result); + } + bool ReadUInt32(PickleIterator* iter, uint32* result) const { + return iter->ReadUInt32(result); + } + bool ReadInt64(PickleIterator* iter, int64* result) const { + return iter->ReadInt64(result); + } + bool ReadUInt64(PickleIterator* iter, uint64* result) const { + return iter->ReadUInt64(result); + } + bool ReadFloat(PickleIterator* iter, float* result) const { + return iter->ReadFloat(result); + } + bool ReadString(PickleIterator* iter, std::string* result) const { + return iter->ReadString(result); + } + bool ReadWString(PickleIterator* iter, std::wstring* result) const { + return iter->ReadWString(result); + } + bool ReadString16(PickleIterator* iter, string16* result) const { + 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 { + 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 { + 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 { + return iter->ReadLength(result); + } + + // Methods for adding to the payload of the Pickle. These values are + // appended to the end of the Pickle's payload. When reading values from a + // Pickle, it is important to read them in the order in which they were added + // to the Pickle. + bool WriteBool(bool value) { + return WriteInt(value ? 1 : 0); + } + bool WriteInt(int value) { + return WriteBytes(&value, sizeof(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 + // platforms, it is 32 bits. On 64-bit platforms, it is 64 bits. If persisted + // 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)); + } + bool WriteUInt16(uint16 value) { + return WriteBytes(&value, sizeof(value)); + } + bool WriteUInt32(uint32 value) { + return WriteBytes(&value, sizeof(value)); + } + bool WriteInt64(int64 value) { + return WriteBytes(&value, sizeof(value)); + } + bool WriteUInt64(uint64 value) { + return WriteBytes(&value, sizeof(value)); + } + bool WriteFloat(float value) { + return WriteBytes(&value, sizeof(value)); + } + bool WriteString(const std::string& value); + bool WriteWString(const std::wstring& value); + bool WriteString16(const 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 + // 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); + + // Payload follows after allocation of Header (header size is customizable). + struct Header { + uint32 payload_size; // Specifies the size of the payload. + }; + + // Returns the header, cast to a user-specified type T. The type T must be a + // subclass of Header and its size must correspond to the header_size passed + // to the Pickle constructor. + template <class T> + T* headerT() { + DCHECK_EQ(header_size_, sizeof(T)); + return static_cast<T*>(header_); + } + template <class T> + const T* headerT() const { + DCHECK_EQ(header_size_, sizeof(T)); + return static_cast<const T*>(header_); + } + + // The payload is the pickle data immediately following the header. + size_t payload_size() const { return header_->payload_size; } + const char* payload() const { + return reinterpret_cast<const char*>(header_) + header_size_; + } + + protected: + char* payload() { + return reinterpret_cast<char*>(header_) + header_size_; + } + + // Returns the address of the byte immediately following the currently valid + // header + payload. + char* end_of_payload() { + // We must have a valid header_. + return payload() + payload_size(); + } + const char* end_of_payload() const { + // This object may be invalid. + return header_ ? payload() + payload_size() : NULL; + } + + size_t capacity() const { + return capacity_; + } + + // 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); + + // Aligns 'i' by rounding it up to the next multiple of 'alignment' + static size_t AlignInt(size_t i, int alignment) { + return i + (alignment - (i % alignment)) % alignment; + } + + // Find the end of the pickled data that starts at range_start. Returns NULL + // if the entire Pickle is not found in the given data range. + static const char* FindNext(size_t header_size, + const char* range_start, + const char* range_end); + + // The allocation granularity of the payload. + static const int kPayloadUnit; + + private: + friend class PickleIterator; + + 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. + + FRIEND_TEST_ALL_PREFIXES(PickleTest, Resize); + FRIEND_TEST_ALL_PREFIXES(PickleTest, FindNext); + FRIEND_TEST_ALL_PREFIXES(PickleTest, FindNextWithIncompleteHeader); +}; + +#endif // BASE_PICKLE_H__
diff --git a/src/base/pickle_unittest.cc b/src/base/pickle_unittest.cc new file mode 100644 index 0000000..c866acd --- /dev/null +++ b/src/base/pickle_unittest.cc
@@ -0,0 +1,342 @@ +// 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 <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/pickle.h" +#include "base/string16.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const int testint = 2093847192; +const std::string teststr("Hello world"); // note non-aligned string length +const std::wstring testwstr(L"Hello, world"); +const char testdata[] = "AAA\0BBB\0"; +const int testdatalen = arraysize(testdata) - 1; +const bool testbool1 = false; +const bool testbool2 = true; +const uint16 testuint16 = 32123; +const float testfloat = 3.1415926935f; + +// checks that the result +void VerifyResult(const Pickle& pickle) { + PickleIterator iter(pickle); + + int outint; + EXPECT_TRUE(pickle.ReadInt(&iter, &outint)); + EXPECT_EQ(testint, outint); + + std::string outstr; + EXPECT_TRUE(pickle.ReadString(&iter, &outstr)); + EXPECT_EQ(teststr, outstr); + + std::wstring outwstr; + EXPECT_TRUE(pickle.ReadWString(&iter, &outwstr)); + EXPECT_EQ(testwstr, outwstr); + + bool outbool; + EXPECT_TRUE(pickle.ReadBool(&iter, &outbool)); + EXPECT_EQ(testbool1, outbool); + EXPECT_TRUE(pickle.ReadBool(&iter, &outbool)); + EXPECT_EQ(testbool2, outbool); + + uint16 outuint16; + EXPECT_TRUE(pickle.ReadUInt16(&iter, &outuint16)); + EXPECT_EQ(testuint16, outuint16); + + float outfloat; + EXPECT_TRUE(pickle.ReadFloat(&iter, &outfloat)); + EXPECT_EQ(testfloat, outfloat); + + const char* outdata; + int outdatalen; + EXPECT_TRUE(pickle.ReadData(&iter, &outdata, &outdatalen)); + 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)); +} + +} // namespace + +TEST(PickleTest, EncodeDecode) { + Pickle pickle; + + EXPECT_TRUE(pickle.WriteInt(testint)); + EXPECT_TRUE(pickle.WriteString(teststr)); + EXPECT_TRUE(pickle.WriteWString(testwstr)); + EXPECT_TRUE(pickle.WriteBool(testbool1)); + EXPECT_TRUE(pickle.WriteBool(testbool2)); + 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 + Pickle pickle2(pickle); + VerifyResult(pickle2); + + // test operator= + Pickle pickle3; + pickle3 = pickle; + VerifyResult(pickle3); +} + +// Tests that we can handle really small buffers. +TEST(PickleTest, SmallBuffer) { + scoped_array<char> buffer(new char[1]); + + // We should not touch the buffer. + Pickle pickle(buffer.get(), 1); + + PickleIterator iter(pickle); + int data; + EXPECT_FALSE(pickle.ReadInt(&iter, &data)); +} + +// Tests that we can handle improper headers. +TEST(PickleTest, BigSize) { + int buffer[] = { 0x56035200, 25, 40, 50 }; + + Pickle pickle(reinterpret_cast<char*>(buffer), sizeof(buffer)); + + PickleIterator iter(pickle); + int data; + EXPECT_FALSE(pickle.ReadInt(&iter, &data)); +} + +TEST(PickleTest, UnalignedSize) { + int buffer[] = { 10, 25, 40, 50 }; + + Pickle pickle(reinterpret_cast<char*>(buffer), sizeof(buffer)); + + PickleIterator iter(pickle); + int data; + EXPECT_FALSE(pickle.ReadInt(&iter, &data)); +} + +TEST(PickleTest, ZeroLenStr) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteString("")); + + PickleIterator iter(pickle); + std::string outstr; + EXPECT_TRUE(pickle.ReadString(&iter, &outstr)); + EXPECT_EQ("", outstr); +} + +TEST(PickleTest, ZeroLenWStr) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteWString(L"")); + + PickleIterator iter(pickle); + std::string outstr; + EXPECT_TRUE(pickle.ReadString(&iter, &outstr)); + EXPECT_EQ("", outstr); +} + +TEST(PickleTest, BadLenStr) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteInt(-2)); + + PickleIterator iter(pickle); + std::string outstr; + EXPECT_FALSE(pickle.ReadString(&iter, &outstr)); +} + +TEST(PickleTest, BadLenWStr) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteInt(-1)); + + PickleIterator iter(pickle); + std::wstring woutstr; + EXPECT_FALSE(pickle.ReadWString(&iter, &woutstr)); +} + +TEST(PickleTest, FindNext) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteInt(1)); + EXPECT_TRUE(pickle.WriteString("Domo")); + + const char* start = reinterpret_cast<const char*>(pickle.data()); + const char* end = start + pickle.size(); + + EXPECT_TRUE(end == Pickle::FindNext(pickle.header_size_, start, end)); + EXPECT_TRUE(NULL == Pickle::FindNext(pickle.header_size_, start, end - 1)); + EXPECT_TRUE(end == Pickle::FindNext(pickle.header_size_, start, end + 1)); +} + +TEST(PickleTest, FindNextWithIncompleteHeader) { + size_t header_size = sizeof(Pickle::Header); + scoped_array<char> buffer(new char[header_size - 1]); + memset(buffer.get(), 0x1, header_size - 1); + + const char* start = buffer.get(); + const char* end = start + header_size - 1; + + EXPECT_TRUE(NULL == Pickle::FindNext(header_size, start, end)); +} + +TEST(PickleTest, GetReadPointerAndAdvance) { + Pickle pickle; + + PickleIterator iter(pickle); + EXPECT_FALSE(iter.GetReadPointerAndAdvance(1)); + + EXPECT_TRUE(pickle.WriteInt(1)); + EXPECT_TRUE(pickle.WriteInt(2)); + int bytes = sizeof(int) * 2; + + EXPECT_TRUE(PickleIterator(pickle).GetReadPointerAndAdvance(0)); + EXPECT_TRUE(PickleIterator(pickle).GetReadPointerAndAdvance(1)); + EXPECT_FALSE(PickleIterator(pickle).GetReadPointerAndAdvance(-1)); + EXPECT_TRUE(PickleIterator(pickle).GetReadPointerAndAdvance(bytes)); + EXPECT_FALSE(PickleIterator(pickle).GetReadPointerAndAdvance(bytes + 1)); + EXPECT_FALSE(PickleIterator(pickle).GetReadPointerAndAdvance(INT_MAX)); + EXPECT_FALSE(PickleIterator(pickle).GetReadPointerAndAdvance(INT_MIN)); +} + +TEST(PickleTest, Resize) { + size_t unit = Pickle::kPayloadUnit; + scoped_array<char> data(new char[unit]); + char* data_ptr = data.get(); + for (size_t i = 0; i < unit; i++) + data_ptr[i] = 'G'; + + // construct a message that will be exactly the size of one payload unit, + // note that any data will have a 4-byte header indicating the size + const size_t payload_size_after_header = unit - sizeof(uint32); + Pickle pickle; + pickle.WriteData(data_ptr, + static_cast<int>(payload_size_after_header - sizeof(uint32))); + size_t cur_payload = payload_size_after_header; + + // note: we assume 'unit' is a power of 2 + EXPECT_EQ(unit, pickle.capacity()); + 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(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(cur_payload, pickle.payload_size()); +} + +namespace { + +struct CustomHeader : Pickle::Header { + int blah; +}; + +} // namespace + +TEST(PickleTest, HeaderPadding) { + const uint32 kMagic = 0x12345678; + + Pickle pickle(sizeof(CustomHeader)); + pickle.WriteInt(kMagic); + + // this should not overwrite the 'int' payload + pickle.headerT<CustomHeader>()->blah = 10; + + PickleIterator iter(pickle); + int result; + ASSERT_TRUE(pickle.ReadInt(&iter, &result)); + + EXPECT_EQ(static_cast<uint32>(result), kMagic); +} + +TEST(PickleTest, EqualsOperator) { + Pickle source; + source.WriteInt(1); + + Pickle copy_refs_source_buffer(static_cast<const char*>(source.data()), + source.size()); + Pickle copy; + copy = copy_refs_source_buffer; + ASSERT_EQ(source.size(), copy.size()); +} + +TEST(PickleTest, EvilLengths) { + Pickle source; + std::string str(100000, 'A'); + EXPECT_TRUE(source.WriteData(str.c_str(), 100000)); + // ReadString16 used to have its read buffer length calculation wrong leading + // to out-of-bounds reading. + PickleIterator iter(source); + string16 str16; + EXPECT_FALSE(source.ReadString16(&iter, &str16)); + + // And check we didn't break ReadString16. + str16 = (wchar_t) 'A'; + Pickle str16_pickle; + EXPECT_TRUE(str16_pickle.WriteString16(str16)); + iter = PickleIterator(str16_pickle); + EXPECT_TRUE(str16_pickle.ReadString16(&iter, &str16)); + EXPECT_EQ(1U, str16.length()); + + // Check we don't fail in a length check with invalid String16 size. + // (1<<31) * sizeof(char16) == 0, so this is particularly evil. + Pickle bad_len; + EXPECT_TRUE(bad_len.WriteInt(1 << 31)); + iter = PickleIterator(bad_len); + EXPECT_FALSE(bad_len.ReadString16(&iter, &str16)); + + // Check we don't fail in a length check with large WStrings. + Pickle big_len; + EXPECT_TRUE(big_len.WriteInt(1 << 30)); + iter = PickleIterator(big_len); + std::wstring wstr; + EXPECT_FALSE(big_len.ReadWString(&iter, &wstr)); +} + +// Check we can write zero bytes of data and 'data' can be NULL. +TEST(PickleTest, ZeroLength) { + Pickle pickle; + EXPECT_TRUE(pickle.WriteData(NULL, 0)); + + PickleIterator iter(pickle); + const char* outdata; + int outdatalen; + EXPECT_TRUE(pickle.ReadData(&iter, &outdata, &outdatalen)); + EXPECT_EQ(0, outdatalen); + // We can't assert that outdata is NULL. +} + +// Check that ReadBytes works properly with an iterator initialized to NULL. +TEST(PickleTest, ReadBytes) { + Pickle pickle; + int data = 0x7abcd; + EXPECT_TRUE(pickle.WriteBytes(&data, sizeof(data))); + + PickleIterator iter(pickle); + const char* outdata_char = NULL; + EXPECT_TRUE(pickle.ReadBytes(&iter, &outdata_char, sizeof(data))); + + int outdata; + memcpy(&outdata, outdata_char, sizeof(outdata)); + EXPECT_EQ(data, outdata); +}
diff --git a/src/base/platform_file.cc b/src/base/platform_file.cc new file mode 100644 index 0000000..aa113d3 --- /dev/null +++ b/src/base/platform_file.cc
@@ -0,0 +1,29 @@ +// 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/platform_file.h" + +namespace base { + +PlatformFileInfo::PlatformFileInfo() + : size(0), + is_directory(false), + is_symbolic_link(false) { +} + +PlatformFileInfo::~PlatformFileInfo() {} + +PlatformFile CreatePlatformFile(const FilePath& name, + int flags, + bool* created, + PlatformFileError* error) { + if (name.ReferencesParent()) { + if (error) + *error = PLATFORM_FILE_ERROR_ACCESS_DENIED; + return kInvalidPlatformFileValue; + } + return CreatePlatformFileUnsafe(name, flags, created, error); +} + +} // namespace base
diff --git a/src/base/platform_file.h b/src/base/platform_file.h new file mode 100644 index 0000000..c3d3ef1 --- /dev/null +++ b/src/base/platform_file.h
@@ -0,0 +1,251 @@ +// 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_PLATFORM_FILE_H_ +#define BASE_PLATFORM_FILE_H_ + +#include "build/build_config.h" +#if defined(OS_WIN) +#include <windows.h> +#endif + +#if defined(OS_STARBOARD) +#include "starboard/file.h" +#endif + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/time.h" + +namespace base { + +#if defined(OS_WIN) +typedef HANDLE PlatformFile; +const PlatformFile kInvalidPlatformFileValue = INVALID_HANDLE_VALUE; +#elif defined(OS_POSIX) +typedef int PlatformFile; +const PlatformFile kInvalidPlatformFileValue = -1; +#elif defined(OS_STARBOARD) +typedef SbFile PlatformFile; +const PlatformFile kInvalidPlatformFileValue = kSbFileInvalid; +#endif + +// 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. +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 + // does not already exist. + PLATFORM_FILE_OPEN_ALWAYS = 1 << 2, // May create a new file. + PLATFORM_FILE_CREATE_ALWAYS = 1 << 3, // May overwrite an old file. + PLATFORM_FILE_OPEN_TRUNCATED = 1 << 4, // Opens a file and truncates it, + // only if it exists. + PLATFORM_FILE_READ = 1 << 5, + PLATFORM_FILE_WRITE = 1 << 6, + PLATFORM_FILE_EXCLUSIVE_READ = 1 << 7, // EXCLUSIVE is opposite of Windows + // SHARE + PLATFORM_FILE_EXCLUSIVE_WRITE = 1 << 8, + PLATFORM_FILE_ASYNC = 1 << 9, + PLATFORM_FILE_TEMPORARY = 1 << 10, // Used on Windows only + PLATFORM_FILE_HIDDEN = 1 << 11, // Used on Windows only +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) + // TODO: Increase scope of elimination for Starboard. + PLATFORM_FILE_DELETE_ON_CLOSE = 1 << 12, +#endif + PLATFORM_FILE_WRITE_ATTRIBUTES = 1 << 13, // Used on Windows only + PLATFORM_FILE_ENUMERATE = 1 << 14, // May enumerate directory + + PLATFORM_FILE_SHARE_DELETE = 1 << 15, // 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_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. +enum PlatformFileError { + PLATFORM_FILE_OK = 0, + PLATFORM_FILE_ERROR_FAILED = -1, + PLATFORM_FILE_ERROR_IN_USE = -2, + PLATFORM_FILE_ERROR_EXISTS = -3, + PLATFORM_FILE_ERROR_NOT_FOUND = -4, + PLATFORM_FILE_ERROR_ACCESS_DENIED = -5, + PLATFORM_FILE_ERROR_TOO_MANY_OPENED = -6, + PLATFORM_FILE_ERROR_NO_MEMORY = -7, + PLATFORM_FILE_ERROR_NO_SPACE = -8, + PLATFORM_FILE_ERROR_NOT_A_DIRECTORY = -9, + PLATFORM_FILE_ERROR_INVALID_OPERATION = -10, + PLATFORM_FILE_ERROR_SECURITY = -11, + PLATFORM_FILE_ERROR_ABORT = -12, + PLATFORM_FILE_ERROR_NOT_A_FILE = -13, + PLATFORM_FILE_ERROR_NOT_EMPTY = -14, + PLATFORM_FILE_ERROR_INVALID_URL = -15, + // Put new entries here and increment PLATFORM_FILE_ERROR_MAX. + PLATFORM_FILE_ERROR_MAX = -16 +}; + +// This explicit mapping matches both FILE_ on Windows and SEEK_ on Linux. +enum PlatformFileWhence { + PLATFORM_FILE_FROM_BEGIN = 0, + PLATFORM_FILE_FROM_CURRENT = 1, + PLATFORM_FILE_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 PlatformFileInfo { + PlatformFileInfo(); + ~PlatformFileInfo(); + + // 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; +}; + +// Creates or opens the given file. If |created| is provided, it will be set to +// true if a new file was created [or an old one truncated to zero length to +// simulate a new file, which can happen with PLATFORM_FILE_CREATE_ALWAYS], and +// false otherwise. |error| can be NULL. +// +// This function fails with 'access denied' if the |name| contains path +// traversal ('..') components. +BASE_EXPORT PlatformFile CreatePlatformFile(const FilePath& name, + int flags, + bool* created, + PlatformFileError* error); + +// Same as CreatePlatformFile but allows paths with traversal (like \..\) +// components. Use only with extreme care. +BASE_EXPORT PlatformFile CreatePlatformFileUnsafe(const FilePath& name, + int flags, + bool* created, + PlatformFileError* error); + +// Closes a file handle. Returns |true| on success and |false| otherwise. +BASE_EXPORT bool ClosePlatformFile(PlatformFile file); + +// 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. +BASE_EXPORT int64 SeekPlatformFile(PlatformFile file, + PlatformFileWhence 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. +BASE_EXPORT int ReadPlatformFile(PlatformFile file, int64 offset, + char* data, int size); + +// Same as above but without seek. +BASE_EXPORT int ReadPlatformFileAtCurrentPos(PlatformFile file, + 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. +BASE_EXPORT int ReadPlatformFileNoBestEffort(PlatformFile file, int64 offset, + char* data, int size); + +// Same as above but without seek. +BASE_EXPORT int ReadPlatformFileCurPosNoBestEffort(PlatformFile file, + 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. +BASE_EXPORT int WritePlatformFile(PlatformFile file, int64 offset, + const char* data, int size); + +// Save as above but without seek. +BASE_EXPORT int WritePlatformFileAtCurrentPos(PlatformFile file, + 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. +BASE_EXPORT int WritePlatformFileCurPosNoBestEffort(PlatformFile file, + const char* data, int size); + +// Truncates the given 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. +BASE_EXPORT bool TruncatePlatformFile(PlatformFile file, int64 length); + +// Flushes the buffers of the given file. +BASE_EXPORT bool FlushPlatformFile(PlatformFile file); + +// Touches the given file. +BASE_EXPORT bool TouchPlatformFile(PlatformFile file, + const Time& last_access_time, + const Time& last_modified_time); + +// Returns some information for the given file. +BASE_EXPORT bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info); + +// 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. +// +// EXAMPLE: +// +// void MaybeProcessFile(PassPlatformFile pass_file) { +// if (...) { +// PlatformFile file = pass_file.ReleaseValue(); +// // Now, we are responsible for closing |file|. +// } +// } +// +// void OpenAndMaybeProcessFile(const FilePath& path) { +// PlatformFile file = CreatePlatformFile(path, ...); +// MaybeProcessFile(PassPlatformFile(&file)); +// if (file != kInvalidPlatformFileValue) +// ClosePlatformFile(file); +// } +// +class BASE_EXPORT PassPlatformFile { + public: + explicit PassPlatformFile(PlatformFile* value) : value_(value) { + } + + // Called to retrieve the PlatformFile stored in this object. The caller + // gains ownership of the PlatformFile and is now responsible for closing it. + // Any subsequent calls to this method will return an invalid PlatformFile. + PlatformFile ReleaseValue() { + PlatformFile temp = *value_; + *value_ = kInvalidPlatformFileValue; + return temp; + } + + private: + PlatformFile* value_; +}; + +} // namespace base + +#endif // BASE_PLATFORM_FILE_H_
diff --git a/src/base/platform_file_posix.cc b/src/base/platform_file_posix.cc new file mode 100644 index 0000000..1b0b5a7 --- /dev/null +++ b/src/base/platform_file_posix.cc
@@ -0,0 +1,348 @@ +// 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/platform_file.h" + +#include <fcntl.h> +#include <errno.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/threading/thread_restrictions.h" +#include "base/utf_string_conversions.h" + +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) +#include "base/os_compat_android.h" +#endif + +namespace base { + +// Make sure our Whence mappings match the system headers. +COMPILE_ASSERT(PLATFORM_FILE_FROM_BEGIN == SEEK_SET && + PLATFORM_FILE_FROM_CURRENT == SEEK_CUR && + PLATFORM_FILE_FROM_END == SEEK_END, whence_matches_system); + +#if defined(OS_BSD) || defined(OS_MACOSX) || defined(__LB_WIIU__) || \ + defined(__LB_PS3__) || defined(__LB_XB1__) || defined(__LB_PS4__) || \ + defined(__LB_XB360__) +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 + +// TODO(erikkay): does it make sense to support PLATFORM_FILE_EXCLUSIVE_* here? +PlatformFile CreatePlatformFileUnsafe(const FilePath& name, + int flags, + bool* created, + PlatformFileError* error) { + base::ThreadRestrictions::AssertIOAllowed(); + + int open_flags = 0; + if (flags & PLATFORM_FILE_CREATE) + open_flags = O_CREAT | O_EXCL; + + if (created) + *created = false; + + if (flags & PLATFORM_FILE_CREATE_ALWAYS) { + DCHECK(!open_flags); + open_flags = O_CREAT | O_TRUNC; + } + + if (flags & PLATFORM_FILE_OPEN_TRUNCATED) { + DCHECK(!open_flags); + DCHECK(flags & PLATFORM_FILE_WRITE); + open_flags = O_TRUNC; + } + + if (!open_flags && !(flags & PLATFORM_FILE_OPEN) && + !(flags & PLATFORM_FILE_OPEN_ALWAYS)) { + NOTREACHED(); + errno = EOPNOTSUPP; + if (error) + *error = PLATFORM_FILE_ERROR_FAILED; + return kInvalidPlatformFileValue; + } + + if (flags & PLATFORM_FILE_WRITE && flags & PLATFORM_FILE_READ) { + open_flags |= O_RDWR; + } else if (flags & PLATFORM_FILE_WRITE) { + open_flags |= O_WRONLY; + } else if (!(flags & PLATFORM_FILE_READ) && + !(flags & PLATFORM_FILE_WRITE_ATTRIBUTES) && + !(flags & PLATFORM_FILE_OPEN_ALWAYS)) { + NOTREACHED(); + } + +#if defined(__LB_XB1__) || defined(__LB_XB360__) + open_flags |= O_BINARY; +#else + if (flags & PLATFORM_FILE_TERMINAL_DEVICE) + open_flags |= O_NOCTTY | O_NDELAY; +#endif + + 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 & PLATFORM_FILE_OPEN_ALWAYS) { + if (descriptor < 0) { + open_flags |= O_CREAT; + if (flags & PLATFORM_FILE_EXCLUSIVE_READ || + flags & PLATFORM_FILE_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 (created && descriptor >= 0) + *created = true; + } + } + + if (created && (descriptor >= 0) && + (flags & (PLATFORM_FILE_CREATE_ALWAYS | PLATFORM_FILE_CREATE))) + *created = true; + +#if !defined (__LB_SHELL__) + if ((descriptor >= 0) && (flags & PLATFORM_FILE_DELETE_ON_CLOSE)) { + unlink(name.value().c_str()); + } +#endif + + if (error) { + if (descriptor >= 0) + *error = PLATFORM_FILE_OK; + else { + switch (errno) { + case EACCES: + case EISDIR: + case EROFS: + case EPERM: + *error = PLATFORM_FILE_ERROR_ACCESS_DENIED; + break; + case ETXTBSY: + *error = PLATFORM_FILE_ERROR_IN_USE; + break; + case EEXIST: + *error = PLATFORM_FILE_ERROR_EXISTS; + break; + case ENOENT: + *error = PLATFORM_FILE_ERROR_NOT_FOUND; + break; + case EMFILE: + *error = PLATFORM_FILE_ERROR_TOO_MANY_OPENED; + break; + case ENOMEM: + *error = PLATFORM_FILE_ERROR_NO_MEMORY; + break; + case ENOSPC: + *error = PLATFORM_FILE_ERROR_NO_SPACE; + break; + case ENOTDIR: + *error = PLATFORM_FILE_ERROR_NOT_A_DIRECTORY; + break; + default: + *error = PLATFORM_FILE_ERROR_FAILED; + } + } + } + + return descriptor; +} + +bool ClosePlatformFile(PlatformFile file) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0) { + errno = EINVAL; + return false; + } + return !HANDLE_EINTR(close(file)); +} + +int64 SeekPlatformFile(PlatformFile file, + PlatformFileWhence whence, + int64 offset) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0 || offset < 0) + return -1; + + return lseek(file, static_cast<off_t>(offset), static_cast<int>(whence)); +} + +#if !defined(__LB_PS3__) +int ReadPlatformFile(PlatformFile file, int64 offset, char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0 || 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; +} +#endif + +int ReadPlatformFileAtCurrentPos(PlatformFile file, char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0 || 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; +} + +#if !defined(__LB_PS3__) +int ReadPlatformFileNoBestEffort(PlatformFile file, int64 offset, + char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0) + return -1; + + return HANDLE_EINTR(pread(file, data, size, offset)); +} +#endif + +int ReadPlatformFileCurPosNoBestEffort(PlatformFile file, + char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0 || size < 0) + return -1; + + return HANDLE_EINTR(read(file, data, size)); +} + +#if !defined(__LB_PS3__) +int WritePlatformFile(PlatformFile file, int64 offset, + const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0 || 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; +} +#endif + +int WritePlatformFileAtCurrentPos(PlatformFile file, + const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0 || 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 WritePlatformFileCurPosNoBestEffort(PlatformFile file, + const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0 || size < 0) + return -1; + + return HANDLE_EINTR(write(file, data, size)); +} + +bool TruncatePlatformFile(PlatformFile file, int64 length) { + base::ThreadRestrictions::AssertIOAllowed(); +#if defined(__LB_PS3__) + // On PS3, when writing specifically to the temp partition, you must sync + // file contents to disk after truncating if you want the truncation + // reflected in subsequent file operations. + return ((file >= 0) && !HANDLE_EINTR(ftruncate(file, length)) && !fsync(file)); +#else + return ((file >= 0) && !HANDLE_EINTR(ftruncate(file, length))); +#endif +} + +bool FlushPlatformFile(PlatformFile file) { + base::ThreadRestrictions::AssertIOAllowed(); + return !HANDLE_EINTR(fsync(file)); +} + +#if !defined(__LB_WIIU__) && !defined(__LB_PS3__) && !defined(__LB_XB1__) && \ + !defined(__LB_XB360__) +bool TouchPlatformFile(PlatformFile file, const base::Time& last_access_time, + const base::Time& last_modified_time) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0) + return false; + + timeval times[2]; + times[0] = last_access_time.ToTimeVal(); + times[1] = last_modified_time.ToTimeVal(); + return !futimes(file, times); +} +#endif + +bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) { + if (!info || file < 0) + return false; + + 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; + info->last_modified = base::Time::FromTimeT(file_info.st_mtime); + info->last_accessed = base::Time::FromTimeT(file_info.st_atime); + info->creation_time = base::Time::FromTimeT(file_info.st_ctime); + return true; +} + +} // namespace base
diff --git a/src/base/platform_file_starboard.cc b/src/base/platform_file_starboard.cc new file mode 100644 index 0000000..c28fd65 --- /dev/null +++ b/src/base/platform_file_starboard.cc
@@ -0,0 +1,309 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Adapted from platform_file_posix.cc + +#include "base/platform_file.h" + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" +#include "starboard/file.h" + +namespace base { + +// Make sure our Whence mappings match the system headers. +COMPILE_ASSERT( + PLATFORM_FILE_FROM_BEGIN == static_cast<int>(kSbFileFromBegin) && + PLATFORM_FILE_FROM_CURRENT == static_cast<int>(kSbFileFromCurrent) && + PLATFORM_FILE_FROM_END == static_cast<int>(kSbFileFromEnd), + whence_matches_system); + +PlatformFile CreatePlatformFileUnsafe(const FilePath& name, + int flags, + bool *created, + PlatformFileError* error) { + base::ThreadRestrictions::AssertIOAllowed(); + + int open_flags = 0; + + switch (flags & (PLATFORM_FILE_OPEN | PLATFORM_FILE_CREATE | + PLATFORM_FILE_OPEN_ALWAYS | PLATFORM_FILE_CREATE_ALWAYS | + PLATFORM_FILE_OPEN_TRUNCATED)) { + case PLATFORM_FILE_OPEN: + open_flags = kSbFileOpenOnly; + break; + + case PLATFORM_FILE_CREATE: + open_flags = kSbFileCreateOnly; + break; + + case PLATFORM_FILE_OPEN_ALWAYS: + open_flags = kSbFileOpenAlways; + break; + + case PLATFORM_FILE_CREATE_ALWAYS: + open_flags = kSbFileCreateAlways; + break; + + case PLATFORM_FILE_OPEN_TRUNCATED: + open_flags = kSbFileOpenTruncated; + DCHECK(flags & PLATFORM_FILE_WRITE); + break; + + default: + NOTREACHED() << "Passed incompatible flags: " << flags; + if (error) { + *error = PLATFORM_FILE_ERROR_FAILED; + } + return kInvalidPlatformFileValue; + } + + DCHECK(flags & PLATFORM_FILE_WRITE || flags & PLATFORM_FILE_READ); + + if (flags & PLATFORM_FILE_READ) { + open_flags |= kSbFileRead; + } + + if (flags & PLATFORM_FILE_WRITE) { + open_flags |= kSbFileWrite; + } + + SbFileError sb_error; + SbFile file = SbFileOpen(name.value().c_str(), open_flags, created, + &sb_error); + + if (error) { + if (SbFileIsValid(file)) { + *error = PLATFORM_FILE_OK; + } else { + switch (sb_error) { + case kSbFileErrorFailed: + case kSbFileErrorInUse: + case kSbFileErrorExists: + case kSbFileErrorNotFound: + case kSbFileErrorAccessDenied: + case kSbFileErrorTooManyOpened: + case kSbFileErrorNoMemory: + case kSbFileErrorNoSpace: + case kSbFileErrorNotADirectory: + case kSbFileErrorInvalidOperation: + case kSbFileErrorSecurity: + case kSbFileErrorAbort: + case kSbFileErrorNotAFile: + case kSbFileErrorNotEmpty: + case kSbFileErrorInvalidUrl: + // Starboard error codes are designed to match Chromium's exactly. + *error = static_cast<PlatformFileError>(sb_error); + break; + default: + NOTREACHED() << "Unrecognized SbFileError: " << sb_error; + break; + } + } + } + + return file; +} + +bool ClosePlatformFile(PlatformFile file) { + base::ThreadRestrictions::AssertIOAllowed(); + return SbFileClose(file); +} + +int64 SeekPlatformFile(PlatformFile file, + PlatformFileWhence whence, + int64 offset) { + base::ThreadRestrictions::AssertIOAllowed(); + return SbFileSeek(file, static_cast<SbFileWhence>(whence), offset); +} + +int ReadPlatformFile(PlatformFile file, int64 offset, char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (size < 0) { + return -1; + } + + int original_position = SbFileSeek(file, kSbFileFromCurrent, 0); + if (original_position < 0) { + return -1; + } + + int position = SbFileSeek(file, kSbFileFromBegin, offset); + int result = 0; + if (position == offset) { + result = ReadPlatformFileAtCurrentPos(file, data, size); + } + + // Restore position regardless of result of write. + position = SbFileSeek(file, kSbFileFromBegin, original_position); + if (result < 0) { + return result; + } + + if (position < 0) { + return -1; + } + + return result; +} + +int ReadPlatformFileAtCurrentPos(PlatformFile file, char *data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0 || size < 0) + return -1; + + int bytes_read = 0; + int rv; + do { + rv = SbFileRead(file, data, size); + if (rv <= 0) { + break; + } + + bytes_read += rv; + } while (bytes_read < size); + + return bytes_read ? bytes_read : rv; +} + +int ReadPlatformFileNoBestEffort(PlatformFile file, + int64 offset, + char *data, + int size) { + base::ThreadRestrictions::AssertIOAllowed(); + int original_position = SbFileSeek(file, kSbFileFromCurrent, 0); + if (original_position < 0) { + return -1; + } + + int position = SbFileSeek(file, kSbFileFromBegin, offset); + int result = 0; + if (position == offset) { + result = SbFileRead(file, data, size); + } + + // Restore position regardless of result of read. + position = SbFileSeek(file, kSbFileFromBegin, original_position); + if (result < 0) { + return result; + } + + if (position < 0) { + return -1; + } + + return result; +} + +int ReadPlatformFileCurPosNoBestEffort(PlatformFile file, + char *data, + int size) { + base::ThreadRestrictions::AssertIOAllowed(); + return SbFileRead(file, data, size); +} + +int WritePlatformFile(PlatformFile file, + int64 offset, + const char *data, + int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (size < 0) { + return -1; + } + + int original_position = SbFileSeek(file, kSbFileFromCurrent, 0); + if (original_position < 0) { + return -1; + } + + int position = SbFileSeek(file, kSbFileFromBegin, offset); + int result = 0; + if (position == offset) { + result = WritePlatformFileAtCurrentPos(file, data, size); + } + + // Restore position regardless of result of write. + position = SbFileSeek(file, kSbFileFromBegin, original_position); + if (result < 0) { + return result; + } + + if (position < 0) { + return -1; + } + + return result; +} + +int WritePlatformFileAtCurrentPos(PlatformFile file, + const char* data, + int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (size < 0) { + return -1; + } + + int bytes_written = 0; + int result; + do { + result = SbFileWrite(file, data, size); + if (result <= 0) { + break; + } + + bytes_written += result; + } while (bytes_written < size); + + return bytes_written ? bytes_written : result; +} + +int WritePlatformFileCurPosNoBestEffort(PlatformFile file, + const char *data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (size < 0) { + return -1; + } + + return SbFileWrite(file, data, size); +} + +bool TruncatePlatformFile(PlatformFile file, int64 length) { + base::ThreadRestrictions::AssertIOAllowed(); + return SbFileTruncate(file, length); +} + +bool FlushPlatformFile(PlatformFile file) { + base::ThreadRestrictions::AssertIOAllowed(); + return SbFileFlush(file); +} + +bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo *info) { + if (!info || file < 0) + return false; + + SbFileInfo file_info; + if (!SbFileGetInfo(file, &file_info)) + return false; + + info->is_directory = file_info.is_directory; + info->is_symbolic_link = file_info.is_symbolic_link; + info->size = file_info.size; + info->last_modified = base::Time::FromSbTime(file_info.last_modified); + info->last_accessed = base::Time::FromSbTime(file_info.last_accessed); + info->creation_time = base::Time::FromSbTime(file_info.creation_time); + return true; +} + +} // namespace base
diff --git a/src/base/platform_file_unittest.cc b/src/base/platform_file_unittest.cc new file mode 100644 index 0000000..e73d930 --- /dev/null +++ b/src/base/platform_file_unittest.cc
@@ -0,0 +1,354 @@ +// 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/scoped_temp_dir.h" +#include "base/platform_file.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +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); +} + +// Writes the given number of bytes to a file. +// Returns the number of bytes written. +int WriteFully(base::PlatformFile file, int64 offset, + const char* data, int size) { + return base::WritePlatformFile(file, offset, data, size); +} + +} // namespace + +TEST(PlatformFile, CreatePlatformFile) { + 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. + base::PlatformFileError error_code = base::PLATFORM_FILE_OK; + base::PlatformFile file = base::CreatePlatformFile( + file_path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, + NULL, &error_code); + EXPECT_EQ(base::kInvalidPlatformFileValue, file); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, error_code); + + // Open or create a file. + bool created = false; + error_code = base::PLATFORM_FILE_OK; + file = base::CreatePlatformFile( + file_path, base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_READ, + &created, &error_code); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_TRUE(created); + EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + base::ClosePlatformFile(file); + + // Open an existing file. + created = false; + file = base::CreatePlatformFile( + file_path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, + &created, &error_code); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_FALSE(created); + EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + base::ClosePlatformFile(file); + + // Create a file that exists. + file = base::CreatePlatformFile( + file_path, base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_READ, + &created, &error_code); + EXPECT_EQ(base::kInvalidPlatformFileValue, file); + EXPECT_FALSE(created); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, error_code); + + // Create or overwrite a file. + error_code = base::PLATFORM_FILE_OK; + file = base::CreatePlatformFile( + file_path, base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_READ, + &created, &error_code); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_TRUE(created); + EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + base::ClosePlatformFile(file); + +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) + // Create a delete-on-close file. + created = false; + file_path = temp_dir.path().AppendASCII("create_file_2"); + file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_OPEN_ALWAYS | + base::PLATFORM_FILE_DELETE_ON_CLOSE | + base::PLATFORM_FILE_READ, + &created, &error_code); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_TRUE(created); + EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + + EXPECT_TRUE(base::ClosePlatformFile(file)); + EXPECT_FALSE(file_util::PathExists(file_path)); +#endif +} + +TEST(PlatformFile, DeleteOpenFile) { + base::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( + file_path, + base::PLATFORM_FILE_OPEN_ALWAYS | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_SHARE_DELETE, + &created, &error_code); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_TRUE(created); + EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) + // Open an existing file and mark it as delete on close. + created = false; + base::PlatformFile same_file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_DELETE_ON_CLOSE | + base::PLATFORM_FILE_READ, + &created, &error_code); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_FALSE(created); + EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + + // Close both handles and check that the file is gone. + base::ClosePlatformFile(file); + base::ClosePlatformFile(same_file); + EXPECT_FALSE(file_util::PathExists(file_path)); +#else + base::ClosePlatformFile(file); +#endif +} + +TEST(PlatformFile, ReadWritePlatformFile) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("read_write_file"); + base::PlatformFile file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_CREATE | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_WRITE, + NULL, NULL); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + + char data_to_write[] = "test"; + const int kTestDataSize = 4; + + // Write 0 bytes to the file. + int bytes_written = WriteFully(file, 0, data_to_write, 0); + EXPECT_EQ(0, bytes_written); + + // Write "test" to the file. + bytes_written = WriteFully(file, 0, data_to_write, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_written); + + // Read from EOF. + char data_read_1[32]; + int bytes_read = ReadFully(file, 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 = ReadFully(file, 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 = ReadFully(file, 0, data_read_1, 0); + EXPECT_EQ(0, bytes_read); + + // Read the entire file. + bytes_read = ReadFully(file, 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 = base::ReadPlatformFileNoBestEffort(file, 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 = WriteFully(file, kOffsetBeyondEndOfFile, + data_to_write, kPartialWriteLength); + EXPECT_EQ(kPartialWriteLength, bytes_written); + + // Make sure the file was extended. + int64 file_size = 0; +#if defined(__LB_SHELL__) + // At this point, the file buffer may not have been flushed to disk, + // so reading the new file size from disk is flaky. Instead, read the file + // size from the file descriptor using GetPlatformFileInfo. + base::PlatformFileInfo file_info; + EXPECT_TRUE(base::GetPlatformFileInfo(file, &file_info)); + file_size = file_info.size; +#else + EXPECT_TRUE(file_util::GetFileSize(file_path, &file_size)); +#endif + EXPECT_EQ(kOffsetBeyondEndOfFile + kPartialWriteLength, file_size); + + // Make sure the file was zero-padded. + char data_read_2[32]; + bytes_read = ReadFully(file, 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]); + + // Close the file handle to allow the temp directory to be deleted. + base::ClosePlatformFile(file); +} + +TEST(PlatformFile, TruncatePlatformFile) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("truncate_file"); + base::PlatformFile file = base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_CREATE | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_WRITE, + NULL, NULL); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + + // Write "test" to the file. + char data_to_write[] = "test"; + int kTestDataSize = 4; + int bytes_written = WriteFully(file, 0, data_to_write, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_written); + + // 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_EQ(kExtendedFileLength, file_size); + + // Make sure the file was zero-padded. + char data_read[32]; + int bytes_read = ReadFully(file, 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(base::TruncatePlatformFile(file, kTruncatedFileLength)); + EXPECT_TRUE(file_util::GetFileSize(file_path, &file_size)); + EXPECT_EQ(kTruncatedFileLength, file_size); + + // Make sure the file was truncated. + bytes_read = ReadFully(file, 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]); + + // Close the file handle to allow the temp directory to be deleted. + base::ClosePlatformFile(file); +} + +#if !defined(OS_STARBOARD) +// Flakily fails: http://crbug.com/86494 +TEST(PlatformFile, DISABLED_TouchGetInfoPlatformFile) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::PlatformFile file = base::CreatePlatformFile( + temp_dir.path().AppendASCII("touch_get_info_file"), + base::PLATFORM_FILE_CREATE | + base::PLATFORM_FILE_WRITE | + base::PLATFORM_FILE_WRITE_ATTRIBUTES, + NULL, NULL); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + + // Get info for a newly created file. + base::PlatformFileInfo info; + EXPECT_TRUE(base::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); + 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 = WriteFully(file, 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); + +#if !defined(__LB_PS3__) + // base::TouchPlatformFile() will never work on PS3 as long as PS3 does not + // implement futime(). + EXPECT_TRUE(base::TouchPlatformFile(file, new_last_accessed, + new_last_modified)); +#endif + + // Make sure the file info was updated accordingly. + EXPECT_TRUE(base::GetPlatformFileInfo(file, &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(__LB_PS3__) && !defined(__LB_WIIU__) +#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 +#endif + + EXPECT_EQ(info.creation_time.ToInternalValue(), + creation_time.ToInternalValue()); + + // Close the file handle to allow the temp directory to be deleted. + base::ClosePlatformFile(file); +} +#endif
diff --git a/src/base/platform_file_win.cc b/src/base/platform_file_win.cc new file mode 100644 index 0000000..7c72f9e --- /dev/null +++ b/src/base/platform_file_win.cc
@@ -0,0 +1,262 @@ +// 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/platform_file.h" + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" + +namespace base { +PlatformFile CreatePlatformFileUnsafe(const FilePath& name, + int flags, + bool* created, + PlatformFileError* error) { + base::ThreadRestrictions::AssertIOAllowed(); + + DWORD disposition = 0; + if (created) + *created = false; + + if (flags & PLATFORM_FILE_OPEN) + disposition = OPEN_EXISTING; + + if (flags & PLATFORM_FILE_CREATE) { + DCHECK(!disposition); + disposition = CREATE_NEW; + } + + if (flags & PLATFORM_FILE_OPEN_ALWAYS) { + DCHECK(!disposition); + disposition = OPEN_ALWAYS; + } + + if (flags & PLATFORM_FILE_CREATE_ALWAYS) { + DCHECK(!disposition); + disposition = CREATE_ALWAYS; + } + + if (flags & PLATFORM_FILE_OPEN_TRUNCATED) { + DCHECK(!disposition); + DCHECK(flags & PLATFORM_FILE_WRITE); + disposition = TRUNCATE_EXISTING; + } + + if (!disposition) { + NOTREACHED(); + return NULL; + } + + DWORD access = (flags & PLATFORM_FILE_READ) ? GENERIC_READ : 0; + if (flags & PLATFORM_FILE_WRITE) + access |= GENERIC_WRITE; + if (flags & PLATFORM_FILE_WRITE_ATTRIBUTES) + access |= FILE_WRITE_ATTRIBUTES; + + DWORD sharing = (flags & PLATFORM_FILE_EXCLUSIVE_READ) ? 0 : FILE_SHARE_READ; + if (!(flags & PLATFORM_FILE_EXCLUSIVE_WRITE)) + sharing |= FILE_SHARE_WRITE; + if (flags & PLATFORM_FILE_SHARE_DELETE) + sharing |= FILE_SHARE_DELETE; + + DWORD create_flags = 0; + if (flags & PLATFORM_FILE_ASYNC) + create_flags |= FILE_FLAG_OVERLAPPED; + if (flags & PLATFORM_FILE_TEMPORARY) + create_flags |= FILE_ATTRIBUTE_TEMPORARY; + if (flags & PLATFORM_FILE_HIDDEN) + create_flags |= FILE_ATTRIBUTE_HIDDEN; + if (flags & PLATFORM_FILE_DELETE_ON_CLOSE) + create_flags |= FILE_FLAG_DELETE_ON_CLOSE; + if (flags & PLATFORM_FILE_BACKUP_SEMANTICS) + create_flags |= FILE_FLAG_BACKUP_SEMANTICS; + + HANDLE file = CreateFile(name.value().c_str(), access, sharing, NULL, + disposition, create_flags, NULL); + + if (created && (INVALID_HANDLE_VALUE != file)) { + if (flags & (PLATFORM_FILE_OPEN_ALWAYS)) + *created = (ERROR_ALREADY_EXISTS != GetLastError()); + else if (flags & (PLATFORM_FILE_CREATE_ALWAYS | PLATFORM_FILE_CREATE)) + *created = true; + } + + if (error) { + if (file != kInvalidPlatformFileValue) + *error = PLATFORM_FILE_OK; + else { + DWORD last_error = GetLastError(); + switch (last_error) { + case ERROR_SHARING_VIOLATION: + *error = PLATFORM_FILE_ERROR_IN_USE; + break; + case ERROR_FILE_EXISTS: + *error = PLATFORM_FILE_ERROR_EXISTS; + break; + case ERROR_FILE_NOT_FOUND: + *error = PLATFORM_FILE_ERROR_NOT_FOUND; + break; + case ERROR_ACCESS_DENIED: + *error = PLATFORM_FILE_ERROR_ACCESS_DENIED; + break; + default: + *error = PLATFORM_FILE_ERROR_FAILED; + } + } + } + + return file; +} + +bool ClosePlatformFile(PlatformFile file) { + base::ThreadRestrictions::AssertIOAllowed(); + return (CloseHandle(file) != 0); +} + +int64 SeekPlatformFile(PlatformFile file, + PlatformFileWhence whence, + int64 offset) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file < 0 || 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 ReadPlatformFile(PlatformFile file, int64 offset, char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file == kInvalidPlatformFileValue) + 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; + else if (ERROR_HANDLE_EOF == GetLastError()) + return 0; + + return -1; +} + +int ReadPlatformFileAtCurrentPos(PlatformFile file, char* data, int size) { + return ReadPlatformFile(file, 0, data, size); +} + +int ReadPlatformFileNoBestEffort(PlatformFile file, int64 offset, char* data, + int size) { + return ReadPlatformFile(file, offset, data, size); +} + +int ReadPlatformFileCurPosNoBestEffort(PlatformFile file, + char* data, int size) { + return ReadPlatformFile(file, 0, data, size); +} + +int WritePlatformFile(PlatformFile file, int64 offset, + const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file == kInvalidPlatformFileValue) + 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_written; + if (::WriteFile(file, data, size, &bytes_written, &overlapped) != 0) + return bytes_written; + + return -1; +} + +int WritePlatformFileAtCurrentPos(PlatformFile file, const char* data, + int size) { + return WritePlatformFile(file, 0, data, size); +} + +int WritePlatformFileCurPosNoBestEffort(PlatformFile file, + const char* data, int size) { + return WritePlatformFile(file, 0, data, size); +} + +bool TruncatePlatformFile(PlatformFile file, int64 length) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file == kInvalidPlatformFileValue) + return false; + + // 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 FlushPlatformFile(PlatformFile file) { + base::ThreadRestrictions::AssertIOAllowed(); + return ((file != kInvalidPlatformFileValue) && ::FlushFileBuffers(file)); +} + +bool TouchPlatformFile(PlatformFile file, const base::Time& last_access_time, + const base::Time& last_modified_time) { + base::ThreadRestrictions::AssertIOAllowed(); + if (file == kInvalidPlatformFileValue) + return false; + + 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 GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) { + base::ThreadRestrictions::AssertIOAllowed(); + if (!info) + return false; + + 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; +} + +} // namespace base
diff --git a/src/base/port.h b/src/base/port.h new file mode 100644 index 0000000..af4e450 --- /dev/null +++ b/src/base/port.h
@@ -0,0 +1,54 @@ +// Copyright (c) 2006-2008 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_PORT_H_ +#define BASE_PORT_H_ + +#include <stdarg.h> +#include "build/build_config.h" + +#ifdef COMPILER_MSVC +#define GG_LONGLONG(x) x##I64 +#define GG_ULONGLONG(x) x##UI64 +#else +#define GG_LONGLONG(x) x##LL +#define GG_ULONGLONG(x) x##ULL +#endif + +// Per C99 7.8.14, define __STDC_CONSTANT_MACROS before including <stdint.h> +// to get the INTn_C and UINTn_C macros for integer constants. It's difficult +// to guarantee any specific ordering of header includes, so it's difficult to +// guarantee that the INTn_C macros can be defined by including <stdint.h> at +// any specific point. Provide GG_INTn_C macros instead. + +#define GG_INT8_C(x) (x) +#define GG_INT16_C(x) (x) +#define GG_INT32_C(x) (x) +#define GG_INT64_C(x) GG_LONGLONG(x) + +#define GG_UINT8_C(x) (x ## U) +#define GG_UINT16_C(x) (x ## U) +#define GG_UINT32_C(x) (x ## U) +#define GG_UINT64_C(x) GG_ULONGLONG(x) + +// It's possible for functions that use a va_list, such as StringPrintf, to +// invalidate the data in it upon use. The fix is to make a copy of the +// structure before using it and use that copy instead. va_copy is provided +// for this purpose. MSVC does not provide va_copy, so define an +// implementation here. It is not guaranteed that assignment is a copy, so the +// StringUtil.VariableArgsFunc unit test tests this capability. +#if defined(COMPILER_GCC) +#define GG_VA_COPY(a, b) (va_copy(a, b)) +#elif defined(COMPILER_MSVC) +#define GG_VA_COPY(a, b) (a = b) +#endif + +// Define an OS-neutral wrapper for shared library entry points +#if defined(OS_WIN) +#define API_CALL __stdcall +#else +#define API_CALL +#endif + +#endif // BASE_PORT_H_
diff --git a/src/base/posix/eintr_wrapper.h b/src/base/posix/eintr_wrapper.h new file mode 100644 index 0000000..689e74e --- /dev/null +++ b/src/base/posix/eintr_wrapper.h
@@ -0,0 +1,33 @@ +// 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. + +// This provides a wrapper around system calls which may be interrupted by a +// signal and return EINTR. See man 7 signal. +// +// On Windows, this wrapper macro does nothing. + +#ifndef BASE_POSIX_EINTR_WRAPPER_H_ +#define BASE_POSIX_EINTR_WRAPPER_H_ + +#include "build/build_config.h" + +#if defined(OS_POSIX) && !defined(COBALT_WIN) + +#include <errno.h> + +#define HANDLE_EINTR(x) ({ \ + typeof(x) __eintr_result__; \ + do { \ + __eintr_result__ = (x); \ + } while (__eintr_result__ == -1 && errno == EINTR); \ + __eintr_result__;\ +}) + +#else + +#define HANDLE_EINTR(x) (x) + +#endif // OS_POSIX + +#endif // BASE_POSIX_EINTR_WRAPPER_H_
diff --git a/src/base/posix/file_descriptor_shuffle.cc b/src/base/posix/file_descriptor_shuffle.cc new file mode 100644 index 0000000..b5b7339 --- /dev/null +++ b/src/base/posix/file_descriptor_shuffle.cc
@@ -0,0 +1,96 @@ +// 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/posix/file_descriptor_shuffle.h" + +#include <unistd.h> +#include <stddef.h> +#include <ostream> + +#include "base/posix/eintr_wrapper.h" +#include "base/logging.h" + +namespace base { + +bool PerformInjectiveMultimapDestructive( + InjectiveMultimap* m, InjectionDelegate* delegate) { + static const size_t kMaxExtraFDs = 16; + int extra_fds[kMaxExtraFDs]; + unsigned next_extra_fd = 0; + + // DANGER: this function may not allocate. + + for (InjectiveMultimap::iterator i = m->begin(); i != m->end(); ++i) { + int temp_fd = -1; + + // We DCHECK the injectiveness of the mapping. + for (InjectiveMultimap::iterator j = i + 1; j != m->end(); ++j) { + DCHECK(i->dest != j->dest) << "Both fd " << i->source + << " and " << j->source << " map to " << i->dest; + } + + const bool is_identity = i->source == i->dest; + + for (InjectiveMultimap::iterator j = i + 1; j != m->end(); ++j) { + if (!is_identity && i->dest == j->source) { + if (temp_fd == -1) { + if (!delegate->Duplicate(&temp_fd, i->dest)) + return false; + if (next_extra_fd < kMaxExtraFDs) { + extra_fds[next_extra_fd++] = temp_fd; + } else { + RAW_LOG(ERROR, "PerformInjectiveMultimapDestructive overflowed " + "extra_fds. Leaking file descriptors!"); + } + } + + j->source = temp_fd; + j->close = false; + } + + if (i->close && i->source == j->dest) + i->close = false; + + if (i->close && i->source == j->source) { + i->close = false; + j->close = true; + } + } + + if (!is_identity) { + if (!delegate->Move(i->source, i->dest)) + return false; + } + + if (!is_identity && i->close) + delegate->Close(i->source); + } + + for (unsigned i = 0; i < next_extra_fd; i++) + delegate->Close(extra_fds[i]); + + return true; +} + +bool PerformInjectiveMultimap(const InjectiveMultimap& m_in, + InjectionDelegate* delegate) { + InjectiveMultimap m(m_in); + return PerformInjectiveMultimapDestructive(&m, delegate); +} + +bool FileDescriptorTableInjection::Duplicate(int* result, int fd) { + *result = HANDLE_EINTR(dup(fd)); + return *result >= 0; +} + +bool FileDescriptorTableInjection::Move(int src, int dest) { + return HANDLE_EINTR(dup2(src, dest)) != -1; +} + +void FileDescriptorTableInjection::Close(int fd) { + int ret = HANDLE_EINTR(close(fd)); + DPCHECK(ret == 0); +} + +} // namespace base
diff --git a/src/base/posix/file_descriptor_shuffle.h b/src/base/posix/file_descriptor_shuffle.h new file mode 100644 index 0000000..6888c3e --- /dev/null +++ b/src/base/posix/file_descriptor_shuffle.h
@@ -0,0 +1,87 @@ +// 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. + +#ifndef BASE_POSIX_FILE_DESCRIPTOR_SHUFFLE_H_ +#define BASE_POSIX_FILE_DESCRIPTOR_SHUFFLE_H_ + +// This code exists to perform the shuffling of file descriptors which is +// commonly needed when forking subprocesses. The naive approve is very simple, +// just call dup2 to setup the desired descriptors, but wrong. It's tough to +// handle the edge cases (like mapping 0 -> 1, 1 -> 0) correctly. +// +// In order to unittest this code, it's broken into the abstract action (an +// injective multimap) and the concrete code for dealing with file descriptors. +// Users should use the code like this: +// base::InjectiveMultimap file_descriptor_map; +// file_descriptor_map.push_back(base::InjectionArc(devnull, 0, true)); +// file_descriptor_map.push_back(base::InjectionArc(devnull, 2, true)); +// file_descriptor_map.push_back(base::InjectionArc(pipe[1], 1, true)); +// base::ShuffleFileDescriptors(file_descriptor_map); +// +// and trust the the Right Thing will get done. + +#include <vector> + +#include "base/base_export.h" +#include "base/compiler_specific.h" + +namespace base { + +// A Delegate which performs the actions required to perform an injective +// multimapping in place. +class InjectionDelegate { + public: + // Duplicate |fd|, an element of the domain, and write a fresh element of the + // domain into |result|. Returns true iff successful. + virtual bool Duplicate(int* result, int fd) = 0; + // Destructively move |src| to |dest|, overwriting |dest|. Returns true iff + // successful. + virtual bool Move(int src, int dest) = 0; + // Delete an element of the domain. + virtual void Close(int fd) = 0; + + protected: + virtual ~InjectionDelegate() {} +}; + +// An implementation of the InjectionDelegate interface using the file +// descriptor table of the current process as the domain. +class FileDescriptorTableInjection : public InjectionDelegate { + virtual bool Duplicate(int* result, int fd) OVERRIDE; + virtual bool Move(int src, int dest) OVERRIDE; + virtual void Close(int fd) OVERRIDE; +}; + +// A single arc of the directed graph which describes an injective multimapping. +struct InjectionArc { + InjectionArc(int in_source, int in_dest, bool in_close) + : source(in_source), + dest(in_dest), + close(in_close) { + } + + int source; + int dest; + bool close; // if true, delete the source element after performing the + // mapping. +}; + +typedef std::vector<InjectionArc> InjectiveMultimap; + +BASE_EXPORT bool PerformInjectiveMultimap(const InjectiveMultimap& map, + InjectionDelegate* delegate); + +BASE_EXPORT bool PerformInjectiveMultimapDestructive( + InjectiveMultimap* map, + InjectionDelegate* delegate); + +// This function will not call malloc but will mutate |map| +static inline bool ShuffleFileDescriptors(InjectiveMultimap* map) { + FileDescriptorTableInjection delegate; + return PerformInjectiveMultimapDestructive(map, &delegate); +} + +} // namespace base + +#endif // BASE_POSIX_FILE_DESCRIPTOR_SHUFFLE_H_
diff --git a/src/base/posix/file_descriptor_shuffle_unittest.cc b/src/base/posix/file_descriptor_shuffle_unittest.cc new file mode 100644 index 0000000..9e1b250 --- /dev/null +++ b/src/base/posix/file_descriptor_shuffle_unittest.cc
@@ -0,0 +1,287 @@ +// 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/posix/file_descriptor_shuffle.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// 'Duplicated' file descriptors start at this number +const int kDuplicateBase = 1000; + +} // namespace + +namespace base { + +struct Action { + enum Type { + CLOSE, + MOVE, + DUPLICATE, + }; + + Action(Type in_type, int in_fd1, int in_fd2 = -1) + : type(in_type), + fd1(in_fd1), + fd2(in_fd2) { + } + + bool operator==(const Action& other) const { + return other.type == type && + other.fd1 == fd1 && + other.fd2 == fd2; + } + + Type type; + int fd1; + int fd2; +}; + +class InjectionTracer : public InjectionDelegate { + public: + InjectionTracer() + : next_duplicate_(kDuplicateBase) { + } + + virtual bool Duplicate(int* result, int fd) OVERRIDE { + *result = next_duplicate_++; + actions_.push_back(Action(Action::DUPLICATE, *result, fd)); + return true; + } + + virtual bool Move(int src, int dest) OVERRIDE { + actions_.push_back(Action(Action::MOVE, src, dest)); + return true; + } + + virtual void Close(int fd) OVERRIDE { + actions_.push_back(Action(Action::CLOSE, fd)); + } + + const std::vector<Action>& actions() const { return actions_; } + + private: + int next_duplicate_; + std::vector<Action> actions_; +}; + +TEST(FileDescriptorShuffleTest, Empty) { + InjectiveMultimap map; + InjectionTracer tracer; + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + EXPECT_EQ(0u, tracer.actions().size()); +} + +TEST(FileDescriptorShuffleTest, Noop) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 0, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + EXPECT_EQ(0u, tracer.actions().size()); +} + +TEST(FileDescriptorShuffleTest, NoopAndClose) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 0, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + EXPECT_EQ(0u, tracer.actions().size()); +} + +TEST(FileDescriptorShuffleTest, Simple1) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(1u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); +} + +TEST(FileDescriptorShuffleTest, Simple2) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + map.push_back(InjectionArc(2, 3, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(2u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 2, 3)); +} + +TEST(FileDescriptorShuffleTest, Simple3) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(2u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::CLOSE, 0)); +} + +TEST(FileDescriptorShuffleTest, Simple4) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(10, 0, true)); + map.push_back(InjectionArc(1, 1, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(2u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 10, 0)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::CLOSE, 10)); +} + +TEST(FileDescriptorShuffleTest, Cycle) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + map.push_back(InjectionArc(1, 0, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(4u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == + Action(Action::DUPLICATE, kDuplicateBase, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0)); + EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase)); +} + +TEST(FileDescriptorShuffleTest, CycleAndClose1) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, true)); + map.push_back(InjectionArc(1, 0, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(4u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == + Action(Action::DUPLICATE, kDuplicateBase, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0)); + EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase)); +} + +TEST(FileDescriptorShuffleTest, CycleAndClose2) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + map.push_back(InjectionArc(1, 0, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(4u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == + Action(Action::DUPLICATE, kDuplicateBase, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0)); + EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase)); +} + +TEST(FileDescriptorShuffleTest, CycleAndClose3) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, true)); + map.push_back(InjectionArc(1, 0, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(4u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == + Action(Action::DUPLICATE, kDuplicateBase, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0)); + EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase)); +} + +TEST(FileDescriptorShuffleTest, Fanout) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + map.push_back(InjectionArc(0, 2, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(2u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2)); +} + +TEST(FileDescriptorShuffleTest, FanoutAndClose1) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, true)); + map.push_back(InjectionArc(0, 2, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(3u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::CLOSE, 0)); +} + +TEST(FileDescriptorShuffleTest, FanoutAndClose2) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, false)); + map.push_back(InjectionArc(0, 2, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(3u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::CLOSE, 0)); +} + +TEST(FileDescriptorShuffleTest, FanoutAndClose3) { + InjectiveMultimap map; + InjectionTracer tracer; + map.push_back(InjectionArc(0, 1, true)); + map.push_back(InjectionArc(0, 2, true)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer)); + ASSERT_EQ(3u, tracer.actions().size()); + EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1)); + EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2)); + EXPECT_TRUE(tracer.actions()[2] == Action(Action::CLOSE, 0)); +} + +class FailingDelegate : public InjectionDelegate { + public: + virtual bool Duplicate(int* result, int fd) OVERRIDE { + return false; + } + + virtual bool Move(int src, int dest) OVERRIDE { + return false; + } + + virtual void Close(int fd) OVERRIDE {} +}; + +TEST(FileDescriptorShuffleTest, EmptyWithFailure) { + InjectiveMultimap map; + FailingDelegate failing; + + EXPECT_TRUE(PerformInjectiveMultimap(map, &failing)); +} + +TEST(FileDescriptorShuffleTest, NoopWithFailure) { + InjectiveMultimap map; + FailingDelegate failing; + map.push_back(InjectionArc(0, 0, false)); + + EXPECT_TRUE(PerformInjectiveMultimap(map, &failing)); +} + +TEST(FileDescriptorShuffleTest, Simple1WithFailure) { + InjectiveMultimap map; + FailingDelegate failing; + map.push_back(InjectionArc(0, 1, false)); + + EXPECT_FALSE(PerformInjectiveMultimap(map, &failing)); +} + +} // namespace base
diff --git a/src/base/posix/global_descriptors.cc b/src/base/posix/global_descriptors.cc new file mode 100644 index 0000000..bcca443 --- /dev/null +++ b/src/base/posix/global_descriptors.cc
@@ -0,0 +1,60 @@ +// 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/posix/global_descriptors.h" + +#include <vector> +#include <utility> + +#include "base/logging.h" + +namespace base { + +// static +GlobalDescriptors* GlobalDescriptors::GetInstance() { + typedef Singleton<base::GlobalDescriptors, + LeakySingletonTraits<base::GlobalDescriptors> > + GlobalDescriptorsSingleton; + return GlobalDescriptorsSingleton::get(); +} + +int GlobalDescriptors::Get(Key key) const { + const int ret = MaybeGet(key); + + if (ret == -1) + DLOG(FATAL) << "Unknown global descriptor: " << key; + return ret; +} + +int GlobalDescriptors::MaybeGet(Key key) const { + for (Mapping::const_iterator + i = descriptors_.begin(); i != descriptors_.end(); ++i) { + if (i->first == key) + return i->second; + } + + return -1; +} + +void GlobalDescriptors::Set(Key key, int fd) { + for (Mapping::iterator + i = descriptors_.begin(); i != descriptors_.end(); ++i) { + if (i->first == key) { + i->second = fd; + return; + } + } + + descriptors_.push_back(std::make_pair(key, fd)); +} + +void GlobalDescriptors::Reset(const Mapping& mapping) { + descriptors_ = mapping; +} + +GlobalDescriptors::GlobalDescriptors() {} + +GlobalDescriptors::~GlobalDescriptors() {} + +} // namespace base
diff --git a/src/base/posix/global_descriptors.h b/src/base/posix/global_descriptors.h new file mode 100644 index 0000000..c7b9f87 --- /dev/null +++ b/src/base/posix/global_descriptors.h
@@ -0,0 +1,70 @@ +// 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_POSIX_GLOBAL_DESCRIPTORS_H_ +#define BASE_POSIX_GLOBAL_DESCRIPTORS_H_ + +#include "build/build_config.h" + +#include <vector> +#include <utility> + +#include <stdint.h> + +#include "base/memory/singleton.h" + +namespace base { + +// It's common practice to install file descriptors into well known slot +// numbers before execing a child; stdin, stdout and stderr are ubiqutous +// examples. +// +// However, when using a zygote model, this becomes troublesome. Since the +// descriptors which need to be in these slots generally aren't known, any code +// could open a resource and take one of the reserved descriptors. Simply +// overwriting the slot isn't a viable solution. +// +// We could try to fill the reserved slots as soon as possible, but this is a +// fragile solution since global constructors etc are able to open files. +// +// Instead, we retreat from the idea of installing descriptors in specific +// slots and add a layer of indirection in the form of this singleton object. +// It maps from an abstract key to a descriptor. If independent modules each +// need to define keys, then values should be chosen randomly so as not to +// collide. +class BASE_EXPORT GlobalDescriptors { + public: + typedef uint32_t Key; + typedef std::pair<Key, int> KeyFDPair; + typedef std::vector<KeyFDPair> Mapping; + + // Often we want a canonical descriptor for a given Key. In this case, we add + // the following constant to the key value: + static const int kBaseDescriptor = 3; // 0, 1, 2 are already taken. + + // Return the singleton instance of GlobalDescriptors. + static GlobalDescriptors* GetInstance(); + + // Get a descriptor given a key. It is a fatal error if the key is not known. + int Get(Key key) const; + + // Get a descriptor give a key. Returns -1 on error. + int MaybeGet(Key key) const; + + // Set the descriptor for the given key. + void Set(Key key, int fd); + + void Reset(const Mapping& mapping); + + private: + friend struct DefaultSingletonTraits<GlobalDescriptors>; + GlobalDescriptors(); + ~GlobalDescriptors(); + + Mapping descriptors_; +}; + +} // namespace base + +#endif // BASE_POSIX_GLOBAL_DESCRIPTORS_H_
diff --git a/src/base/posix/unix_domain_socket.cc b/src/base/posix/unix_domain_socket.cc new file mode 100644 index 0000000..5bfcef1 --- /dev/null +++ b/src/base/posix/unix_domain_socket.cc
@@ -0,0 +1,153 @@ +// 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/posix/unix_domain_socket.h" + +#include <errno.h> +#include <unistd.h> +#include <sys/uio.h> +#include <sys/socket.h> + +#include "base/logging.h" +#include "base/pickle.h" +#include "base/posix/eintr_wrapper.h" +#include "base/stl_util.h" + +const size_t UnixDomainSocket::kMaxFileDescriptors = 16; + +// static +bool UnixDomainSocket::SendMsg(int fd, + const void* buf, + size_t length, + const std::vector<int>& fds) { + struct msghdr msg; + memset(&msg, 0, sizeof(msg)); + struct iovec iov = {const_cast<void*>(buf), length}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + char* control_buffer = NULL; + if (fds.size()) { + const unsigned control_len = CMSG_SPACE(sizeof(int) * fds.size()); + control_buffer = new char[control_len]; + + struct cmsghdr *cmsg; + msg.msg_control = control_buffer; + msg.msg_controllen = control_len; + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * fds.size()); + memcpy(CMSG_DATA(cmsg), &fds[0], sizeof(int) * fds.size()); + msg.msg_controllen = cmsg->cmsg_len; + } + + // When available, take advantage of MSG_NOSIGNAL to avoid + // a SIGPIPE if the other end breaks the connection. +#if defined(MSG_NOSIGNAL) + const int flags = MSG_NOSIGNAL; +#else + const int flags = 0; +#endif + const ssize_t r = HANDLE_EINTR(sendmsg(fd, &msg, flags)); + const bool ret = static_cast<ssize_t>(length) == r; + delete[] control_buffer; + return ret; +} + +// static +ssize_t UnixDomainSocket::RecvMsg(int fd, + void* buf, + size_t length, + std::vector<int>* fds) { + fds->clear(); + + struct msghdr msg; + memset(&msg, 0, sizeof(msg)); + struct iovec iov = {buf, length}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + char control_buffer[CMSG_SPACE(sizeof(int) * kMaxFileDescriptors)]; + msg.msg_control = control_buffer; + msg.msg_controllen = sizeof(control_buffer); + + const ssize_t r = HANDLE_EINTR(recvmsg(fd, &msg, 0)); + if (r == -1) + return -1; + + int* wire_fds = NULL; + unsigned wire_fds_len = 0; + + if (msg.msg_controllen > 0) { + struct cmsghdr* cmsg; + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0); + DCHECK(payload_len % sizeof(int) == 0); + wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg)); + wire_fds_len = payload_len / sizeof(int); + break; + } + } + } + + if (msg.msg_flags & MSG_TRUNC || msg.msg_flags & MSG_CTRUNC) { + for (unsigned i = 0; i < wire_fds_len; ++i) + close(wire_fds[i]); + errno = EMSGSIZE; + return -1; + } + + fds->resize(wire_fds_len); + memcpy(vector_as_array(fds), wire_fds, sizeof(int) * wire_fds_len); + + return r; +} + +// static +ssize_t UnixDomainSocket::SendRecvMsg(int fd, + uint8_t* reply, + unsigned max_reply_len, + int* result_fd, + const Pickle& request) { + int fds[2]; + + // This socketpair is only used for the IPC and is cleaned up before + // returning. + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) == -1) + return -1; + + std::vector<int> fd_vector; + fd_vector.push_back(fds[1]); + if (!SendMsg(fd, request.data(), request.size(), fd_vector)) { + close(fds[0]); + close(fds[1]); + return -1; + } + close(fds[1]); + + fd_vector.clear(); + const ssize_t reply_len = RecvMsg(fds[0], reply, max_reply_len, &fd_vector); + close(fds[0]); + if (reply_len == -1) + return -1; + + if ((!fd_vector.empty() && result_fd == NULL) || fd_vector.size() > 1) { + for (std::vector<int>::const_iterator + i = fd_vector.begin(); i != fd_vector.end(); ++i) { + close(*i); + } + + NOTREACHED(); + + return -1; + } + + if (result_fd) + *result_fd = fd_vector.empty() ? -1 : fd_vector[0]; + + return reply_len; +}
diff --git a/src/base/posix/unix_domain_socket.h b/src/base/posix/unix_domain_socket.h new file mode 100644 index 0000000..cb2a0b8 --- /dev/null +++ b/src/base/posix/unix_domain_socket.h
@@ -0,0 +1,58 @@ +// 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. + +#ifndef BASE_POSIX_UNIX_DOMAIN_SOCKET_H_ +#define BASE_POSIX_UNIX_DOMAIN_SOCKET_H_ + +#include <stdint.h> +#include <sys/types.h> +#include <vector> + +#include "base/base_export.h" + +class Pickle; + +class BASE_EXPORT UnixDomainSocket { + public: + // Maximum number of file descriptors that can be read by RecvMsg(). + static const size_t kMaxFileDescriptors; + + // Use sendmsg to write the given msg and include a vector of file + // descriptors. Returns true if successful. + static bool SendMsg(int fd, + const void* msg, + size_t length, + const std::vector<int>& fds); + + // Use recvmsg to read a message and an array of file descriptors. Returns + // -1 on failure. Note: will read, at most, |kMaxFileDescriptors| descriptors. + static ssize_t RecvMsg(int fd, + void* msg, + size_t length, + std::vector<int>* fds); + + // Perform a sendmsg/recvmsg pair. + // 1. This process creates a UNIX DGRAM socketpair. + // 2. This proces writes a request to |fd| with an SCM_RIGHTS control + // message containing on end of the fresh socket pair. + // 3. This process blocks reading from the other end of the fresh + // socketpair. + // 4. The target process receives the request, processes it and writes the + // reply to the end of the socketpair contained in the request. + // 5. This process wakes up and continues. + // + // fd: descriptor to send the request on + // reply: buffer for the reply + // reply_len: size of |reply| + // result_fd: (may be NULL) the file descriptor returned in the reply + // (if any) + // request: the bytes to send in the request + static ssize_t SendRecvMsg(int fd, + uint8_t* reply, + unsigned reply_len, + int* result_fd, + const Pickle& request); +}; + +#endif // BASE_POSIX_UNIX_DOMAIN_SOCKET_POSIX_H_
diff --git a/src/base/pr_time_unittest.cc b/src/base/pr_time_unittest.cc new file mode 100644 index 0000000..da1ab96 --- /dev/null +++ b/src/base/pr_time_unittest.cc
@@ -0,0 +1,189 @@ +// 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 "build/build_config.h" + +#if !defined(OS_STARBOARD) +#include <time.h> +#endif + +#include "base/compiler_specific.h" +#include "base/test/time_helpers.h" +#include "base/third_party/nspr/prtime.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::Time; + +namespace { + +// time_t representation of 15th Oct 2007 12:45:00 PDT +PRTime comparison_time_pdt = 1192477500 * Time::kMicrosecondsPerSecond; + +// Specialized test fixture allowing time strings without timezones to be +// tested by comparing them to a known time in the local zone. +class PRTimeTest : public testing::Test { + // TODO: This test breaks the platform seal by including time.h, and + // using that to check the NS PRTime code against. Do something else instead + // that is more platform-abstracted. + + protected: + virtual void SetUp() OVERRIDE { +#if defined(OS_STARBOARD) + Time local_time = base::test::time_helpers::TestDateToTime( + base::test::time_helpers::kTimeZoneLocal); + comparison_time_local_ = (local_time - Time::UnixEpoch()).ToInternalValue(); +#else + // Use mktime to get a time_t, and turn it into a PRTime by converting + // seconds to microseconds. Use 15th Oct 2007 12:45:00 local. This + // must be a time guaranteed to be outside of a DST fallback hour in + // any timezone. + struct tm local_comparison_tm = { + 0, // second + 45, // minute + 12, // hour + 15, // day of month + 10 - 1, // month + 2007 - 1900, // year + 0, // day of week (ignored, output only) + 0, // day of year (ignored, output only) + -1 // DST in effect, -1 tells mktime to figure it out + }; + comparison_time_local_ = mktime(&local_comparison_tm) * + Time::kMicrosecondsPerSecond; +#endif + ASSERT_GT(comparison_time_local_, 0); + } + + PRTime comparison_time_local_; +}; + +#if !defined(OS_STARBOARD) +// More of the no local time on Starboard issue. We can't use these standard +// functions to check NSPR Time against because they don't always work on all +// platforms, making these tests inherently flaky and non-portable. + +// Tests the PR_ParseTimeString nspr helper function for +// a variety of time strings. +TEST_F(PRTimeTest, ParseTimeTest1) { + time_t current_time = 0; + time(¤t_time); + + const int BUFFER_SIZE = 64; + struct tm local_time = {0}; + char time_buf[BUFFER_SIZE] = {0}; +#if defined(OS_WIN) + localtime_s(&local_time, ¤t_time); + asctime_s(time_buf, arraysize(time_buf), &local_time); +#elif defined(__STDC_LIB_EXT1__) + localtime_s(¤t_time, &local_time); + asctime_s(time_buf, arraysize(time_buf), &local_time); +#elif defined(OS_POSIX) + localtime_r(¤t_time, &local_time); + asctime_r(&local_time, time_buf); +#endif + + PRTime current_time64 = static_cast<PRTime>(current_time) * PR_USEC_PER_SEC; + + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString(time_buf, PR_FALSE, &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(current_time64, parsed_time); +} +#endif + +TEST_F(PRTimeTest, ParseTimeTest2) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("Mon, 15 Oct 2007 19:45:00 GMT", + PR_FALSE, &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_pdt); +} + +TEST_F(PRTimeTest, ParseTimeTest3) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("15 Oct 07 12:45:00", PR_FALSE, + &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_local_); +} + +TEST_F(PRTimeTest, ParseTimeTest4) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("15 Oct 07 19:45 GMT", PR_FALSE, + &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_pdt); +} + +TEST_F(PRTimeTest, ParseTimeTest5) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("Mon Oct 15 12:45 PDT 2007", + PR_FALSE, &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_pdt); +} + +TEST_F(PRTimeTest, ParseTimeTest6) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("Monday, Oct 15, 2007 12:45 PM", + PR_FALSE, &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_local_); +} + +TEST_F(PRTimeTest, ParseTimeTest7) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("10/15/07 12:45:00 PM", PR_FALSE, + &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_local_); +} + +TEST_F(PRTimeTest, ParseTimeTest8) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("15-OCT-2007 12:45pm", PR_FALSE, + &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_local_); +} + +TEST_F(PRTimeTest, ParseTimeTest9) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("16 Oct 2007 4:45-JST (Tuesday)", + PR_FALSE, &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(parsed_time, comparison_time_pdt); +} + +// This test should not crash when compiled with Visual C++ 2005 (see +// http://crbug.com/4387). +TEST_F(PRTimeTest, ParseTimeTestOutOfRange) { + PRTime parsed_time = 0; + // Note the lack of timezone in the time string. The year has to be 3001. + // The date has to be after 23:59:59, December 31, 3000, US Pacific Time, so + // we use January 2, 3001 to make sure it's after the magic maximum in any + // timezone. + PRStatus result = PR_ParseTimeString("Sun Jan 2 00:00:00 3001", + PR_FALSE, &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); +} + +TEST_F(PRTimeTest, ParseTimeTestNotNormalized1) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("Mon Oct 15 12:44:60 PDT 2007", + PR_FALSE, &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(comparison_time_pdt, parsed_time); +} + +TEST_F(PRTimeTest, ParseTimeTestNotNormalized2) { + PRTime parsed_time = 0; + PRStatus result = PR_ParseTimeString("Sun Oct 14 36:45 PDT 2007", + PR_FALSE, &parsed_time); + EXPECT_EQ(PR_SUCCESS, result); + EXPECT_EQ(comparison_time_pdt, parsed_time); +} + +} // namespace
diff --git a/src/base/prefs/README b/src/base/prefs/README new file mode 100644 index 0000000..a49604e --- /dev/null +++ b/src/base/prefs/README
@@ -0,0 +1,10 @@ +Prefs is a general-purpose key-value store for application preferences. + +At the moment, src/base/prefs has a bunch of dependencies back to +src/chrome, and thus should only be used by code under src/chrome. + +We are working to remove these dependencies; once we do so, +src/base/prefs will be available as a separate module, 'base_prefs', +from src/base/base.gyp. + +Until that happens, please do not try to reuse outside of chrome/.
diff --git a/src/base/prefs/base_prefs_export.h b/src/base/prefs/base_prefs_export.h new file mode 100644 index 0000000..039e7a9 --- /dev/null +++ b/src/base/prefs/base_prefs_export.h
@@ -0,0 +1,29 @@ +// 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_BASE_PREFS_EXPORT_H_ +#define BASE_PREFS_BASE_PREFS_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(_MSC_VER) + +#if defined(BASE_PREFS_IMPLEMENTATION) +#define BASE_PREFS_EXPORT __declspec(dllexport) +#else +#define BASE_PREFS_EXPORT __declspec(dllimport) +#endif // defined(BASE_PREFS_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(BASE_PREFS_IMPLEMENTATION) +#define BASE_PREFS_EXPORT __attribute__((visibility("default"))) +#else +#define BASE_PREFS_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define BASE_PREFS_EXPORT +#endif + +#endif // BASE_PREFS_BASE_PREFS_EXPORT_H_
diff --git a/src/base/prefs/default_pref_store.cc b/src/base/prefs/default_pref_store.cc new file mode 100644 index 0000000..8e2644c --- /dev/null +++ b/src/base/prefs/default_pref_store.cc
@@ -0,0 +1,41 @@ +// 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/default_pref_store.h" +#include "base/logging.h" + +using base::Value; + +DefaultPrefStore::DefaultPrefStore() {} + +bool DefaultPrefStore::GetValue( + const std::string& key, + const base::Value** result) const { + return prefs_.GetValue(key, result); +} + +void DefaultPrefStore::SetDefaultValue(const std::string& key, Value* value) { + DCHECK(!GetValue(key, NULL)); + prefs_.SetValue(key, value); +} + +void DefaultPrefStore::RemoveDefaultValue(const std::string& key) { + DCHECK(GetValue(key, NULL)); + prefs_.RemoveValue(key); +} + +base::Value::Type DefaultPrefStore::GetType(const std::string& key) const { + const Value* value = NULL; + return GetValue(key, &value) ? value->GetType() : Value::TYPE_NULL; +} + +DefaultPrefStore::const_iterator DefaultPrefStore::begin() const { + return prefs_.begin(); +} + +DefaultPrefStore::const_iterator DefaultPrefStore::end() const { + return prefs_.end(); +} + +DefaultPrefStore::~DefaultPrefStore() {}
diff --git a/src/base/prefs/default_pref_store.h b/src/base/prefs/default_pref_store.h new file mode 100644 index 0000000..996bf8b --- /dev/null +++ b/src/base/prefs/default_pref_store.h
@@ -0,0 +1,48 @@ +// 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_DEFAULT_PREF_STORE_H_ +#define BASE_PREFS_DEFAULT_PREF_STORE_H_ + +#include <string> + +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/pref_store.h" +#include "base/prefs/pref_value_map.h" +#include "base/values.h" + +// This PrefStore keeps track of default preference values set when a +// preference is registered with the PrefService. +class BASE_PREFS_EXPORT DefaultPrefStore : public PrefStore { + public: + typedef PrefValueMap::const_iterator const_iterator; + + DefaultPrefStore(); + + virtual bool GetValue(const std::string& key, + const base::Value** result) const OVERRIDE; + + // Stores a new |value| for |key|. Assumes ownership of |value|. + void SetDefaultValue(const std::string& key, Value* value); + + // Removes the value for |key|. + void RemoveDefaultValue(const std::string& key); + + // Returns the registered type for |key| or Value::TYPE_NULL if the |key| + // has not been registered. + base::Value::Type GetType(const std::string& key) const; + + const_iterator begin() const; + const_iterator end() const; + + protected: + virtual ~DefaultPrefStore(); + + private: + PrefValueMap prefs_; + + DISALLOW_COPY_AND_ASSIGN(DefaultPrefStore); +}; + +#endif // BASE_PREFS_DEFAULT_PREF_STORE_H_
diff --git a/src/base/prefs/json_pref_store.cc b/src/base/prefs/json_pref_store.cc new file mode 100644 index 0000000..0447975 --- /dev/null +++ b/src/base/prefs/json_pref_store.cc
@@ -0,0 +1,357 @@ +// 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/json_pref_store.h" + +#include <algorithm> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/file_util.h" +#include "base/json/json_file_value_serializer.h" +#include "base/json/json_string_value_serializer.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop_proxy.h" +#include "base/sequenced_task_runner.h" +#include "base/threading/sequenced_worker_pool.h" +#include "base/values.h" + +namespace { + +// Some extensions we'll tack on to copies of the Preferences files. +const FilePath::CharType* kBadExtension = FILE_PATH_LITERAL("bad"); + +// Differentiates file loading between origin thread and passed +// (aka file) thread. +class FileThreadDeserializer + : public base::RefCountedThreadSafe<FileThreadDeserializer> { + public: + FileThreadDeserializer(JsonPrefStore* delegate, + base::SequencedTaskRunner* sequenced_task_runner) + : no_dir_(false), + error_(PersistentPrefStore::PREF_READ_ERROR_NONE), + delegate_(delegate), + sequenced_task_runner_(sequenced_task_runner), + origin_loop_proxy_(base::MessageLoopProxy::current()) { + } + + void Start(const FilePath& path) { + DCHECK(origin_loop_proxy_->BelongsToCurrentThread()); + sequenced_task_runner_->PostTask( + FROM_HERE, + base::Bind(&FileThreadDeserializer::ReadFileAndReport, + this, path)); + } + + // Deserializes JSON on the sequenced task runner. + void ReadFileAndReport(const FilePath& path) { + DCHECK(sequenced_task_runner_->RunsTasksOnCurrentThread()); + + value_.reset(DoReading(path, &error_, &no_dir_)); + + origin_loop_proxy_->PostTask( + FROM_HERE, + base::Bind(&FileThreadDeserializer::ReportOnOriginThread, this)); + } + + // Reports deserialization result on the origin thread. + void ReportOnOriginThread() { + DCHECK(origin_loop_proxy_->BelongsToCurrentThread()); + delegate_->OnFileRead(value_.release(), error_, no_dir_); + } + + static Value* DoReading(const FilePath& path, + PersistentPrefStore::PrefReadError* error, + bool* no_dir) { + int error_code; + std::string error_msg; + JSONFileValueSerializer serializer(path); + Value* value = serializer.Deserialize(&error_code, &error_msg); + HandleErrors(value, path, error_code, error_msg, error); + *no_dir = !file_util::PathExists(path.DirName()); + return value; + } + + static void HandleErrors(const Value* value, + const FilePath& path, + int error_code, + const std::string& error_msg, + PersistentPrefStore::PrefReadError* error); + + private: + friend class base::RefCountedThreadSafe<FileThreadDeserializer>; + ~FileThreadDeserializer() {} + + bool no_dir_; + PersistentPrefStore::PrefReadError error_; + scoped_ptr<Value> value_; + const scoped_refptr<JsonPrefStore> delegate_; + const scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_; + const scoped_refptr<base::MessageLoopProxy> origin_loop_proxy_; +}; + +// static +void FileThreadDeserializer::HandleErrors( + const Value* value, + const FilePath& path, + int error_code, + const std::string& error_msg, + PersistentPrefStore::PrefReadError* error) { + *error = PersistentPrefStore::PREF_READ_ERROR_NONE; + if (!value) { + DVLOG(1) << "Error while loading JSON file: " << error_msg + << ", file: " << path.value(); + switch (error_code) { + case JSONFileValueSerializer::JSON_ACCESS_DENIED: + *error = PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED; + break; + case JSONFileValueSerializer::JSON_CANNOT_READ_FILE: + *error = PersistentPrefStore::PREF_READ_ERROR_FILE_OTHER; + break; + case JSONFileValueSerializer::JSON_FILE_LOCKED: + *error = PersistentPrefStore::PREF_READ_ERROR_FILE_LOCKED; + break; + case JSONFileValueSerializer::JSON_NO_SUCH_FILE: + *error = PersistentPrefStore::PREF_READ_ERROR_NO_FILE; + break; + default: + *error = PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE; + // JSON errors indicate file corruption of some sort. + // Since the file is corrupt, move it to the side and continue with + // empty preferences. This will result in them losing their settings. + // We keep the old file for possible support and debugging assistance + // as well as to detect if they're seeing these errors repeatedly. + // TODO(erikkay) Instead, use the last known good file. + FilePath bad = path.ReplaceExtension(kBadExtension); + + // If they've ever had a parse error before, put them in another bucket. + // TODO(erikkay) if we keep this error checking for very long, we may + // want to differentiate between recent and long ago errors. + if (file_util::PathExists(bad)) + *error = PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT; + file_util::Move(path, bad); + break; + } + } else if (!value->IsType(Value::TYPE_DICTIONARY)) { + *error = PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE; + } +} + +} // namespace + +scoped_refptr<base::SequencedTaskRunner> JsonPrefStore::GetTaskRunnerForFile( + const FilePath& filename, + base::SequencedWorkerPool* worker_pool) { + std::string token("json_pref_store-"); + token.append(filename.AsUTF8Unsafe()); + return worker_pool->GetSequencedTaskRunnerWithShutdownBehavior( + worker_pool->GetNamedSequenceToken(token), + base::SequencedWorkerPool::BLOCK_SHUTDOWN); +} + +JsonPrefStore::JsonPrefStore(const FilePath& filename, + base::SequencedTaskRunner* sequenced_task_runner) + : path_(filename), + sequenced_task_runner_(sequenced_task_runner), + prefs_(new DictionaryValue()), + read_only_(false), + writer_(filename, sequenced_task_runner), + error_delegate_(NULL), + initialized_(false), + read_error_(PREF_READ_ERROR_OTHER) { +} + +bool JsonPrefStore::GetValue(const std::string& key, + const Value** result) const { + Value* tmp = NULL; + if (!prefs_->Get(key, &tmp)) + return false; + + if (result) + *result = tmp; + return true; +} + +void JsonPrefStore::AddObserver(PrefStore::Observer* observer) { + observers_.AddObserver(observer); +} + +void JsonPrefStore::RemoveObserver(PrefStore::Observer* observer) { + observers_.RemoveObserver(observer); +} + +size_t JsonPrefStore::NumberOfObservers() const { + return observers_.size(); +} + +bool JsonPrefStore::IsInitializationComplete() const { + return initialized_; +} + +bool JsonPrefStore::GetMutableValue(const std::string& key, + Value** result) { + return prefs_->Get(key, result); +} + +void JsonPrefStore::SetValue(const std::string& key, Value* value) { + DCHECK(value); + scoped_ptr<Value> new_value(value); + Value* old_value = NULL; + prefs_->Get(key, &old_value); + if (!old_value || !value->Equals(old_value)) { + prefs_->Set(key, new_value.release()); + ReportValueChanged(key); + } +} + +void JsonPrefStore::SetValueSilently(const std::string& key, Value* value) { + DCHECK(value); + scoped_ptr<Value> new_value(value); + Value* old_value = NULL; + prefs_->Get(key, &old_value); + if (!old_value || !value->Equals(old_value)) { + prefs_->Set(key, new_value.release()); + if (!read_only_) + writer_.ScheduleWrite(this); + } +} + +void JsonPrefStore::RemoveValue(const std::string& key) { + if (prefs_->Remove(key, NULL)) + ReportValueChanged(key); +} + +void JsonPrefStore::MarkNeedsEmptyValue(const std::string& key) { + keys_need_empty_value_.insert(key); +} + +bool JsonPrefStore::ReadOnly() const { + return read_only_; +} + +PersistentPrefStore::PrefReadError JsonPrefStore::GetReadError() const { + return read_error_; +} + +PersistentPrefStore::PrefReadError JsonPrefStore::ReadPrefs() { + if (path_.empty()) { + OnFileRead(NULL, PREF_READ_ERROR_FILE_NOT_SPECIFIED, false); + return PREF_READ_ERROR_FILE_NOT_SPECIFIED; + } + + PrefReadError error; + bool no_dir; + Value* value = FileThreadDeserializer::DoReading(path_, &error, &no_dir); + OnFileRead(value, error, no_dir); + return error; +} + +void JsonPrefStore::ReadPrefsAsync(ReadErrorDelegate *error_delegate) { + initialized_ = false; + error_delegate_.reset(error_delegate); + if (path_.empty()) { + OnFileRead(NULL, PREF_READ_ERROR_FILE_NOT_SPECIFIED, false); + return; + } + + // Start async reading of the preferences file. It will delete itself + // in the end. + scoped_refptr<FileThreadDeserializer> deserializer( + new FileThreadDeserializer(this, sequenced_task_runner_.get())); + deserializer->Start(path_); +} + +void JsonPrefStore::CommitPendingWrite() { + if (writer_.HasPendingWrite() && !read_only_) + writer_.DoScheduledWrite(); +} + +void JsonPrefStore::ReportValueChanged(const std::string& key) { + FOR_EACH_OBSERVER(PrefStore::Observer, observers_, OnPrefValueChanged(key)); + if (!read_only_) + writer_.ScheduleWrite(this); +} + +void JsonPrefStore::OnFileRead(Value* value_owned, + PersistentPrefStore::PrefReadError error, + bool no_dir) { + scoped_ptr<Value> value(value_owned); + read_error_ = error; + + if (no_dir) { + FOR_EACH_OBSERVER(PrefStore::Observer, + observers_, + OnInitializationCompleted(false)); + return; + } + + initialized_ = true; + + switch (error) { + case PREF_READ_ERROR_ACCESS_DENIED: + case PREF_READ_ERROR_FILE_OTHER: + case PREF_READ_ERROR_FILE_LOCKED: + case PREF_READ_ERROR_JSON_TYPE: + case PREF_READ_ERROR_FILE_NOT_SPECIFIED: + read_only_ = true; + break; + case PREF_READ_ERROR_NONE: + DCHECK(value.get()); + prefs_.reset(static_cast<DictionaryValue*>(value.release())); + break; + case PREF_READ_ERROR_NO_FILE: + // If the file just doesn't exist, maybe this is first run. In any case + // there's no harm in writing out default prefs in this case. + break; + case PREF_READ_ERROR_JSON_PARSE: + case PREF_READ_ERROR_JSON_REPEAT: + break; + default: + NOTREACHED() << "Unknown error: " << error; + } + + if (error_delegate_.get() && error != PREF_READ_ERROR_NONE) + error_delegate_->OnError(error); + + FOR_EACH_OBSERVER(PrefStore::Observer, + observers_, + OnInitializationCompleted(true)); +} + +JsonPrefStore::~JsonPrefStore() { + CommitPendingWrite(); +} + +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<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())); +}
diff --git a/src/base/prefs/json_pref_store.h b/src/base/prefs/json_pref_store.h new file mode 100644 index 0000000..49dd71a --- /dev/null +++ b/src/base/prefs/json_pref_store.h
@@ -0,0 +1,103 @@ +// 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_JSON_PREF_STORE_H_ +#define BASE_PREFS_JSON_PREF_STORE_H_ + +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/files/important_file_writer.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop_proxy.h" +#include "base/observer_list.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/persistent_pref_store.h" + +namespace base { +class DictionaryValue; +class SequencedWorkerPool; +class SequencedTaskRunner; +class Value; +} + +class FilePath; + +// A writable PrefStore implementation that is used for user preferences. +class BASE_PREFS_EXPORT JsonPrefStore + : public PersistentPrefStore, + public base::ImportantFileWriter::DataSerializer { + public: + // Returns instance of SequencedTaskRunner which guarantees that file + // operations on the same file will be executed in sequenced order. + static scoped_refptr<base::SequencedTaskRunner> GetTaskRunnerForFile( + const FilePath& pref_filename, + base::SequencedWorkerPool* worker_pool); + + // |sequenced_task_runner| is must be a shutdown-blocking task runner, ideally + // created by GetTaskRunnerForFile() method above. + JsonPrefStore(const FilePath& pref_filename, + base::SequencedTaskRunner* sequenced_task_runner); + + // PrefStore overrides: + virtual bool GetValue(const std::string& key, + const base::Value** result) const OVERRIDE; + virtual void AddObserver(PrefStore::Observer* observer) OVERRIDE; + virtual void RemoveObserver(PrefStore::Observer* observer) OVERRIDE; + virtual size_t NumberOfObservers() const OVERRIDE; + virtual bool IsInitializationComplete() const OVERRIDE; + + // PersistentPrefStore overrides: + virtual bool GetMutableValue(const std::string& key, + base::Value** result) OVERRIDE; + virtual void SetValue(const std::string& key, base::Value* value) OVERRIDE; + 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; + virtual void ReadPrefsAsync(ReadErrorDelegate* error_delegate) OVERRIDE; + virtual void CommitPendingWrite() OVERRIDE; + virtual void ReportValueChanged(const std::string& key) OVERRIDE; + + // This method is called after JSON file has been read. Method takes + // ownership of the |value| pointer. Note, this method is used with + // asynchronous file reading, so class exposes it only for the internal needs. + // (read: do not call it manually). + void OnFileRead(base::Value* value_owned, PrefReadError error, bool no_dir); + + private: + virtual ~JsonPrefStore(); + + // ImportantFileWriter::DataSerializer overrides: + virtual bool SerializeData(std::string* output) OVERRIDE; + + FilePath path_; + const scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_; + + scoped_ptr<base::DictionaryValue> prefs_; + + bool read_only_; + + // Helper for safely writing pref data. + base::ImportantFileWriter writer_; + + ObserverList<PrefStore::Observer, true> observers_; + + scoped_ptr<ReadErrorDelegate> error_delegate_; + + bool initialized_; + PrefReadError read_error_; + + std::set<std::string> keys_need_empty_value_; + + DISALLOW_COPY_AND_ASSIGN(JsonPrefStore); +}; + +#endif // BASE_PREFS_JSON_PREF_STORE_H_
diff --git a/src/base/prefs/json_pref_store_unittest.cc b/src/base/prefs/json_pref_store_unittest.cc new file mode 100644 index 0000000..dc6f95e --- /dev/null +++ b/src/base/prefs/json_pref_store_unittest.cc
@@ -0,0 +1,300 @@ +// 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/json_pref_store.h" + +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/threading/sequenced_worker_pool.h" +#include "base/threading/thread.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +const char kHomePage[] = "homepage"; + +class MockPrefStoreObserver : public PrefStore::Observer { + public: + MOCK_METHOD1(OnPrefValueChanged, void (const std::string&)); + MOCK_METHOD1(OnInitializationCompleted, void (bool)); +}; + +class MockReadErrorDelegate : public PersistentPrefStore::ReadErrorDelegate { + public: + MOCK_METHOD1(OnError, void(PersistentPrefStore::PrefReadError)); +}; + +} // namespace + +class JsonPrefStoreTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &data_dir_)); + data_dir_ = data_dir_.AppendASCII("base"); + data_dir_ = data_dir_.AppendASCII("prefs"); + data_dir_ = data_dir_.AppendASCII("test"); + data_dir_ = data_dir_.AppendASCII("data"); + data_dir_ = data_dir_.AppendASCII("pref_service"); + LOG(WARNING) << data_dir_.value().c_str(); + ASSERT_TRUE(file_util::PathExists(data_dir_)); + } + + // The path to temporary directory used to contain the test operations. + base::ScopedTempDir temp_dir_; + // The path to the directory where the test data is stored. + FilePath data_dir_; + // A message loop that we can use as the file thread message loop. + MessageLoop message_loop_; +}; + +// Test fallback behavior for a nonexistent file. +TEST_F(JsonPrefStoreTest, NonExistentFile) { + FilePath bogus_input_file = data_dir_.AppendASCII("read.txt"); + ASSERT_FALSE(file_util::PathExists(bogus_input_file)); + scoped_refptr<JsonPrefStore> pref_store = + new JsonPrefStore( + bogus_input_file, message_loop_.message_loop_proxy()); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE, + pref_store->ReadPrefs()); + EXPECT_FALSE(pref_store->ReadOnly()); +} + +// Test fallback behavior for an invalid file. +TEST_F(JsonPrefStoreTest, InvalidFile) { + FilePath invalid_file_original = data_dir_.AppendASCII("invalid.json"); + FilePath invalid_file = temp_dir_.path().AppendASCII("invalid.json"); + ASSERT_TRUE(file_util::CopyFile(invalid_file_original, invalid_file)); + scoped_refptr<JsonPrefStore> pref_store = + new JsonPrefStore( + invalid_file, message_loop_.message_loop_proxy()); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE, + pref_store->ReadPrefs()); + EXPECT_FALSE(pref_store->ReadOnly()); + + // The file should have been moved aside. + EXPECT_FALSE(file_util::PathExists(invalid_file)); + FilePath moved_aside = temp_dir_.path().AppendASCII("invalid.bad"); + EXPECT_TRUE(file_util::PathExists(moved_aside)); + EXPECT_TRUE(file_util::TextContentsEqual(invalid_file_original, + moved_aside)); +} + +// This function is used to avoid code duplication while testing synchronous and +// asynchronous version of the JsonPrefStore loading. +void RunBasicJsonPrefStoreTest(JsonPrefStore* pref_store, + const FilePath& output_file, + const FilePath& golden_output_file) { + const char kNewWindowsInTabs[] = "tabs.new_windows_in_tabs"; + const char kMaxTabs[] = "tabs.max_tabs"; + const char kLongIntPref[] = "long_int.pref"; + + std::string cnn("http://www.cnn.com"); + + const Value* actual; + EXPECT_TRUE(pref_store->GetValue(kHomePage, &actual)); + std::string string_value; + EXPECT_TRUE(actual->GetAsString(&string_value)); + EXPECT_EQ(cnn, string_value); + + const char kSomeDirectory[] = "some_directory"; + + EXPECT_TRUE(pref_store->GetValue(kSomeDirectory, &actual)); + FilePath::StringType path; + EXPECT_TRUE(actual->GetAsString(&path)); + EXPECT_EQ(FilePath::StringType(FILE_PATH_LITERAL("/usr/local/")), path); + FilePath some_path(FILE_PATH_LITERAL("/usr/sbin/")); + + pref_store->SetValue(kSomeDirectory, new StringValue(some_path.value())); + EXPECT_TRUE(pref_store->GetValue(kSomeDirectory, &actual)); + EXPECT_TRUE(actual->GetAsString(&path)); + EXPECT_EQ(some_path.value(), path); + + // Test reading some other data types from sub-dictionaries. + EXPECT_TRUE(pref_store->GetValue(kNewWindowsInTabs, &actual)); + bool boolean = false; + EXPECT_TRUE(actual->GetAsBoolean(&boolean)); + EXPECT_TRUE(boolean); + + pref_store->SetValue(kNewWindowsInTabs, new FundamentalValue(false)); + EXPECT_TRUE(pref_store->GetValue(kNewWindowsInTabs, &actual)); + EXPECT_TRUE(actual->GetAsBoolean(&boolean)); + EXPECT_FALSE(boolean); + + EXPECT_TRUE(pref_store->GetValue(kMaxTabs, &actual)); + int integer = 0; + EXPECT_TRUE(actual->GetAsInteger(&integer)); + EXPECT_EQ(20, integer); + pref_store->SetValue(kMaxTabs, new FundamentalValue(10)); + EXPECT_TRUE(pref_store->GetValue(kMaxTabs, &actual)); + EXPECT_TRUE(actual->GetAsInteger(&integer)); + EXPECT_EQ(10, integer); + + pref_store->SetValue(kLongIntPref, + new StringValue(base::Int64ToString(214748364842LL))); + EXPECT_TRUE(pref_store->GetValue(kLongIntPref, &actual)); + EXPECT_TRUE(actual->GetAsString(&string_value)); + int64 value; + base::StringToInt64(string_value, &value); + EXPECT_EQ(214748364842LL, value); + + // Serialize and compare to expected output. + ASSERT_TRUE(file_util::PathExists(golden_output_file)); + pref_store->CommitPendingWrite(); + MessageLoop::current()->RunUntilIdle(); + EXPECT_TRUE(file_util::TextContentsEqual(golden_output_file, output_file)); + ASSERT_TRUE(file_util::Delete(output_file, false)); +} + +TEST_F(JsonPrefStoreTest, Basic) { + ASSERT_TRUE(file_util::CopyFile(data_dir_.AppendASCII("read.json"), + temp_dir_.path().AppendASCII("write.json"))); + + // Test that the persistent value can be loaded. + FilePath input_file = temp_dir_.path().AppendASCII("write.json"); + ASSERT_TRUE(file_util::PathExists(input_file)); + scoped_refptr<JsonPrefStore> pref_store = + new JsonPrefStore( + input_file, message_loop_.message_loop_proxy()); + ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs()); + ASSERT_FALSE(pref_store->ReadOnly()); + + // The JSON file looks like this: + // { + // "homepage": "http://www.cnn.com", + // "some_directory": "/usr/local/", + // "tabs": { + // "new_windows_in_tabs": true, + // "max_tabs": 20 + // } + // } + + RunBasicJsonPrefStoreTest(pref_store, + input_file, + data_dir_.AppendASCII("write.golden.json")); +} + +TEST_F(JsonPrefStoreTest, BasicAsync) { + ASSERT_TRUE(file_util::CopyFile(data_dir_.AppendASCII("read.json"), + temp_dir_.path().AppendASCII("write.json"))); + + // Test that the persistent value can be loaded. + FilePath input_file = temp_dir_.path().AppendASCII("write.json"); + ASSERT_TRUE(file_util::PathExists(input_file)); + scoped_refptr<JsonPrefStore> pref_store = + new JsonPrefStore( + input_file, message_loop_.message_loop_proxy()); + + { + MockPrefStoreObserver mock_observer; + pref_store->AddObserver(&mock_observer); + + MockReadErrorDelegate* mock_error_delegate = new MockReadErrorDelegate; + pref_store->ReadPrefsAsync(mock_error_delegate); + + EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1); + EXPECT_CALL(*mock_error_delegate, + OnError(PersistentPrefStore::PREF_READ_ERROR_NONE)).Times(0); + message_loop_.RunUntilIdle(); + pref_store->RemoveObserver(&mock_observer); + + ASSERT_FALSE(pref_store->ReadOnly()); + } + + // The JSON file looks like this: + // { + // "homepage": "http://www.cnn.com", + // "some_directory": "/usr/local/", + // "tabs": { + // "new_windows_in_tabs": true, + // "max_tabs": 20 + // } + // } + + RunBasicJsonPrefStoreTest(pref_store, + input_file, + data_dir_.AppendASCII("write.golden.json")); +} + +// Tests asynchronous reading of the file when there is no file. +TEST_F(JsonPrefStoreTest, AsyncNonExistingFile) { + FilePath bogus_input_file = data_dir_.AppendASCII("read.txt"); + ASSERT_FALSE(file_util::PathExists(bogus_input_file)); + scoped_refptr<JsonPrefStore> pref_store = + new JsonPrefStore( + bogus_input_file, message_loop_.message_loop_proxy()); + MockPrefStoreObserver mock_observer; + pref_store->AddObserver(&mock_observer); + + MockReadErrorDelegate *mock_error_delegate = new MockReadErrorDelegate; + pref_store->ReadPrefsAsync(mock_error_delegate); + + EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1); + EXPECT_CALL(*mock_error_delegate, + OnError(PersistentPrefStore::PREF_READ_ERROR_NO_FILE)).Times(1); + message_loop_.RunUntilIdle(); + pref_store->RemoveObserver(&mock_observer); + + EXPECT_FALSE(pref_store->ReadOnly()); +} + +TEST_F(JsonPrefStoreTest, NeedsEmptyValue) { + FilePath pref_file = temp_dir_.path().AppendASCII("write.json"); + + ASSERT_TRUE(file_util::CopyFile( + data_dir_.AppendASCII("read.need_empty_value.json"), + pref_file)); + + // Test that the persistent value can be loaded. + ASSERT_TRUE(file_util::PathExists(pref_file)); + scoped_refptr<JsonPrefStore> 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()); + + // 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(); + MessageLoop::current()->RunUntilIdle(); + + // Compare to expected output. + FilePath golden_output_file = + data_dir_.AppendASCII("write.golden.need_empty_value.json"); + ASSERT_TRUE(file_util::PathExists(golden_output_file)); + EXPECT_TRUE(file_util::TextContentsEqual(golden_output_file, pref_file)); +} + +} // namespace base
diff --git a/src/base/prefs/overlay_user_pref_store.cc b/src/base/prefs/overlay_user_pref_store.cc new file mode 100644 index 0000000..3a74a5c --- /dev/null +++ b/src/base/prefs/overlay_user_pref_store.cc
@@ -0,0 +1,182 @@ +// 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/overlay_user_pref_store.h" + +#include "base/memory/scoped_ptr.h" +#include "base/values.h" + +OverlayUserPrefStore::OverlayUserPrefStore( + PersistentPrefStore* underlay) + : underlay_(underlay) { + underlay_->AddObserver(this); +} + +bool OverlayUserPrefStore::IsSetInOverlay(const std::string& key) const { + return overlay_.GetValue(key, NULL); +} + +void OverlayUserPrefStore::AddObserver(PrefStore::Observer* observer) { + observers_.AddObserver(observer); +} + +void OverlayUserPrefStore::RemoveObserver(PrefStore::Observer* observer) { + observers_.RemoveObserver(observer); +} + +size_t OverlayUserPrefStore::NumberOfObservers() const { + return observers_.size(); +} + +bool OverlayUserPrefStore::IsInitializationComplete() const { + return underlay_->IsInitializationComplete(); +} + +bool OverlayUserPrefStore::GetValue(const std::string& key, + const Value** result) const { + // If the |key| shall NOT be stored in the overlay store, there must not + // be an entry. + DCHECK(ShallBeStoredInOverlay(key) || !overlay_.GetValue(key, NULL)); + + if (overlay_.GetValue(key, result)) + return true; + return underlay_->GetValue(GetUnderlayKey(key), result); +} + +bool OverlayUserPrefStore::GetMutableValue(const std::string& key, + Value** result) { + if (!ShallBeStoredInOverlay(key)) + return underlay_->GetMutableValue(GetUnderlayKey(key), result); + + if (overlay_.GetValue(key, result)) + return true; + + // Try to create copy of underlay if the overlay does not contain a value. + Value* underlay_value = NULL; + if (!underlay_->GetMutableValue(GetUnderlayKey(key), &underlay_value)) + return false; + + *result = underlay_value->DeepCopy(); + overlay_.SetValue(key, *result); + return true; +} + +void OverlayUserPrefStore::SetValue(const std::string& key, + Value* value) { + if (!ShallBeStoredInOverlay(key)) { + underlay_->SetValue(GetUnderlayKey(key), value); + return; + } + + if (overlay_.SetValue(key, value)) + ReportValueChanged(key); +} + +void OverlayUserPrefStore::SetValueSilently(const std::string& key, + Value* value) { + if (!ShallBeStoredInOverlay(key)) { + underlay_->SetValueSilently(GetUnderlayKey(key), value); + return; + } + + overlay_.SetValue(key, value); +} + +void OverlayUserPrefStore::RemoveValue(const std::string& key) { + if (!ShallBeStoredInOverlay(key)) { + underlay_->RemoveValue(GetUnderlayKey(key)); + return; + } + + if (overlay_.RemoveValue(key)) + ReportValueChanged(key); +} + +void OverlayUserPrefStore::MarkNeedsEmptyValue(const std::string& key) { + if (!ShallBeStoredInOverlay(key)) + underlay_->MarkNeedsEmptyValue(key); +} + +bool OverlayUserPrefStore::ReadOnly() const { + return false; +} + +PersistentPrefStore::PrefReadError OverlayUserPrefStore::GetReadError() const { + return PersistentPrefStore::PREF_READ_ERROR_NONE; +} + +PersistentPrefStore::PrefReadError OverlayUserPrefStore::ReadPrefs() { + // We do not read intentionally. + OnInitializationCompleted(true); + return PersistentPrefStore::PREF_READ_ERROR_NONE; +} + +void OverlayUserPrefStore::ReadPrefsAsync( + ReadErrorDelegate* error_delegate_raw) { + scoped_ptr<ReadErrorDelegate> error_delegate(error_delegate_raw); + // We do not read intentionally. + OnInitializationCompleted(true); +} + +void OverlayUserPrefStore::CommitPendingWrite() { + underlay_->CommitPendingWrite(); + // We do not write our content intentionally. +} + +void OverlayUserPrefStore::ReportValueChanged(const std::string& key) { + FOR_EACH_OBSERVER(PrefStore::Observer, observers_, OnPrefValueChanged(key)); +} + +void OverlayUserPrefStore::OnPrefValueChanged(const std::string& key) { + if (!overlay_.GetValue(GetOverlayKey(key), NULL)) + ReportValueChanged(GetOverlayKey(key)); +} + +void OverlayUserPrefStore::OnInitializationCompleted(bool succeeded) { + FOR_EACH_OBSERVER(PrefStore::Observer, observers_, + OnInitializationCompleted(succeeded)); +} + +void OverlayUserPrefStore::RegisterOverlayPref(const std::string& key) { + RegisterOverlayPref(key, key); +} + +void OverlayUserPrefStore::RegisterOverlayPref( + const std::string& overlay_key, + const std::string& underlay_key) { + DCHECK(!overlay_key.empty()) << "Overlay key is empty"; + DCHECK(overlay_to_underlay_names_map_.find(overlay_key) == + overlay_to_underlay_names_map_.end()) << + "Overlay key already registered"; + DCHECK(!underlay_key.empty()) << "Underlay key is empty"; + DCHECK(underlay_to_overlay_names_map_.find(underlay_key) == + underlay_to_overlay_names_map_.end()) << + "Underlay key already registered"; + overlay_to_underlay_names_map_[overlay_key] = underlay_key; + underlay_to_overlay_names_map_[underlay_key] = overlay_key; +} + +OverlayUserPrefStore::~OverlayUserPrefStore() { + underlay_->RemoveObserver(this); +} + +const std::string& OverlayUserPrefStore::GetOverlayKey( + const std::string& underlay_key) const { + NamesMap::const_iterator i = + underlay_to_overlay_names_map_.find(underlay_key); + return i != underlay_to_overlay_names_map_.end() ? i->second : underlay_key; +} + +const std::string& OverlayUserPrefStore::GetUnderlayKey( + const std::string& overlay_key) const { + NamesMap::const_iterator i = + overlay_to_underlay_names_map_.find(overlay_key); + return i != overlay_to_underlay_names_map_.end() ? i->second : overlay_key; +} + +bool OverlayUserPrefStore::ShallBeStoredInOverlay( + const std::string& key) const { + return overlay_to_underlay_names_map_.find(key) != + overlay_to_underlay_names_map_.end(); +}
diff --git a/src/base/prefs/overlay_user_pref_store.h b/src/base/prefs/overlay_user_pref_store.h new file mode 100644 index 0000000..120d405 --- /dev/null +++ b/src/base/prefs/overlay_user_pref_store.h
@@ -0,0 +1,85 @@ +// 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_OVERLAY_USER_PREF_STORE_H_ +#define BASE_PREFS_OVERLAY_USER_PREF_STORE_H_ + +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/persistent_pref_store.h" +#include "base/prefs/pref_value_map.h" + +// PersistentPrefStore that directs all write operations into an in-memory +// PrefValueMap. Read operations are first answered by the PrefValueMap. +// If the PrefValueMap does not contain a value for the requested key, +// the look-up is passed on to an underlying PersistentPrefStore |underlay_|. +class BASE_PREFS_EXPORT OverlayUserPrefStore : public PersistentPrefStore, + public PrefStore::Observer { + public: + explicit OverlayUserPrefStore(PersistentPrefStore* underlay); + + // Returns true if a value has been set for the |key| in this + // OverlayUserPrefStore, i.e. if it potentially overrides a value + // from the |underlay_|. + virtual bool IsSetInOverlay(const std::string& key) const; + + // Methods of PrefStore. + virtual void AddObserver(PrefStore::Observer* observer) OVERRIDE; + virtual void RemoveObserver(PrefStore::Observer* observer) OVERRIDE; + virtual size_t NumberOfObservers() const OVERRIDE; + virtual bool IsInitializationComplete() const OVERRIDE; + virtual bool GetValue(const std::string& key, + const base::Value** result) const OVERRIDE; + + // Methods of PersistentPrefStore. + virtual bool GetMutableValue(const std::string& key, + base::Value** result) OVERRIDE; + virtual void SetValue(const std::string& key, base::Value* value) OVERRIDE; + 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; + virtual void ReadPrefsAsync(ReadErrorDelegate* delegate) OVERRIDE; + virtual void CommitPendingWrite() OVERRIDE; + virtual void ReportValueChanged(const std::string& key) OVERRIDE; + + // Methods of PrefStore::Observer. + virtual void OnPrefValueChanged(const std::string& key) OVERRIDE; + virtual void OnInitializationCompleted(bool succeeded) OVERRIDE; + + void RegisterOverlayPref(const std::string& key); + void RegisterOverlayPref(const std::string& overlay_key, + const std::string& underlay_key); + + protected: + virtual ~OverlayUserPrefStore(); + + private: + typedef std::map<std::string, std::string> NamesMap; + + const std::string& GetOverlayKey(const std::string& underlay_key) const; + const std::string& GetUnderlayKey(const std::string& overlay_key) const; + + // Returns true if |key| corresponds to a preference that shall be stored in + // an in-memory PrefStore that is not persisted to disk. + bool ShallBeStoredInOverlay(const std::string& key) const; + + ObserverList<PrefStore::Observer, true> observers_; + PrefValueMap overlay_; + scoped_refptr<PersistentPrefStore> underlay_; + NamesMap overlay_to_underlay_names_map_; + NamesMap underlay_to_overlay_names_map_; + + DISALLOW_COPY_AND_ASSIGN(OverlayUserPrefStore); +}; + +#endif // BASE_PREFS_OVERLAY_USER_PREF_STORE_H_
diff --git a/src/base/prefs/overlay_user_pref_store_unittest.cc b/src/base/prefs/overlay_user_pref_store_unittest.cc new file mode 100644 index 0000000..c4e980b --- /dev/null +++ b/src/base/prefs/overlay_user_pref_store_unittest.cc
@@ -0,0 +1,278 @@ +// 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/overlay_user_pref_store.h" + +#include "base/prefs/pref_store_observer_mock.h" +#include "base/prefs/testing_pref_store.h" +#include "base/values.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::Mock; +using ::testing::StrEq; + +namespace base { +namespace { + +const char kBrowserWindowPlacement[] = "browser.window_placement"; +const char kShowBookmarkBar[] = "bookmark_bar.show_on_all_tabs"; + +const char* overlay_key = kBrowserWindowPlacement; +const char* regular_key = kShowBookmarkBar; +// With the removal of the kWebKitGlobalXXX prefs, we'll no longer have real +// prefs using the overlay pref store, so make up keys here. +const char* mapped_overlay_key = "test.per_tab.javascript_enabled"; +const char* mapped_underlay_key = "test.per_profile.javascript_enabled"; + +} // namespace + +class OverlayUserPrefStoreTest : public testing::Test { + protected: + OverlayUserPrefStoreTest() + : underlay_(new TestingPrefStore()), + overlay_(new OverlayUserPrefStore(underlay_.get())) { + overlay_->RegisterOverlayPref(overlay_key); + overlay_->RegisterOverlayPref(mapped_overlay_key, mapped_underlay_key); + } + + virtual ~OverlayUserPrefStoreTest() {} + + scoped_refptr<TestingPrefStore> underlay_; + scoped_refptr<OverlayUserPrefStore> overlay_; +}; + +TEST_F(OverlayUserPrefStoreTest, Observer) { + PrefStoreObserverMock obs; + overlay_->AddObserver(&obs); + + // Check that underlay first value is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(1); + underlay_->SetValue(overlay_key, new FundamentalValue(42)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that underlay overwriting is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(1); + underlay_->SetValue(overlay_key, new FundamentalValue(43)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that overwriting change in overlay is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(1); + overlay_->SetValue(overlay_key, new FundamentalValue(44)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that hidden underlay change is not reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(0); + underlay_->SetValue(overlay_key, new FundamentalValue(45)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that overlay remove is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(1); + overlay_->RemoveValue(overlay_key); + Mock::VerifyAndClearExpectations(&obs); + + // Check that underlay remove is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(1); + underlay_->RemoveValue(overlay_key); + Mock::VerifyAndClearExpectations(&obs); + + // Check respecting of silence. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(0); + overlay_->SetValueSilently(overlay_key, new FundamentalValue(46)); + Mock::VerifyAndClearExpectations(&obs); + + overlay_->RemoveObserver(&obs); + + // Check successful unsubscription. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(overlay_key))).Times(0); + underlay_->SetValue(overlay_key, new FundamentalValue(47)); + overlay_->SetValue(overlay_key, new FundamentalValue(48)); + Mock::VerifyAndClearExpectations(&obs); +} + +TEST_F(OverlayUserPrefStoreTest, GetAndSet) { + const Value* value = NULL; + EXPECT_FALSE(overlay_->GetValue(overlay_key, &value)); + EXPECT_FALSE(underlay_->GetValue(overlay_key, &value)); + + underlay_->SetValue(overlay_key, new FundamentalValue(42)); + + // Value shines through: + EXPECT_TRUE(overlay_->GetValue(overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(42).Equals(value)); + + EXPECT_TRUE(underlay_->GetValue(overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(42).Equals(value)); + + overlay_->SetValue(overlay_key, new FundamentalValue(43)); + + EXPECT_TRUE(overlay_->GetValue(overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(43).Equals(value)); + + EXPECT_TRUE(underlay_->GetValue(overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(42).Equals(value)); + + overlay_->RemoveValue(overlay_key); + + // Value shines through: + EXPECT_TRUE(overlay_->GetValue(overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(42).Equals(value)); + + EXPECT_TRUE(underlay_->GetValue(overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(42).Equals(value)); +} + +// Check that GetMutableValue does not return the dictionary of the underlay. +TEST_F(OverlayUserPrefStoreTest, ModifyDictionaries) { + underlay_->SetValue(overlay_key, new DictionaryValue); + + Value* modify = NULL; + EXPECT_TRUE(overlay_->GetMutableValue(overlay_key, &modify)); + ASSERT_TRUE(modify); + ASSERT_TRUE(modify->IsType(Value::TYPE_DICTIONARY)); + static_cast<DictionaryValue*>(modify)->SetInteger(overlay_key, 42); + + Value* original_in_underlay = NULL; + EXPECT_TRUE(underlay_->GetMutableValue(overlay_key, &original_in_underlay)); + ASSERT_TRUE(original_in_underlay); + ASSERT_TRUE(original_in_underlay->IsType(Value::TYPE_DICTIONARY)); + EXPECT_TRUE(static_cast<DictionaryValue*>(original_in_underlay)->empty()); + + Value* modified = NULL; + EXPECT_TRUE(overlay_->GetMutableValue(overlay_key, &modified)); + ASSERT_TRUE(modified); + ASSERT_TRUE(modified->IsType(Value::TYPE_DICTIONARY)); + EXPECT_TRUE(Value::Equals(modify, static_cast<DictionaryValue*>(modified))); +} + +// Here we consider a global preference that is not overlayed. +TEST_F(OverlayUserPrefStoreTest, GlobalPref) { + PrefStoreObserverMock obs; + overlay_->AddObserver(&obs); + + const Value* value = NULL; + + // Check that underlay first value is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(regular_key))).Times(1); + underlay_->SetValue(regular_key, new FundamentalValue(42)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that underlay overwriting is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(regular_key))).Times(1); + underlay_->SetValue(regular_key, new FundamentalValue(43)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that we get this value from the overlay + EXPECT_TRUE(overlay_->GetValue(regular_key, &value)); + EXPECT_TRUE(base::FundamentalValue(43).Equals(value)); + + // Check that overwriting change in overlay is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(regular_key))).Times(1); + overlay_->SetValue(regular_key, new FundamentalValue(44)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that we get this value from the overlay and the underlay. + EXPECT_TRUE(overlay_->GetValue(regular_key, &value)); + EXPECT_TRUE(base::FundamentalValue(44).Equals(value)); + EXPECT_TRUE(underlay_->GetValue(regular_key, &value)); + EXPECT_TRUE(base::FundamentalValue(44).Equals(value)); + + // Check that overlay remove is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(regular_key))).Times(1); + overlay_->RemoveValue(regular_key); + Mock::VerifyAndClearExpectations(&obs); + + // Check that value was removed from overlay and underlay + EXPECT_FALSE(overlay_->GetValue(regular_key, &value)); + EXPECT_FALSE(underlay_->GetValue(regular_key, &value)); + + // Check respecting of silence. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(regular_key))).Times(0); + overlay_->SetValueSilently(regular_key, new FundamentalValue(46)); + Mock::VerifyAndClearExpectations(&obs); + + overlay_->RemoveObserver(&obs); + + // Check successful unsubscription. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(regular_key))).Times(0); + underlay_->SetValue(regular_key, new FundamentalValue(47)); + overlay_->SetValue(regular_key, new FundamentalValue(48)); + Mock::VerifyAndClearExpectations(&obs); +} + +// Check that names mapping works correctly. +TEST_F(OverlayUserPrefStoreTest, NamesMapping) { + PrefStoreObserverMock obs; + overlay_->AddObserver(&obs); + + const Value* value = NULL; + + // Check that if there is no override in the overlay, changing underlay value + // is reported as changing an overlay value. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(1); + underlay_->SetValue(mapped_underlay_key, new FundamentalValue(42)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that underlay overwriting is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(1); + underlay_->SetValue(mapped_underlay_key, new FundamentalValue(43)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that we get this value from the overlay with both keys + EXPECT_TRUE(overlay_->GetValue(mapped_overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(43).Equals(value)); + // In this case, overlay reads directly from the underlay. + EXPECT_TRUE(overlay_->GetValue(mapped_underlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(43).Equals(value)); + + // Check that overwriting change in overlay is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(1); + overlay_->SetValue(mapped_overlay_key, new FundamentalValue(44)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that we get an overriden value from overlay, while reading the + // value from underlay still holds an old value. + EXPECT_TRUE(overlay_->GetValue(mapped_overlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(44).Equals(value)); + EXPECT_TRUE(overlay_->GetValue(mapped_underlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(43).Equals(value)); + EXPECT_TRUE(underlay_->GetValue(mapped_underlay_key, &value)); + EXPECT_TRUE(base::FundamentalValue(43).Equals(value)); + + // Check that hidden underlay change is not reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(0); + underlay_->SetValue(mapped_underlay_key, new FundamentalValue(45)); + Mock::VerifyAndClearExpectations(&obs); + + // Check that overlay remove is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(1); + overlay_->RemoveValue(mapped_overlay_key); + Mock::VerifyAndClearExpectations(&obs); + + // Check that underlay remove is reported. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(1); + underlay_->RemoveValue(mapped_underlay_key); + Mock::VerifyAndClearExpectations(&obs); + + // Check that value was removed. + EXPECT_FALSE(overlay_->GetValue(mapped_overlay_key, &value)); + EXPECT_FALSE(overlay_->GetValue(mapped_underlay_key, &value)); + + // Check respecting of silence. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(0); + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_underlay_key))).Times(0); + overlay_->SetValueSilently(mapped_overlay_key, new FundamentalValue(46)); + Mock::VerifyAndClearExpectations(&obs); + + overlay_->RemoveObserver(&obs); + + // Check successful unsubscription. + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_overlay_key))).Times(0); + EXPECT_CALL(obs, OnPrefValueChanged(StrEq(mapped_underlay_key))).Times(0); + underlay_->SetValue(mapped_underlay_key, new FundamentalValue(47)); + overlay_->SetValue(mapped_overlay_key, new FundamentalValue(48)); + Mock::VerifyAndClearExpectations(&obs); +} + +} // namespace base
diff --git a/src/base/prefs/persistent_pref_store.h b/src/base/prefs/persistent_pref_store.h new file mode 100644 index 0000000..0baf02a --- /dev/null +++ b/src/base/prefs/persistent_pref_store.h
@@ -0,0 +1,95 @@ +// 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_PERSISTENT_PREF_STORE_H_ +#define BASE_PREFS_PERSISTENT_PREF_STORE_H_ + +#include <string> + +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/pref_store.h" + +// This interface is complementary to the PrefStore interface, declaring +// additional functionality that adds support for setting values and persisting +// the data to some backing store. +class BASE_PREFS_EXPORT PersistentPrefStore : public PrefStore { + public: + // Unique integer code for each type of error so we can report them + // distinctly in a histogram. + // NOTE: Don't change the order here as it will change the server's meaning + // of the histogram. + enum PrefReadError { + PREF_READ_ERROR_NONE = 0, + PREF_READ_ERROR_JSON_PARSE, + PREF_READ_ERROR_JSON_TYPE, + PREF_READ_ERROR_ACCESS_DENIED, + PREF_READ_ERROR_FILE_OTHER, + PREF_READ_ERROR_FILE_LOCKED, + PREF_READ_ERROR_NO_FILE, + PREF_READ_ERROR_JSON_REPEAT, + PREF_READ_ERROR_OTHER, + PREF_READ_ERROR_FILE_NOT_SPECIFIED, + PREF_READ_ERROR_MAX_ENUM + }; + + class ReadErrorDelegate { + public: + virtual ~ReadErrorDelegate() {} + + virtual void OnError(PrefReadError error) = 0; + }; + + // Equivalent to PrefStore::GetValue but returns a mutable value. + virtual bool GetMutableValue(const std::string& key, + base::Value** result) = 0; + + // Triggers a value changed notification. This function needs to be called + // if one retrieves a list or dictionary with GetMutableValue and change its + // value. SetValue takes care of notifications itself. Note that + // ReportValueChanged will trigger notifications even if nothing has changed. + virtual void ReportValueChanged(const std::string& key) = 0; + + // Sets a |value| for |key| in the store. Assumes ownership of |value|, which + // must be non-NULL. + virtual void SetValue(const std::string& key, base::Value* value) = 0; + + // Same as SetValue, but doesn't generate notifications. This is used by + // PrefService::GetMutableUserPref() in order to put empty entries + // into the user pref store. Using SetValue is not an option since existing + // tests rely on the number of notifications generated. + virtual void SetValueSilently(const std::string& key, base::Value* value) = 0; + + // 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. + virtual bool ReadOnly() const = 0; + + // Gets the read error. Only valid if IsInitializationComplete() returns true. + virtual PrefReadError GetReadError() const = 0; + + // Reads the preferences from disk. Notifies observers via + // "PrefStore::OnInitializationCompleted" when done. + virtual PrefReadError ReadPrefs() = 0; + + // Reads the preferences from disk asynchronously. Notifies observers via + // "PrefStore::OnInitializationCompleted" when done. Also it fires + // |error_delegate| if it is not NULL and reading error has occurred. + // Owns |error_delegate|. + virtual void ReadPrefsAsync(ReadErrorDelegate* error_delegate) = 0; + + // Lands any pending writes to disk. + virtual void CommitPendingWrite() = 0; + + protected: + virtual ~PersistentPrefStore() {} +}; + +#endif // BASE_PREFS_PERSISTENT_PREF_STORE_H_
diff --git a/src/base/prefs/pref_notifier.h b/src/base/prefs/pref_notifier.h new file mode 100644 index 0000000..e0df260 --- /dev/null +++ b/src/base/prefs/pref_notifier.h
@@ -0,0 +1,26 @@ +// 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. + +#ifndef BASE_PREFS_PREF_NOTIFIER_H_ +#define BASE_PREFS_PREF_NOTIFIER_H_ + +#include <string> + +// Delegate interface used by PrefValueStore to notify its owner about changes +// to the preference values. +// TODO(mnissler, danno): Move this declaration to pref_value_store.h once we've +// cleaned up all public uses of this interface. +class PrefNotifier { + public: + virtual ~PrefNotifier() {} + + // Sends out a change notification for the preference identified by + // |pref_name|. + virtual void OnPreferenceChanged(const std::string& pref_name) = 0; + + // Broadcasts the intialization completed notification. + virtual void OnInitializationCompleted(bool succeeded) = 0; +}; + +#endif // BASE_PREFS_PREF_NOTIFIER_H_
diff --git a/src/base/prefs/pref_observer.h b/src/base/prefs/pref_observer.h new file mode 100644 index 0000000..93a0454 --- /dev/null +++ b/src/base/prefs/pref_observer.h
@@ -0,0 +1,19 @@ +// 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_OBSERVER_H_ +#define BASE_PREFS_PREF_OBSERVER_H_ + +#include <string> + +class PrefServiceBase; + +// TODO(joi): Switch to base::Callback and remove this. +class PrefObserver { + public: + virtual void OnPreferenceChanged(PrefServiceBase* service, + const std::string& pref_name) = 0; +}; + +#endif // BASE_PREFS_PREF_OBSERVER_H_
diff --git a/src/base/prefs/pref_store.cc b/src/base/prefs/pref_store.cc new file mode 100644 index 0000000..0521654 --- /dev/null +++ b/src/base/prefs/pref_store.cc
@@ -0,0 +1,13 @@ +// 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_store.h" + +size_t PrefStore::NumberOfObservers() const { + return 0; +} + +bool PrefStore::IsInitializationComplete() const { + return true; +}
diff --git a/src/base/prefs/pref_store.h b/src/base/prefs/pref_store.h new file mode 100644 index 0000000..2239528 --- /dev/null +++ b/src/base/prefs/pref_store.h
@@ -0,0 +1,63 @@ +// 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_STORE_H_ +#define BASE_PREFS_PREF_STORE_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/prefs/base_prefs_export.h" + +namespace base { +class Value; +} + +// This is an abstract interface for reading and writing from/to a persistent +// preference store, used by PrefService. An implementation using a JSON file +// can be found in JsonPrefStore, while an implementation without any backing +// store for testing can be found in TestingPrefStore. Furthermore, there is +// CommandLinePrefStore, which bridges command line options to preferences and +// ConfigurationPolicyPrefStore, which is used for hooking up configuration +// policy with the preference subsystem. +class BASE_PREFS_EXPORT PrefStore : public base::RefCounted<PrefStore> { + public: + // Observer interface for monitoring PrefStore. + class BASE_PREFS_EXPORT Observer { + public: + // Called when the value for the given |key| in the store changes. + virtual void OnPrefValueChanged(const std::string& key) = 0; + // Notification about the PrefStore being fully initialized. + virtual void OnInitializationCompleted(bool succeeded) = 0; + + protected: + virtual ~Observer() {} + }; + + PrefStore() {} + + // Add and remove observers. + virtual void AddObserver(Observer* observer) {} + virtual void RemoveObserver(Observer* observer) {} + virtual size_t NumberOfObservers() const; + + // Whether the store has completed all asynchronous initialization. + virtual bool IsInitializationComplete() const; + + // Get the value for a given preference |key| and stores it in |*result|. + // |*result| is only modified if the return value is true and if |result| + // is not NULL. Ownership of the |*result| value remains with the PrefStore. + virtual bool GetValue(const std::string& key, + const base::Value** result) const = 0; + + protected: + friend class base::RefCounted<PrefStore>; + virtual ~PrefStore() {} + + private: + DISALLOW_COPY_AND_ASSIGN(PrefStore); +}; + +#endif // BASE_PREFS_PREF_STORE_H_
diff --git a/src/base/prefs/pref_store_observer_mock.cc b/src/base/prefs/pref_store_observer_mock.cc new file mode 100644 index 0000000..0970e63 --- /dev/null +++ b/src/base/prefs/pref_store_observer_mock.cc
@@ -0,0 +1,9 @@ +// 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/prefs/pref_store_observer_mock.h" + +PrefStoreObserverMock::PrefStoreObserverMock() {} + +PrefStoreObserverMock::~PrefStoreObserverMock() {}
diff --git a/src/base/prefs/pref_store_observer_mock.h b/src/base/prefs/pref_store_observer_mock.h new file mode 100644 index 0000000..8252c3b --- /dev/null +++ b/src/base/prefs/pref_store_observer_mock.h
@@ -0,0 +1,25 @@ +// 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. + +#ifndef BASE_PREFS_PREF_STORE_OBSERVER_MOCK_H_ +#define BASE_PREFS_PREF_STORE_OBSERVER_MOCK_H_ + +#include "base/basictypes.h" +#include "base/prefs/pref_store.h" +#include "testing/gmock/include/gmock/gmock.h" + +// A gmock-ified implementation of PrefStore::Observer. +class PrefStoreObserverMock : public PrefStore::Observer { + public: + PrefStoreObserverMock(); + virtual ~PrefStoreObserverMock(); + + MOCK_METHOD1(OnPrefValueChanged, void(const std::string&)); + MOCK_METHOD1(OnInitializationCompleted, void(bool)); + + private: + DISALLOW_COPY_AND_ASSIGN(PrefStoreObserverMock); +}; + +#endif // BASE_PREFS_PREF_STORE_OBSERVER_MOCK_H_
diff --git a/src/base/prefs/pref_value_map.cc b/src/base/prefs/pref_value_map.cc new file mode 100644 index 0000000..43d2a4c --- /dev/null +++ b/src/base/prefs/pref_value_map.cc
@@ -0,0 +1,151 @@ +// 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/prefs/pref_value_map.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "base/values.h" + +PrefValueMap::PrefValueMap() {} + +PrefValueMap::~PrefValueMap() { + Clear(); +} + +bool PrefValueMap::GetValue(const std::string& key, const Value** value) const { + const Map::const_iterator entry = prefs_.find(key); + if (entry != prefs_.end()) { + if (value) + *value = entry->second; + return true; + } + + return false; +} + +bool PrefValueMap::GetValue(const std::string& key, Value** value) { + const Map::const_iterator entry = prefs_.find(key); + if (entry != prefs_.end()) { + if (value) + *value = entry->second; + return true; + } + + return false; +} + +bool PrefValueMap::SetValue(const std::string& key, Value* value) { + DCHECK(value); + scoped_ptr<Value> value_ptr(value); + const Map::iterator entry = prefs_.find(key); + if (entry != prefs_.end()) { + if (Value::Equals(entry->second, value)) + return false; + delete entry->second; + entry->second = value_ptr.release(); + } else { + prefs_[key] = value_ptr.release(); + } + + return true; +} + +bool PrefValueMap::RemoveValue(const std::string& key) { + const Map::iterator entry = prefs_.find(key); + if (entry != prefs_.end()) { + delete entry->second; + prefs_.erase(entry); + return true; + } + + return false; +} + +void PrefValueMap::Clear() { + STLDeleteValues(&prefs_); + prefs_.clear(); +} + +void PrefValueMap::Swap(PrefValueMap* other) { + prefs_.swap(other->prefs_); +} + +PrefValueMap::iterator PrefValueMap::begin() { + return prefs_.begin(); +} + +PrefValueMap::iterator PrefValueMap::end() { + return prefs_.end(); +} + +PrefValueMap::const_iterator PrefValueMap::begin() const { + return prefs_.begin(); +} + +PrefValueMap::const_iterator PrefValueMap::end() const { + return prefs_.end(); +} + +bool PrefValueMap::GetBoolean(const std::string& key, + bool* value) const { + const Value* stored_value = NULL; + return GetValue(key, &stored_value) && stored_value->GetAsBoolean(value); +} + +void PrefValueMap::SetBoolean(const std::string& key, bool value) { + SetValue(key, new base::FundamentalValue(value)); +} + +bool PrefValueMap::GetString(const std::string& key, + std::string* value) const { + const Value* stored_value = NULL; + return GetValue(key, &stored_value) && stored_value->GetAsString(value); +} + +void PrefValueMap::SetString(const std::string& key, + const std::string& value) { + SetValue(key, new base::StringValue(value)); +} + +bool PrefValueMap::GetInteger(const std::string& key, int* value) const { + const Value* stored_value = NULL; + return GetValue(key, &stored_value) && stored_value->GetAsInteger(value); +} + +void PrefValueMap::SetInteger(const std::string& key, const int value) { + SetValue(key, new base::FundamentalValue(value)); +} + +void PrefValueMap::GetDifferingKeys( + const PrefValueMap* other, + std::vector<std::string>* differing_keys) const { + differing_keys->clear(); + + // Walk over the maps in lockstep, adding everything that is different. + Map::const_iterator this_pref(prefs_.begin()); + Map::const_iterator other_pref(other->prefs_.begin()); + while (this_pref != prefs_.end() && other_pref != other->prefs_.end()) { + const int diff = this_pref->first.compare(other_pref->first); + if (diff == 0) { + if (!this_pref->second->Equals(other_pref->second)) + differing_keys->push_back(this_pref->first); + ++this_pref; + ++other_pref; + } else if (diff < 0) { + differing_keys->push_back(this_pref->first); + ++this_pref; + } else if (diff > 0) { + differing_keys->push_back(other_pref->first); + ++other_pref; + } + } + + // Add the remaining entries. + for ( ; this_pref != prefs_.end(); ++this_pref) + differing_keys->push_back(this_pref->first); + for ( ; other_pref != other->prefs_.end(); ++other_pref) + differing_keys->push_back(other_pref->first); +}
diff --git a/src/base/prefs/pref_value_map.h b/src/base/prefs/pref_value_map.h new file mode 100644 index 0000000..1d79127 --- /dev/null +++ b/src/base/prefs/pref_value_map.h
@@ -0,0 +1,88 @@ +// 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. + +#ifndef BASE_PREFS_PREF_VALUE_MAP_H_ +#define BASE_PREFS_PREF_VALUE_MAP_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/prefs/base_prefs_export.h" + +namespace base { +class Value; +} + +// A generic string to value map used by the PrefStore implementations. +class BASE_PREFS_EXPORT PrefValueMap { + public: + typedef std::map<std::string, base::Value*>::iterator iterator; + typedef std::map<std::string, base::Value*>::const_iterator const_iterator; + + PrefValueMap(); + virtual ~PrefValueMap(); + + // Gets the value for |key| and stores it in |value|. Ownership remains with + // the map. Returns true if a value is present. If not, |value| is not + // touched. + bool GetValue(const std::string& key, const base::Value** value) const; + bool GetValue(const std::string& key, base::Value** value); + + // Sets a new |value| for |key|. Takes ownership of |value|, which must be + // non-NULL. Returns true if the value changed. + bool SetValue(const std::string& key, base::Value* value); + + // Removes the value for |key| from the map. Returns true if a value was + // removed. + bool RemoveValue(const std::string& key); + + // Clears the map. + void Clear(); + + // Swaps the contents of two maps. + void Swap(PrefValueMap* other); + + iterator begin(); + iterator end(); + const_iterator begin() const; + const_iterator end() const; + + // Gets a boolean value for |key| and stores it in |value|. Returns true if + // the value was found and of the proper type. + bool GetBoolean(const std::string& key, bool* value) const; + + // Sets the value for |key| to the boolean |value|. + void SetBoolean(const std::string& key, bool value); + + // Gets a string value for |key| and stores it in |value|. Returns true if + // the value was found and of the proper type. + bool GetString(const std::string& key, std::string* value) const; + + // Sets the value for |key| to the string |value|. + void SetString(const std::string& key, const std::string& value); + + // Gets an int value for |key| and stores it in |value|. Returns true if + // the value was found and of the proper type. + bool GetInteger(const std::string& key, int* value) const; + + // Sets the value for |key| to the int |value|. + void SetInteger(const std::string& key, const int value); + + // Compares this value map against |other| and stores all key names that have + // different values in |differing_keys|. This includes keys that are present + // only in one of the maps. + void GetDifferingKeys(const PrefValueMap* other, + std::vector<std::string>* differing_keys) const; + + private: + typedef std::map<std::string, base::Value*> Map; + + Map prefs_; + + DISALLOW_COPY_AND_ASSIGN(PrefValueMap); +}; + +#endif // BASE_PREFS_PREF_VALUE_MAP_H_
diff --git a/src/base/prefs/pref_value_map_unittest.cc b/src/base/prefs/pref_value_map_unittest.cc new file mode 100644 index 0000000..8cc51ad --- /dev/null +++ b/src/base/prefs/pref_value_map_unittest.cc
@@ -0,0 +1,115 @@ +// 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/prefs/pref_value_map.h" + +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +TEST(PrefValueMapTest, SetValue) { + PrefValueMap map; + const Value* result = NULL; + EXPECT_FALSE(map.GetValue("key", &result)); + EXPECT_FALSE(result); + + EXPECT_TRUE(map.SetValue("key", new StringValue("test"))); + EXPECT_FALSE(map.SetValue("key", new StringValue("test"))); + EXPECT_TRUE(map.SetValue("key", new StringValue("hi mom!"))); + + EXPECT_TRUE(map.GetValue("key", &result)); + EXPECT_TRUE(StringValue("hi mom!").Equals(result)); +} + +TEST(PrefValueMapTest, GetAndSetIntegerValue) { + PrefValueMap map; + ASSERT_TRUE(map.SetValue("key", new FundamentalValue(5))); + + int int_value = 0; + EXPECT_TRUE(map.GetInteger("key", &int_value)); + EXPECT_EQ(5, int_value); + + map.SetInteger("key", -14); + EXPECT_TRUE(map.GetInteger("key", &int_value)); + EXPECT_EQ(-14, int_value); +} + +TEST(PrefValueMapTest, RemoveValue) { + PrefValueMap map; + EXPECT_FALSE(map.RemoveValue("key")); + + EXPECT_TRUE(map.SetValue("key", new StringValue("test"))); + EXPECT_TRUE(map.GetValue("key", NULL)); + + EXPECT_TRUE(map.RemoveValue("key")); + EXPECT_FALSE(map.GetValue("key", NULL)); + + EXPECT_FALSE(map.RemoveValue("key")); +} + +TEST(PrefValueMapTest, Clear) { + PrefValueMap map; + EXPECT_TRUE(map.SetValue("key", new StringValue("test"))); + EXPECT_TRUE(map.GetValue("key", NULL)); + + map.Clear(); + + EXPECT_FALSE(map.GetValue("key", NULL)); +} + +TEST(PrefValueMapTest, GetDifferingKeys) { + PrefValueMap reference; + EXPECT_TRUE(reference.SetValue("b", new StringValue("test"))); + EXPECT_TRUE(reference.SetValue("c", new StringValue("test"))); + EXPECT_TRUE(reference.SetValue("e", new StringValue("test"))); + + PrefValueMap check; + std::vector<std::string> differing_paths; + std::vector<std::string> expected_differing_paths; + + reference.GetDifferingKeys(&check, &differing_paths); + expected_differing_paths.push_back("b"); + expected_differing_paths.push_back("c"); + expected_differing_paths.push_back("e"); + EXPECT_EQ(expected_differing_paths, differing_paths); + + EXPECT_TRUE(check.SetValue("a", new StringValue("test"))); + EXPECT_TRUE(check.SetValue("c", new StringValue("test"))); + EXPECT_TRUE(check.SetValue("d", new StringValue("test"))); + + reference.GetDifferingKeys(&check, &differing_paths); + expected_differing_paths.clear(); + expected_differing_paths.push_back("a"); + expected_differing_paths.push_back("b"); + expected_differing_paths.push_back("d"); + expected_differing_paths.push_back("e"); + EXPECT_EQ(expected_differing_paths, differing_paths); +} + +TEST(PrefValueMapTest, SwapTwoMaps) { + PrefValueMap first_map; + EXPECT_TRUE(first_map.SetValue("a", new StringValue("test"))); + EXPECT_TRUE(first_map.SetValue("b", new StringValue("test"))); + EXPECT_TRUE(first_map.SetValue("c", new StringValue("test"))); + + PrefValueMap second_map; + EXPECT_TRUE(second_map.SetValue("d", new StringValue("test"))); + EXPECT_TRUE(second_map.SetValue("e", new StringValue("test"))); + EXPECT_TRUE(second_map.SetValue("f", new StringValue("test"))); + + first_map.Swap(&second_map); + + EXPECT_TRUE(first_map.GetValue("d", NULL)); + EXPECT_TRUE(first_map.GetValue("e", NULL)); + EXPECT_TRUE(first_map.GetValue("f", NULL)); + + EXPECT_TRUE(second_map.GetValue("a", NULL)); + EXPECT_TRUE(second_map.GetValue("b", NULL)); + EXPECT_TRUE(second_map.GetValue("c", NULL)); +} + +} // namespace +} // namespace base
diff --git a/src/base/prefs/public/pref_change_registrar.cc b/src/base/prefs/public/pref_change_registrar.cc new file mode 100644 index 0000000..681d912 --- /dev/null +++ b/src/base/prefs/public/pref_change_registrar.cc
@@ -0,0 +1,91 @@ +// Copyright (c) 2010 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/public/pref_change_registrar.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/prefs/public/pref_service_base.h" + +PrefChangeRegistrar::PrefChangeRegistrar() : service_(NULL) {} + +PrefChangeRegistrar::~PrefChangeRegistrar() { + // If you see an invalid memory access in this destructor, this + // PrefChangeRegistrar might be subscribed to an OffTheRecordProfileImpl that + // has been destroyed. This should not happen any more but be warned. + // Feel free to contact battre@chromium.org in case this happens. + RemoveAll(); +} + +void PrefChangeRegistrar::Init(PrefServiceBase* service) { + DCHECK(IsEmpty() || service_ == service); + service_ = service; +} + +void PrefChangeRegistrar::Add(const char* path, + const base::Closure& obs) { + Add(path, base::Bind(&PrefChangeRegistrar::InvokeUnnamedCallback, obs)); +} + +void PrefChangeRegistrar::Add(const char* path, + const NamedChangeCallback& obs) { + if (!service_) { + NOTREACHED(); + return; + } + DCHECK(!IsObserved(path)) << "Already had this pref registered."; + + service_->AddPrefObserver(path, this); + observers_[path] = obs; +} + +void PrefChangeRegistrar::Remove(const char* path) { + DCHECK(IsObserved(path)); + + observers_.erase(path); + service_->RemovePrefObserver(path, this); +} + +void PrefChangeRegistrar::RemoveAll() { + for (ObserverMap::const_iterator it = observers_.begin(); + it != observers_.end(); ++it) { + service_->RemovePrefObserver(it->first.c_str(), this); + } + + observers_.clear(); +} + +bool PrefChangeRegistrar::IsEmpty() const { + return observers_.empty(); +} + +bool PrefChangeRegistrar::IsObserved(const std::string& pref) { + return observers_.find(pref) != observers_.end(); +} + +bool PrefChangeRegistrar::IsManaged() { + for (ObserverMap::const_iterator it = observers_.begin(); + it != observers_.end(); ++it) { + const PrefServiceBase::Preference* pref = + service_->FindPreference(it->first.c_str()); + if (pref && pref->IsManaged()) + return true; + } + return false; +} + +void PrefChangeRegistrar::OnPreferenceChanged(PrefServiceBase* service, + const std::string& pref) { + if (IsObserved(pref)) + observers_[pref].Run(pref); +} + +void PrefChangeRegistrar::InvokeUnnamedCallback(const base::Closure& callback, + const std::string& pref_name) { + callback.Run(); +} + +PrefServiceBase* PrefChangeRegistrar::prefs() { + return service_; +}
diff --git a/src/base/prefs/public/pref_change_registrar.h b/src/base/prefs/public/pref_change_registrar.h new file mode 100644 index 0000000..84df652 --- /dev/null +++ b/src/base/prefs/public/pref_change_registrar.h
@@ -0,0 +1,80 @@ +// Copyright (c) 2010 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_PUBLIC_PREF_CHANGE_REGISTRAR_H_ +#define BASE_PREFS_PUBLIC_PREF_CHANGE_REGISTRAR_H_ + +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/pref_observer.h" + +class PrefServiceBase; + +// Automatically manages the registration of one or more pref change observers +// with a PrefStore. Functions much like NotificationRegistrar, but specifically +// manages observers of preference changes. When the Registrar is destroyed, +// all registered observers are automatically unregistered with the PrefStore. +class BASE_PREFS_EXPORT PrefChangeRegistrar : public PrefObserver { + public: + // You can register this type of callback if you need to know the + // path of the preference that is changing. + typedef base::Callback<void(const std::string&)> NamedChangeCallback; + + PrefChangeRegistrar(); + virtual ~PrefChangeRegistrar(); + + // Must be called before adding or removing observers. Can be called more + // than once as long as the value of |service| doesn't change. + void Init(PrefServiceBase* service); + + // Adds a pref observer for the specified pref |path| and |obs| observer + // object. All registered observers will be automatically unregistered + // when the registrar's destructor is called. + // + // The second version binds a callback that will receive the path of + // the preference that is changing as its parameter. + // + // Only one observer may be registered per path. + void Add(const char* path, const base::Closure& obs); + void Add(const char* path, const NamedChangeCallback& obs); + + // Removes the pref observer registered for |path|. + void Remove(const char* path); + + // Removes all observers that have been previously added with a call to Add. + void RemoveAll(); + + // Returns true if no pref observers are registered. + bool IsEmpty() const; + + // Check whether |pref| is in the set of preferences being observed. + bool IsObserved(const std::string& pref); + + // Check whether any of the observed preferences has the managed bit set. + bool IsManaged(); + + // Return the PrefServiceBase for this registrar. + PrefServiceBase* prefs(); + + private: + // PrefObserver: + virtual void OnPreferenceChanged(PrefServiceBase* service, + const std::string& pref_name) OVERRIDE; + + static void InvokeUnnamedCallback(const base::Closure& callback, + const std::string& pref_name); + + typedef std::map<std::string, NamedChangeCallback> ObserverMap; + + ObserverMap observers_; + PrefServiceBase* service_; + + DISALLOW_COPY_AND_ASSIGN(PrefChangeRegistrar); +}; + +#endif // BASE_PREFS_PUBLIC_PREF_CHANGE_REGISTRAR_H_
diff --git a/src/base/prefs/public/pref_change_registrar_unittest.cc b/src/base/prefs/public/pref_change_registrar_unittest.cc new file mode 100644 index 0000000..d194c36 --- /dev/null +++ b/src/base/prefs/public/pref_change_registrar_unittest.cc
@@ -0,0 +1,204 @@ +// Copyright (c) 2010 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/bind.h" +#include "base/bind_helpers.h" +#include "base/prefs/pref_observer.h" +#include "base/prefs/public/pref_change_registrar.h" +#include "chrome/test/base/testing_pref_service.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::Mock; +using testing::Eq; + +namespace base { +namespace { + +const char kHomePage[] = "homepage"; +const char kHomePageIsNewTabPage[] = "homepage_is_newtabpage"; +const char kApplicationLocale[] = "intl.app_locale"; + +// A mock provider that allows us to capture pref observer changes. +class MockPrefService : public TestingPrefService { + public: + MockPrefService() {} + virtual ~MockPrefService() {} + + MOCK_METHOD2(AddPrefObserver, + void(const char*, PrefObserver*)); + MOCK_METHOD2(RemovePrefObserver, + void(const char*, PrefObserver*)); +}; + +} // namespace + +class PrefChangeRegistrarTest : public testing::Test { + public: + PrefChangeRegistrarTest() {} + virtual ~PrefChangeRegistrarTest() {} + + protected: + virtual void SetUp() OVERRIDE; + + base::Closure observer() const { + return base::Bind(&base::DoNothing); + } + + MockPrefService* service() const { return service_.get(); } + + private: + scoped_ptr<MockPrefService> service_; +}; + +void PrefChangeRegistrarTest::SetUp() { + service_.reset(new MockPrefService()); +} + +TEST_F(PrefChangeRegistrarTest, AddAndRemove) { + PrefChangeRegistrar registrar; + registrar.Init(service()); + + // Test adding. + EXPECT_CALL(*service(), + AddPrefObserver(Eq(std::string("test.pref.1")), ®istrar)); + EXPECT_CALL(*service(), + AddPrefObserver(Eq(std::string("test.pref.2")), ®istrar)); + registrar.Add("test.pref.1", observer()); + registrar.Add("test.pref.2", observer()); + EXPECT_FALSE(registrar.IsEmpty()); + + // Test removing. + Mock::VerifyAndClearExpectations(service()); + EXPECT_CALL(*service(), + RemovePrefObserver(Eq(std::string("test.pref.1")), ®istrar)); + EXPECT_CALL(*service(), + RemovePrefObserver(Eq(std::string("test.pref.2")), ®istrar)); + registrar.Remove("test.pref.1"); + registrar.Remove("test.pref.2"); + EXPECT_TRUE(registrar.IsEmpty()); + + // Explicitly check the expectations now to make sure that the Removes + // worked (rather than the registrar destructor doing the work). + Mock::VerifyAndClearExpectations(service()); +} + +TEST_F(PrefChangeRegistrarTest, AutoRemove) { + PrefChangeRegistrar registrar; + registrar.Init(service()); + + // Setup of auto-remove. + EXPECT_CALL(*service(), + AddPrefObserver(Eq(std::string("test.pref.1")), ®istrar)); + registrar.Add("test.pref.1", observer()); + Mock::VerifyAndClearExpectations(service()); + EXPECT_FALSE(registrar.IsEmpty()); + + // Test auto-removing. + EXPECT_CALL(*service(), + RemovePrefObserver(Eq(std::string("test.pref.1")), ®istrar)); +} + +TEST_F(PrefChangeRegistrarTest, RemoveAll) { + PrefChangeRegistrar registrar; + registrar.Init(service()); + + EXPECT_CALL(*service(), + AddPrefObserver(Eq(std::string("test.pref.1")), ®istrar)); + EXPECT_CALL(*service(), + AddPrefObserver(Eq(std::string("test.pref.2")), ®istrar)); + registrar.Add("test.pref.1", observer()); + registrar.Add("test.pref.2", observer()); + Mock::VerifyAndClearExpectations(service()); + + EXPECT_CALL(*service(), + RemovePrefObserver(Eq(std::string("test.pref.1")), ®istrar)); + EXPECT_CALL(*service(), + RemovePrefObserver(Eq(std::string("test.pref.2")), ®istrar)); + registrar.RemoveAll(); + EXPECT_TRUE(registrar.IsEmpty()); + + // Explicitly check the expectations now to make sure that the RemoveAll + // worked (rather than the registrar destructor doing the work). + Mock::VerifyAndClearExpectations(service()); +} + +class ObserveSetOfPreferencesTest : public testing::Test { + public: + virtual void SetUp() { + pref_service_.reset(new TestingPrefService); + pref_service_->RegisterStringPref(kHomePage, + "http://google.com", + PrefService::UNSYNCABLE_PREF); + pref_service_->RegisterBooleanPref(kHomePageIsNewTabPage, + false, + PrefService::UNSYNCABLE_PREF); + pref_service_->RegisterStringPref(kApplicationLocale, + "", + PrefService::UNSYNCABLE_PREF); + } + + PrefChangeRegistrar* CreatePrefChangeRegistrar() { + PrefChangeRegistrar* pref_set = new PrefChangeRegistrar(); + base::Closure callback = base::Bind(&base::DoNothing); + pref_set->Init(pref_service_.get()); + pref_set->Add(kHomePage, callback); + pref_set->Add(kHomePageIsNewTabPage, callback); + return pref_set; + } + + MOCK_METHOD1(OnPreferenceChanged, void(const std::string&)); + + scoped_ptr<TestingPrefService> pref_service_; +}; + +TEST_F(ObserveSetOfPreferencesTest, IsObserved) { + scoped_ptr<PrefChangeRegistrar> pref_set(CreatePrefChangeRegistrar()); + EXPECT_TRUE(pref_set->IsObserved(kHomePage)); + EXPECT_TRUE(pref_set->IsObserved(kHomePageIsNewTabPage)); + EXPECT_FALSE(pref_set->IsObserved(kApplicationLocale)); +} + +TEST_F(ObserveSetOfPreferencesTest, IsManaged) { + scoped_ptr<PrefChangeRegistrar> pref_set(CreatePrefChangeRegistrar()); + EXPECT_FALSE(pref_set->IsManaged()); + pref_service_->SetManagedPref(kHomePage, + new StringValue("http://crbug.com")); + EXPECT_TRUE(pref_set->IsManaged()); + pref_service_->SetManagedPref(kHomePageIsNewTabPage, + new FundamentalValue(true)); + EXPECT_TRUE(pref_set->IsManaged()); + pref_service_->RemoveManagedPref(kHomePage); + EXPECT_TRUE(pref_set->IsManaged()); + pref_service_->RemoveManagedPref(kHomePageIsNewTabPage); + EXPECT_FALSE(pref_set->IsManaged()); +} + +TEST_F(ObserveSetOfPreferencesTest, Observe) { + using testing::_; + using testing::Mock; + + PrefChangeRegistrar pref_set; + PrefChangeRegistrar::NamedChangeCallback callback = base::Bind( + &ObserveSetOfPreferencesTest::OnPreferenceChanged, + base::Unretained(this)); + pref_set.Init(pref_service_.get()); + pref_set.Add(kHomePage, callback); + pref_set.Add(kHomePageIsNewTabPage, callback); + + EXPECT_CALL(*this, OnPreferenceChanged(kHomePage)); + pref_service_->SetUserPref(kHomePage, new StringValue("http://crbug.com")); + Mock::VerifyAndClearExpectations(this); + + EXPECT_CALL(*this, OnPreferenceChanged(kHomePageIsNewTabPage)); + pref_service_->SetUserPref(kHomePageIsNewTabPage, + new FundamentalValue(true)); + Mock::VerifyAndClearExpectations(this); + + EXPECT_CALL(*this, OnPreferenceChanged(_)).Times(0); + pref_service_->SetUserPref(kApplicationLocale, new StringValue("en_US.utf8")); + Mock::VerifyAndClearExpectations(this); +} + +} // namespace base
diff --git a/src/base/prefs/public/pref_member.cc b/src/base/prefs/public/pref_member.cc new file mode 100644 index 0000000..82a1247 --- /dev/null +++ b/src/base/prefs/public/pref_member.cc
@@ -0,0 +1,220 @@ +// 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/public/pref_member.h" + +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/prefs/public/pref_service_base.h" +#include "base/value_conversions.h" + +using base::MessageLoopProxy; + +namespace subtle { + +PrefMemberBase::PrefMemberBase() + : prefs_(NULL), + setting_value_(false) { +} + +PrefMemberBase::~PrefMemberBase() { + Destroy(); +} + +void PrefMemberBase::Init(const char* pref_name, + PrefServiceBase* prefs, + const NamedChangeCallback& observer) { + observer_ = observer; + Init(pref_name, prefs); +} + +void PrefMemberBase::Init(const char* pref_name, + PrefServiceBase* prefs) { + DCHECK(pref_name); + DCHECK(prefs); + DCHECK(pref_name_.empty()); // Check that Init is only called once. + prefs_ = prefs; + pref_name_ = pref_name; + // Check that the preference is registered. + DCHECK(prefs_->FindPreference(pref_name_.c_str())) + << pref_name << " not registered."; + + // Add ourselves as a pref observer so we can keep our local value in sync. + prefs_->AddPrefObserver(pref_name, this); +} + +void PrefMemberBase::Destroy() { + if (prefs_ && !pref_name_.empty()) { + prefs_->RemovePrefObserver(pref_name_.c_str(), this); + prefs_ = NULL; + } +} + +void PrefMemberBase::MoveToThread( + const scoped_refptr<MessageLoopProxy>& message_loop) { + VerifyValuePrefName(); + // Load the value from preferences if it hasn't been loaded so far. + if (!internal()) + UpdateValueFromPref(base::Closure()); + internal()->MoveToThread(message_loop); +} + +void PrefMemberBase::OnPreferenceChanged(PrefServiceBase* service, + const std::string& pref_name) { + VerifyValuePrefName(); + UpdateValueFromPref((!setting_value_ && !observer_.is_null()) ? + base::Bind(observer_, pref_name) : base::Closure()); +} + +void PrefMemberBase::UpdateValueFromPref(const base::Closure& callback) const { + VerifyValuePrefName(); + const PrefServiceBase::Preference* pref = + prefs_->FindPreference(pref_name_.c_str()); + DCHECK(pref); + if (!internal()) + CreateInternal(); + internal()->UpdateValue(pref->GetValue()->DeepCopy(), + pref->IsManaged(), + pref->IsUserModifiable(), + callback); +} + +void PrefMemberBase::VerifyPref() const { + VerifyValuePrefName(); + if (!internal()) + UpdateValueFromPref(base::Closure()); +} + +void PrefMemberBase::InvokeUnnamedCallback(const base::Closure& callback, + const std::string& pref_name) { + callback.Run(); +} + +PrefMemberBase::Internal::Internal() + : thread_loop_(MessageLoopProxy::current()), + is_managed_(false) { +} +PrefMemberBase::Internal::~Internal() { } + +bool PrefMemberBase::Internal::IsOnCorrectThread() const { + // In unit tests, there may not be a message loop. + return thread_loop_ == NULL || thread_loop_->BelongsToCurrentThread(); +} + +void PrefMemberBase::Internal::UpdateValue( + Value* v, + bool is_managed, + bool is_user_modifiable, + const base::Closure& callback) const { + scoped_ptr<Value> value(v); + base::ScopedClosureRunner closure_runner(callback); + if (IsOnCorrectThread()) { + bool rv = UpdateValueInternal(*value); + DCHECK(rv); + is_managed_ = is_managed; + is_user_modifiable_ = is_user_modifiable; + } else { + bool may_run = thread_loop_->PostTask( + FROM_HERE, + base::Bind(&PrefMemberBase::Internal::UpdateValue, this, + value.release(), is_managed, is_user_modifiable, + closure_runner.Release())); + DCHECK(may_run); + } +} + +void PrefMemberBase::Internal::MoveToThread( + const scoped_refptr<MessageLoopProxy>& message_loop) { + CheckOnCorrectThread(); + thread_loop_ = message_loop; +} + +bool PrefMemberVectorStringUpdate(const Value& value, + std::vector<std::string>* string_vector) { + if (!value.IsType(Value::TYPE_LIST)) + return false; + const ListValue* list = static_cast<const ListValue*>(&value); + + std::vector<std::string> local_vector; + for (ListValue::const_iterator it = list->begin(); it != list->end(); ++it) { + std::string string_value; + if (!(*it)->GetAsString(&string_value)) + return false; + + local_vector.push_back(string_value); + } + + string_vector->swap(local_vector); + return true; +} + +} // namespace subtle + +template <> +void PrefMember<bool>::UpdatePref(const bool& value) { + prefs()->SetBoolean(pref_name().c_str(), value); +} + +template <> +bool PrefMember<bool>::Internal::UpdateValueInternal(const Value& value) const { + return value.GetAsBoolean(&value_); +} + +template <> +void PrefMember<int>::UpdatePref(const int& value) { + prefs()->SetInteger(pref_name().c_str(), value); +} + +template <> +bool PrefMember<int>::Internal::UpdateValueInternal(const Value& value) const { + return value.GetAsInteger(&value_); +} + +template <> +void PrefMember<double>::UpdatePref(const double& value) { + prefs()->SetDouble(pref_name().c_str(), value); +} + +template <> +bool PrefMember<double>::Internal::UpdateValueInternal(const Value& value) + const { + return value.GetAsDouble(&value_); +} + +template <> +void PrefMember<std::string>::UpdatePref(const std::string& value) { + prefs()->SetString(pref_name().c_str(), value); +} + +template <> +bool PrefMember<std::string>::Internal::UpdateValueInternal(const Value& value) + const { + return value.GetAsString(&value_); +} + +template <> +void PrefMember<FilePath>::UpdatePref(const FilePath& value) { + prefs()->SetFilePath(pref_name().c_str(), value); +} + +template <> +bool PrefMember<FilePath>::Internal::UpdateValueInternal(const Value& value) + const { + return base::GetValueAsFilePath(value, &value_); +} + +template <> +void PrefMember<std::vector<std::string> >::UpdatePref( + const std::vector<std::string>& value) { + ListValue list_value; + list_value.AppendStrings(value); + prefs()->Set(pref_name().c_str(), list_value); +} + +template <> +bool PrefMember<std::vector<std::string> >::Internal::UpdateValueInternal( + const Value& value) const { + return subtle::PrefMemberVectorStringUpdate(value, &value_); +}
diff --git a/src/base/prefs/public/pref_member.h b/src/base/prefs/public/pref_member.h new file mode 100644 index 0000000..52a91b5 --- /dev/null +++ b/src/base/prefs/public/pref_member.h
@@ -0,0 +1,352 @@ +// 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. +// +// A helper class that stays in sync with a preference (bool, int, real, +// string or filepath). For example: +// +// class MyClass { +// public: +// MyClass(PrefService* prefs) { +// my_string_.Init(prefs::kHomePage, prefs); +// } +// private: +// StringPrefMember my_string_; +// }; +// +// my_string_ should stay in sync with the prefs::kHomePage pref and will +// update if either the pref changes or if my_string_.SetValue is called. +// +// An optional observer can be passed into the Init method which can be used to +// notify MyClass of changes. Note that if you use SetValue(), the observer +// will not be notified. + +#ifndef BASE_PREFS_PUBLIC_PREF_MEMBER_H_ +#define BASE_PREFS_PUBLIC_PREF_MEMBER_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback_forward.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop_proxy.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/pref_observer.h" +#include "base/values.h" + +class PrefServiceBase; + +namespace subtle { + +class BASE_PREFS_EXPORT PrefMemberBase : public PrefObserver { + public: + // Type of callback you can register if you need to know the name of + // the pref that is changing. + typedef base::Callback<void(const std::string&)> NamedChangeCallback; + + PrefServiceBase* prefs() { return prefs_; } + const PrefServiceBase* prefs() const { return prefs_; } + + protected: + class BASE_PREFS_EXPORT Internal + : public base::RefCountedThreadSafe<Internal> { + public: + Internal(); + + // Update the value, either by calling |UpdateValueInternal| directly + // or by dispatching to the right thread. + // Takes ownership of |value|. + void UpdateValue(base::Value* value, + bool is_managed, + bool is_user_modifiable, + const base::Closure& callback) const; + + void MoveToThread( + const scoped_refptr<base::MessageLoopProxy>& message_loop); + + // See PrefMember<> for description. + bool IsManaged() const { + return is_managed_; + } + + bool IsUserModifiable() const { + return is_user_modifiable_; + } + + protected: + friend class base::RefCountedThreadSafe<Internal>; + virtual ~Internal(); + + void CheckOnCorrectThread() const { + DCHECK(IsOnCorrectThread()); + } + + private: + // This method actually updates the value. It should only be called from + // the thread the PrefMember is on. + virtual bool UpdateValueInternal(const base::Value& value) const = 0; + + bool IsOnCorrectThread() const; + + scoped_refptr<base::MessageLoopProxy> thread_loop_; + mutable bool is_managed_; + mutable bool is_user_modifiable_; + + DISALLOW_COPY_AND_ASSIGN(Internal); + }; + + PrefMemberBase(); + virtual ~PrefMemberBase(); + + // See PrefMember<> for description. + void Init(const char* pref_name, PrefServiceBase* prefs, + const NamedChangeCallback& observer); + void Init(const char* pref_name, PrefServiceBase* prefs); + + virtual void CreateInternal() const = 0; + + // See PrefMember<> for description. + void Destroy(); + + void MoveToThread(const scoped_refptr<base::MessageLoopProxy>& message_loop); + + // PrefObserver + virtual void OnPreferenceChanged(PrefServiceBase* service, + const std::string& pref_name) OVERRIDE; + + void VerifyValuePrefName() const { + DCHECK(!pref_name_.empty()); + } + + // This method is used to do the actual sync with the preference. + // Note: it is logically const, because it doesn't modify the state + // seen by the outside world. It is just doing a lazy load behind the scenes. + void UpdateValueFromPref(const base::Closure& callback) const; + + // Verifies the preference name, and lazily loads the preference value if + // it hasn't been loaded yet. + void VerifyPref() const; + + const std::string& pref_name() const { return pref_name_; } + + virtual Internal* internal() const = 0; + + // Used to allow registering plain base::Closure callbacks. + static void InvokeUnnamedCallback(const base::Closure& callback, + const std::string& pref_name); + + private: + // Ordered the members to compact the class instance. + std::string pref_name_; + NamedChangeCallback observer_; + PrefServiceBase* prefs_; + + protected: + bool setting_value_; +}; + +// This function implements StringListPrefMember::UpdateValue(). +// It is exposed here for testing purposes. +bool BASE_PREFS_EXPORT PrefMemberVectorStringUpdate( + const Value& value, + std::vector<std::string>* string_vector); + +} // namespace subtle + +template <typename ValueType> +class PrefMember : public subtle::PrefMemberBase { + public: + // Defer initialization to an Init method so it's easy to make this class be + // a member variable. + PrefMember() {} + virtual ~PrefMember() {} + + // Do the actual initialization of the class. Use the two-parameter + // version if you don't want any notifications of changes. This + // method should only be called on the UI thread. + void Init(const char* pref_name, PrefServiceBase* prefs, + const NamedChangeCallback& observer) { + subtle::PrefMemberBase::Init(pref_name, prefs, observer); + } + void Init(const char* pref_name, PrefServiceBase* prefs, + const base::Closure& observer) { + subtle::PrefMemberBase::Init( + pref_name, prefs, + base::Bind(&PrefMemberBase::InvokeUnnamedCallback, observer)); + } + void Init(const char* pref_name, PrefServiceBase* prefs) { + subtle::PrefMemberBase::Init(pref_name, prefs); + } + + // Unsubscribes the PrefMember from the PrefService. After calling this + // function, the PrefMember may not be used any more on the UI thread. + // Assuming |MoveToThread| was previously called, |GetValue|, |IsManaged|, + // and |IsUserModifiable| can still be called from the other thread but + // the results will no longer update from the PrefService. + // This method should only be called on the UI thread. + void Destroy() { + subtle::PrefMemberBase::Destroy(); + } + + // Moves the PrefMember to another thread, allowing read accesses from there. + // Changes from the PrefService will be propagated asynchronously + // via PostTask. + // This method should only be used from the thread the PrefMember is currently + // on, which is the UI thread by default. + void MoveToThread(const scoped_refptr<base::MessageLoopProxy>& message_loop) { + subtle::PrefMemberBase::MoveToThread(message_loop); + } + + // Check whether the pref is managed, i.e. controlled externally through + // enterprise configuration management (e.g. windows group policy). Returns + // false for unknown prefs. + // This method should only be used from the thread the PrefMember is currently + // on, which is the UI thread unless changed by |MoveToThread|. + bool IsManaged() const { + VerifyPref(); + return internal_->IsManaged(); + } + + // Checks whether the pref can be modified by the user. This returns false + // when the pref is managed by a policy or an extension, and when a command + // line flag overrides the pref. + // This method should only be used from the thread the PrefMember is currently + // on, which is the UI thread unless changed by |MoveToThread|. + bool IsUserModifiable() const { + VerifyPref(); + return internal_->IsUserModifiable(); + } + + // Retrieve the value of the member variable. + // This method should only be used from the thread the PrefMember is currently + // on, which is the UI thread unless changed by |MoveToThread|. + ValueType GetValue() const { + VerifyPref(); + return internal_->value(); + } + + // Provided as a convenience. + ValueType operator*() const { + return GetValue(); + } + + // Set the value of the member variable. + // This method should only be called on the UI thread. + void SetValue(const ValueType& value) { + VerifyValuePrefName(); + setting_value_ = true; + UpdatePref(value); + setting_value_ = false; + } + + // Returns the pref name. + const std::string& GetPrefName() const { + return pref_name(); + } + + private: + class Internal : public subtle::PrefMemberBase::Internal { + public: + Internal() : value_(ValueType()) {} + + ValueType value() { + CheckOnCorrectThread(); + return value_; + } + + protected: + virtual ~Internal() {} + + virtual BASE_PREFS_EXPORT bool UpdateValueInternal( + const base::Value& value) const OVERRIDE; + + // We cache the value of the pref so we don't have to keep walking the pref + // tree. + mutable ValueType value_; + + DISALLOW_COPY_AND_ASSIGN(Internal); + }; + + virtual Internal* internal() const OVERRIDE { return internal_; } + virtual void CreateInternal() const OVERRIDE { + internal_ = new Internal(); + } + + // This method is used to do the actual sync with pref of the specified type. + void BASE_PREFS_EXPORT UpdatePref(const ValueType& value); + + mutable scoped_refptr<Internal> internal_; + + DISALLOW_COPY_AND_ASSIGN(PrefMember); +}; + +// Declaration of template specialization need to be repeated here +// specifically for each specialization (rather than just once above) +// or at least one of our compilers won't be happy in all cases. +// Specifically, it was failing on ChromeOS with a complaint about +// PrefMember<FilePath>::UpdateValueInternal not being defined when +// built in a chroot with the following parameters: +// +// FEATURES="noclean nostrip" USE="-chrome_debug -chrome_remoting +// -chrome_internal -chrome_pdf component_build" +// ~/trunk/goma/goma-wrapper cros_chrome_make --board=${BOARD} +// --install --runhooks + +template <> +BASE_PREFS_EXPORT void PrefMember<bool>::UpdatePref(const bool& value); + +template <> +BASE_PREFS_EXPORT bool PrefMember<bool>::Internal::UpdateValueInternal( + const Value& value) const; + +template <> +BASE_PREFS_EXPORT void PrefMember<int>::UpdatePref(const int& value); + +template <> +BASE_PREFS_EXPORT bool PrefMember<int>::Internal::UpdateValueInternal( + const Value& value) const; + +template <> +BASE_PREFS_EXPORT void PrefMember<double>::UpdatePref(const double& value); + +template <> +BASE_PREFS_EXPORT bool PrefMember<double>::Internal::UpdateValueInternal( + const Value& value) const; + +template <> +BASE_PREFS_EXPORT void PrefMember<std::string>::UpdatePref( + const std::string& value); + +template <> +BASE_PREFS_EXPORT bool PrefMember<std::string>::Internal::UpdateValueInternal( + const Value& value) const; + +template <> +BASE_PREFS_EXPORT void PrefMember<FilePath>::UpdatePref(const FilePath& value); + +template <> +BASE_PREFS_EXPORT bool PrefMember<FilePath>::Internal::UpdateValueInternal( + const Value& value) const; + +template <> +BASE_PREFS_EXPORT void PrefMember<std::vector<std::string> >::UpdatePref( + const std::vector<std::string>& value); + +template <> +BASE_PREFS_EXPORT bool +PrefMember<std::vector<std::string> >::Internal::UpdateValueInternal( + const Value& value) const; + +typedef PrefMember<bool> BooleanPrefMember; +typedef PrefMember<int> IntegerPrefMember; +typedef PrefMember<double> DoublePrefMember; +typedef PrefMember<std::string> StringPrefMember; +typedef PrefMember<FilePath> FilePathPrefMember; +// This preference member is expensive for large string arrays. +typedef PrefMember<std::vector<std::string> > StringListPrefMember; + +#endif // BASE_PREFS_PUBLIC_PREF_MEMBER_H_
diff --git a/src/base/prefs/public/pref_member_unittest.cc b/src/base/prefs/public/pref_member_unittest.cc new file mode 100644 index 0000000..4203db3 --- /dev/null +++ b/src/base/prefs/public/pref_member_unittest.cc
@@ -0,0 +1,316 @@ +// 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/prefs/public/pref_member.h" + +#include "base/bind.h" +#include "base/message_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "chrome/test/base/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kBoolPref[] = "bool"; +const char kIntPref[] = "int"; +const char kDoublePref[] = "double"; +const char kStringPref[] = "string"; +const char kStringListPref[] = "string_list"; + +void RegisterTestPrefs(PrefService* prefs) { + prefs->RegisterBooleanPref(kBoolPref, false, PrefService::UNSYNCABLE_PREF); + prefs->RegisterIntegerPref(kIntPref, 0, PrefService::UNSYNCABLE_PREF); + prefs->RegisterDoublePref(kDoublePref, 0.0, PrefService::UNSYNCABLE_PREF); + prefs->RegisterStringPref(kStringPref, + "default", + PrefService::UNSYNCABLE_PREF); + prefs->RegisterListPref(kStringListPref, + new ListValue(), + PrefService::UNSYNCABLE_PREF); +} + +class GetPrefValueHelper + : public base::RefCountedThreadSafe<GetPrefValueHelper> { + public: + GetPrefValueHelper() : value_(false), pref_thread_("pref thread") { + pref_thread_.Start(); + } + + void Init(const char* pref_name, PrefService* prefs) { + pref_.Init(pref_name, prefs); + pref_.MoveToThread(pref_thread_.message_loop_proxy()); + } + + void Destroy() { + pref_.Destroy(); + } + + void FetchValue() { + base::WaitableEvent event(true, false); + ASSERT_TRUE( + pref_thread_.message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&GetPrefValueHelper::GetPrefValue, this, &event))); + event.Wait(); + } + + bool value() { return value_; } + + private: + friend class base::RefCountedThreadSafe<GetPrefValueHelper>; + ~GetPrefValueHelper() {} + + void GetPrefValue(base::WaitableEvent* event) { + value_ = pref_.GetValue(); + event->Signal(); + } + + BooleanPrefMember pref_; + bool value_; + + base::Thread pref_thread_; // The thread |pref_| runs on. +}; + +class PrefMemberTestClass { + public: + explicit PrefMemberTestClass(PrefService* prefs) + : observe_cnt_(0), prefs_(prefs) { + str_.Init(kStringPref, prefs, + base::Bind(&PrefMemberTestClass::OnPreferenceChanged, + base::Unretained(this))); + } + + void OnPreferenceChanged(const std::string& pref_name) { + EXPECT_EQ(pref_name, kStringPref); + EXPECT_EQ(str_.GetValue(), prefs_->GetString(kStringPref)); + ++observe_cnt_; + } + + StringPrefMember str_; + int observe_cnt_; + + private: + PrefService* prefs_; +}; + +} // anonymous namespace + +TEST(PrefMemberTest, BasicGetAndSet) { + TestingPrefService prefs; + RegisterTestPrefs(&prefs); + + // Test bool + BooleanPrefMember boolean; + boolean.Init(kBoolPref, &prefs); + + // Check the defaults + EXPECT_FALSE(prefs.GetBoolean(kBoolPref)); + EXPECT_FALSE(boolean.GetValue()); + EXPECT_FALSE(*boolean); + + // Try changing through the member variable. + boolean.SetValue(true); + EXPECT_TRUE(boolean.GetValue()); + EXPECT_TRUE(prefs.GetBoolean(kBoolPref)); + EXPECT_TRUE(*boolean); + + // Try changing back through the pref. + prefs.SetBoolean(kBoolPref, false); + EXPECT_FALSE(prefs.GetBoolean(kBoolPref)); + EXPECT_FALSE(boolean.GetValue()); + EXPECT_FALSE(*boolean); + + // Test int + IntegerPrefMember integer; + integer.Init(kIntPref, &prefs); + + // Check the defaults + EXPECT_EQ(0, prefs.GetInteger(kIntPref)); + EXPECT_EQ(0, integer.GetValue()); + EXPECT_EQ(0, *integer); + + // Try changing through the member variable. + integer.SetValue(5); + EXPECT_EQ(5, integer.GetValue()); + EXPECT_EQ(5, prefs.GetInteger(kIntPref)); + EXPECT_EQ(5, *integer); + + // Try changing back through the pref. + prefs.SetInteger(kIntPref, 2); + EXPECT_EQ(2, prefs.GetInteger(kIntPref)); + EXPECT_EQ(2, integer.GetValue()); + EXPECT_EQ(2, *integer); + + // Test double + DoublePrefMember double_member; + double_member.Init(kDoublePref, &prefs); + + // Check the defaults + EXPECT_EQ(0.0, prefs.GetDouble(kDoublePref)); + EXPECT_EQ(0.0, double_member.GetValue()); + EXPECT_EQ(0.0, *double_member); + + // Try changing through the member variable. + double_member.SetValue(1.0); + EXPECT_EQ(1.0, double_member.GetValue()); + EXPECT_EQ(1.0, prefs.GetDouble(kDoublePref)); + EXPECT_EQ(1.0, *double_member); + + // Try changing back through the pref. + prefs.SetDouble(kDoublePref, 3.0); + EXPECT_EQ(3.0, prefs.GetDouble(kDoublePref)); + EXPECT_EQ(3.0, double_member.GetValue()); + EXPECT_EQ(3.0, *double_member); + + // Test string + StringPrefMember string; + string.Init(kStringPref, &prefs); + + // Check the defaults + EXPECT_EQ("default", prefs.GetString(kStringPref)); + EXPECT_EQ("default", string.GetValue()); + EXPECT_EQ("default", *string); + + // Try changing through the member variable. + string.SetValue("foo"); + EXPECT_EQ("foo", string.GetValue()); + EXPECT_EQ("foo", prefs.GetString(kStringPref)); + EXPECT_EQ("foo", *string); + + // Try changing back through the pref. + prefs.SetString(kStringPref, "bar"); + EXPECT_EQ("bar", prefs.GetString(kStringPref)); + EXPECT_EQ("bar", string.GetValue()); + EXPECT_EQ("bar", *string); + + // Test string list + ListValue expected_list; + std::vector<std::string> expected_vector; + StringListPrefMember string_list; + string_list.Init(kStringListPref, &prefs); + + // Check the defaults + EXPECT_TRUE(expected_list.Equals(prefs.GetList(kStringListPref))); + EXPECT_EQ(expected_vector, string_list.GetValue()); + EXPECT_EQ(expected_vector, *string_list); + + // Try changing through the pref member. + expected_list.AppendString("foo"); + expected_vector.push_back("foo"); + string_list.SetValue(expected_vector); + + EXPECT_TRUE(expected_list.Equals(prefs.GetList(kStringListPref))); + EXPECT_EQ(expected_vector, string_list.GetValue()); + EXPECT_EQ(expected_vector, *string_list); + + // Try adding through the pref. + expected_list.AppendString("bar"); + expected_vector.push_back("bar"); + prefs.Set(kStringListPref, expected_list); + + EXPECT_TRUE(expected_list.Equals(prefs.GetList(kStringListPref))); + EXPECT_EQ(expected_vector, string_list.GetValue()); + EXPECT_EQ(expected_vector, *string_list); + + // Try removing through the pref. + expected_list.Remove(0, NULL); + expected_vector.erase(expected_vector.begin()); + prefs.Set(kStringListPref, expected_list); + + EXPECT_TRUE(expected_list.Equals(prefs.GetList(kStringListPref))); + EXPECT_EQ(expected_vector, string_list.GetValue()); + EXPECT_EQ(expected_vector, *string_list); +} + +TEST(PrefMemberTest, InvalidList) { + // Set the vector to an initial good value. + std::vector<std::string> expected_vector; + expected_vector.push_back("foo"); + + // Try to add a valid list first. + ListValue list; + list.AppendString("foo"); + std::vector<std::string> vector; + EXPECT_TRUE(subtle::PrefMemberVectorStringUpdate(list, &vector)); + EXPECT_EQ(expected_vector, vector); + + // Now try to add an invalid list. |vector| should not be changed. + list.AppendInteger(0); + EXPECT_FALSE(subtle::PrefMemberVectorStringUpdate(list, &vector)); + EXPECT_EQ(expected_vector, vector); +} + +TEST(PrefMemberTest, TwoPrefs) { + // Make sure two DoublePrefMembers stay in sync. + TestingPrefService prefs; + RegisterTestPrefs(&prefs); + + DoublePrefMember pref1; + pref1.Init(kDoublePref, &prefs); + DoublePrefMember pref2; + pref2.Init(kDoublePref, &prefs); + + pref1.SetValue(2.3); + EXPECT_EQ(2.3, *pref2); + + pref2.SetValue(3.5); + EXPECT_EQ(3.5, *pref1); + + prefs.SetDouble(kDoublePref, 4.2); + EXPECT_EQ(4.2, *pref1); + EXPECT_EQ(4.2, *pref2); +} + +TEST(PrefMemberTest, Observer) { + TestingPrefService prefs; + RegisterTestPrefs(&prefs); + + PrefMemberTestClass test_obj(&prefs); + EXPECT_EQ("default", *test_obj.str_); + + // Calling SetValue should not fire the observer. + test_obj.str_.SetValue("hello"); + EXPECT_EQ(0, test_obj.observe_cnt_); + EXPECT_EQ("hello", prefs.GetString(kStringPref)); + + // Changing the pref does fire the observer. + prefs.SetString(kStringPref, "world"); + EXPECT_EQ(1, test_obj.observe_cnt_); + EXPECT_EQ("world", *(test_obj.str_)); + + // Not changing the value should not fire the observer. + prefs.SetString(kStringPref, "world"); + EXPECT_EQ(1, test_obj.observe_cnt_); + EXPECT_EQ("world", *(test_obj.str_)); + + prefs.SetString(kStringPref, "hello"); + EXPECT_EQ(2, test_obj.observe_cnt_); + EXPECT_EQ("hello", prefs.GetString(kStringPref)); +} + +TEST(PrefMemberTest, NoInit) { + // Make sure not calling Init on a PrefMember doesn't cause problems. + IntegerPrefMember pref; +} + +TEST(PrefMemberTest, MoveToThread) { + TestingPrefService prefs; + scoped_refptr<GetPrefValueHelper> helper(new GetPrefValueHelper()); + RegisterTestPrefs(&prefs); + helper->Init(kBoolPref, &prefs); + + helper->FetchValue(); + EXPECT_FALSE(helper->value()); + + prefs.SetBoolean(kBoolPref, true); + + helper->FetchValue(); + EXPECT_TRUE(helper->value()); + + helper->Destroy(); + + helper->FetchValue(); + EXPECT_TRUE(helper->value()); +}
diff --git a/src/base/prefs/public/pref_service_base.h b/src/base/prefs/public/pref_service_base.h new file mode 100644 index 0000000..504632b --- /dev/null +++ b/src/base/prefs/public/pref_service_base.h
@@ -0,0 +1,268 @@ +// 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. + +// This is the base interface for a preference services that provides +// a way to access the application's current preferences. +// +// This base interface assumes all preferences are local. See +// SyncablePrefServiceBase for the interface to a preference service +// that stores preferences that can be synced. +// +// Chromium settings and storage represent user-selected preferences and +// information and MUST not be extracted, overwritten or modified except +// through Chromium defined APIs. + +#ifndef BASE_PREFS_PUBLIC_PREF_SERVICE_BASE_H_ +#define BASE_PREFS_PUBLIC_PREF_SERVICE_BASE_H_ + +#include "base/values.h" + +namespace content { +class BrowserContext; +} + +namespace subtle { +class PrefMemberBase; +} + +class FilePath; +class PrefObserver; + +class PrefServiceBase { + public: + // Retrieves a PrefServiceBase for the given context. + static PrefServiceBase* FromBrowserContext(content::BrowserContext* context); + + virtual ~PrefServiceBase() {} + + // Enum used when registering preferences to determine if it should be synced + // or not. This is only used for profile prefs, not local state prefs. + // See the Register*Pref methods for profile prefs below. + enum PrefSyncStatus { + UNSYNCABLE_PREF, + SYNCABLE_PREF + }; + + // Interface to a single preference. + class Preference { + public: + virtual ~Preference() {} + + // Returns the name of the Preference (i.e., the key, e.g., + // browser.window_placement). + virtual const std::string name() const = 0; + + // Returns the registered type of the preference. + virtual base::Value::Type GetType() const = 0; + + // Returns the value of the Preference, falling back to the registered + // default value if no other has been set. + virtual const base::Value* GetValue() const = 0; + + // Returns the value recommended by the admin, if any. + virtual const base::Value* GetRecommendedValue() const = 0; + + // Returns true if the Preference is managed, i.e. set by an admin policy. + // Since managed prefs have the highest priority, this also indicates + // whether the pref is actually being controlled by the policy setting. + virtual bool IsManaged() const = 0; + + // Returns true if the Preference is recommended, i.e. set by an admin + // policy but the user is allowed to change it. + virtual bool IsRecommended() const = 0; + + // Returns true if the Preference has a value set by an extension, even if + // that value is being overridden by a higher-priority source. + virtual bool HasExtensionSetting() const = 0; + + // Returns true if the Preference has a user setting, even if that value is + // being overridden by a higher-priority source. + virtual bool HasUserSetting() const = 0; + + // Returns true if the Preference value is currently being controlled by an + // extension, and not by any higher-priority source. + virtual bool IsExtensionControlled() const = 0; + + // Returns true if the Preference value is currently being controlled by a + // user setting, and not by any higher-priority source. + virtual bool IsUserControlled() const = 0; + + // Returns true if the Preference is currently using its default value, + // and has not been set by any higher-priority source (even with the same + // value). + virtual bool IsDefaultValue() const = 0; + + // Returns true if the user can change the Preference value, which is the + // case if no higher-priority source than the user store controls the + // Preference. + virtual bool IsUserModifiable() const = 0; + + // Returns true if an extension can change the Preference value, which is + // the case if no higher-priority source than the extension store controls + // the Preference. + virtual bool IsExtensionModifiable() const = 0; + }; + + // Returns true if the preference for the given preference name is available + // and is managed. + virtual bool IsManagedPreference(const char* pref_name) const = 0; + + // Returns |true| if a preference with the given name is available and its + // value can be changed by the user. + virtual bool IsUserModifiablePreference(const char* pref_name) const = 0; + + // Make the PrefService aware of a pref. + // TODO(zea): split local state and profile prefs into their own subclasses. + // ---------- Local state prefs ---------- + virtual void RegisterBooleanPref(const char* path, + bool default_value) = 0; + virtual void RegisterIntegerPref(const char* path, + int default_value) = 0; + virtual void RegisterDoublePref(const char* path, + double default_value) = 0; + virtual void RegisterStringPref(const char* path, + const std::string& default_value) = 0; + virtual void RegisterFilePathPref(const char* path, + const FilePath& default_value) = 0; + virtual void RegisterListPref(const char* path) = 0; + virtual void RegisterDictionaryPref(const char* path) = 0; + // These take ownership of the default_value: + virtual void RegisterListPref(const char* path, + base::ListValue* default_value) = 0; + virtual void RegisterDictionaryPref( + const char* path, base::DictionaryValue* default_value) = 0; + // These variants use a default value from the locale dll instead. + virtual void RegisterLocalizedBooleanPref( + const char* path, int locale_default_message_id) = 0; + virtual void RegisterLocalizedIntegerPref( + const char* path, int locale_default_message_id) = 0; + virtual void RegisterLocalizedDoublePref( + const char* path, int locale_default_message_id) = 0; + virtual void RegisterLocalizedStringPref( + const char* path, int locale_default_message_id) = 0; + virtual void RegisterInt64Pref(const char* path, + int64 default_value) = 0; + + // ---------- Profile prefs ---------- + // Profile prefs must specify whether the pref should be synchronized across + // machines or not (see PrefSyncStatus enum above). + virtual void RegisterBooleanPref(const char* path, + bool default_value, + PrefSyncStatus sync_status) = 0; + virtual void RegisterIntegerPref(const char* path, + int default_value, + PrefSyncStatus sync_status) = 0; + virtual void RegisterDoublePref(const char* path, + double default_value, + PrefSyncStatus sync_status) = 0; + virtual void RegisterStringPref(const char* path, + const std::string& default_value, + PrefSyncStatus sync_status) = 0; + virtual void RegisterFilePathPref(const char* path, + const FilePath& default_value, + PrefSyncStatus sync_status) = 0; + virtual void RegisterListPref(const char* path, + PrefSyncStatus sync_status) = 0; + virtual void RegisterDictionaryPref(const char* path, + PrefSyncStatus sync_status) = 0; + // These take ownership of the default_value: + virtual void RegisterListPref(const char* path, + base::ListValue* default_value, + PrefSyncStatus sync_status) = 0; + virtual void RegisterDictionaryPref(const char* path, + base::DictionaryValue* default_value, + PrefSyncStatus sync_status) = 0; + // These variants use a default value from the locale dll instead. + virtual void RegisterLocalizedBooleanPref( + const char* path, + int locale_default_message_id, + PrefSyncStatus sync_status) = 0; + virtual void RegisterLocalizedIntegerPref( + const char* path, + int locale_default_message_id, + PrefSyncStatus sync_status) = 0; + virtual void RegisterLocalizedDoublePref( + const char* path, + int locale_default_message_id, + PrefSyncStatus sync_status) = 0; + virtual void RegisterLocalizedStringPref( + const char* path, + int locale_default_message_id, + PrefSyncStatus sync_status) = 0; + virtual void RegisterInt64Pref(const char* path, + int64 default_value, + PrefSyncStatus sync_status) = 0; + virtual void RegisterUint64Pref(const char* path, + uint64 default_value, + PrefSyncStatus sync_status) = 0; + // Unregisters a preference. + virtual void UnregisterPreference(const char* path) = 0; + + // Look up a preference. Returns NULL if the preference is not + // registered. + virtual const Preference* FindPreference(const char* pref_name) const = 0; + + // If the path is valid and the value at the end of the path matches the type + // specified, it will return the specified value. Otherwise, the default + // value (set when the pref was registered) will be returned. + virtual bool GetBoolean(const char* path) const = 0; + virtual int GetInteger(const char* path) const = 0; + virtual double GetDouble(const char* path) const = 0; + virtual std::string GetString(const char* path) const = 0; + virtual FilePath GetFilePath(const char* path) const = 0; + + // Returns the branch if it exists, or the registered default value otherwise. + // Note that |path| must point to a registered preference. In that case, these + // functions will never return NULL. + virtual const base::DictionaryValue* GetDictionary( + const char* path) const = 0; + virtual const base::ListValue* GetList(const char* path) const = 0; + + // Removes a user pref and restores the pref to its default value. + virtual void ClearPref(const char* path) = 0; + + // If the path is valid (i.e., registered), update the pref value in the user + // prefs. + // To set the value of dictionary or list values in the pref tree use + // Set(), but to modify the value of a dictionary or list use either + // ListPrefUpdate or DictionaryPrefUpdate from scoped_user_pref_update.h. + virtual void Set(const char* path, const base::Value& value) = 0; + virtual void SetBoolean(const char* path, bool value) = 0; + virtual void SetInteger(const char* path, int value) = 0; + virtual void SetDouble(const char* path, double value) = 0; + virtual void SetString(const char* path, const std::string& value) = 0; + virtual void SetFilePath(const char* path, const FilePath& value) = 0; + + // Int64 helper methods that actually store the given value as a string. + // Note that if obtaining the named value via GetDictionary or GetList, the + // Value type will be TYPE_STRING. + virtual void SetInt64(const char* path, int64 value) = 0; + virtual int64 GetInt64(const char* path) const = 0; + + // As above, but for unsigned values. + virtual void SetUint64(const char* path, uint64 value) = 0; + virtual uint64 GetUint64(const char* path) const = 0; + + protected: + // Registration of pref change observers must be done using the + // PrefChangeRegistrar, which is declared as a friend here to grant it + // access to the otherwise protected members Add/RemovePrefObserver. + // PrefMember registers for preferences changes notification directly to + // avoid the storage overhead of the registrar, so its base class must be + // declared as a friend, too. + friend class PrefChangeRegistrar; + friend class subtle::PrefMemberBase; + + // These are protected so they can only be accessed by the friend + // classes listed above. + // + // If the pref at the given path changes, we call the observer's + // OnPreferenceChanged method. Note that observers should not call + // these methods directly but rather use a PrefChangeRegistrar to + // make sure the observer gets cleaned up properly. + virtual void AddPrefObserver(const char* path, PrefObserver* obs) = 0; + virtual void RemovePrefObserver(const char* path, PrefObserver* obs) = 0; +}; + +#endif // BASE_PREFS_PUBLIC_PREF_SERVICE_BASE_H_
diff --git a/src/base/prefs/test/data/pref_service/invalid.json b/src/base/prefs/test/data/pref_service/invalid.json new file mode 100644 index 0000000..43392a9 --- /dev/null +++ b/src/base/prefs/test/data/pref_service/invalid.json
@@ -0,0 +1 @@ +!@#$%^& \ No newline at end of file
diff --git a/src/base/prefs/test/data/pref_service/read.json b/src/base/prefs/test/data/pref_service/read.json new file mode 100644 index 0000000..ea578a4 --- /dev/null +++ b/src/base/prefs/test/data/pref_service/read.json
@@ -0,0 +1,8 @@ +{ + "homepage": "http://www.cnn.com", + "some_directory": "/usr/local/", + "tabs": { + "new_windows_in_tabs": true, + "max_tabs": 20 + } +}
diff --git a/src/base/prefs/test/data/pref_service/read.need_empty_value.json b/src/base/prefs/test/data/pref_service/read.need_empty_value.json new file mode 100644 index 0000000..48e1749 --- /dev/null +++ b/src/base/prefs/test/data/pref_service/read.need_empty_value.json
@@ -0,0 +1,10 @@ +{ + "list": [ 1 ], + "list_needs_empty_value": [ 2 ], + "dict": { + "dummy": true + }, + "dict_needs_empty_value": { + "dummy": true + } +}
diff --git a/src/base/prefs/test/data/pref_service/write.golden.json b/src/base/prefs/test/data/pref_service/write.golden.json new file mode 100644 index 0000000..9a5523c --- /dev/null +++ b/src/base/prefs/test/data/pref_service/write.golden.json
@@ -0,0 +1,11 @@ +{ + "homepage": "http://www.cnn.com", + "long_int": { + "pref": "214748364842" + }, + "some_directory": "/usr/sbin/", + "tabs": { + "max_tabs": 10, + "new_windows_in_tabs": false + } +}
diff --git a/src/base/prefs/test/data/pref_service/write.golden.need_empty_value.json b/src/base/prefs/test/data/pref_service/write.golden.need_empty_value.json new file mode 100644 index 0000000..fa79590 --- /dev/null +++ b/src/base/prefs/test/data/pref_service/write.golden.need_empty_value.json
@@ -0,0 +1,6 @@ +{ + "dict_needs_empty_value": { + + }, + "list_needs_empty_value": [ ] +}
diff --git a/src/base/prefs/testing_pref_store.cc b/src/base/prefs/testing_pref_store.cc new file mode 100644 index 0000000..85f35bb --- /dev/null +++ b/src/base/prefs/testing_pref_store.cc
@@ -0,0 +1,135 @@ +// 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/testing_pref_store.h" + +#include "base/memory/scoped_ptr.h" +#include "base/values.h" + +TestingPrefStore::TestingPrefStore() + : read_only_(true), + init_complete_(false) { +} + +bool TestingPrefStore::GetValue(const std::string& key, + const Value** value) const { + return prefs_.GetValue(key, value); +} + +bool TestingPrefStore::GetMutableValue(const std::string& key, + Value** value) { + return prefs_.GetValue(key, value); +} + +void TestingPrefStore::AddObserver(PrefStore::Observer* observer) { + observers_.AddObserver(observer); +} + +void TestingPrefStore::RemoveObserver(PrefStore::Observer* observer) { + observers_.RemoveObserver(observer); +} + +size_t TestingPrefStore::NumberOfObservers() const { + return observers_.size(); +} + +bool TestingPrefStore::IsInitializationComplete() const { + return init_complete_; +} + +void TestingPrefStore::SetValue(const std::string& key, Value* value) { + if (prefs_.SetValue(key, value)) + NotifyPrefValueChanged(key); +} + +void TestingPrefStore::SetValueSilently(const std::string& key, Value* value) { + prefs_.SetValue(key, value); +} + +void TestingPrefStore::RemoveValue(const std::string& key) { + if (prefs_.RemoveValue(key)) + NotifyPrefValueChanged(key); +} + +void TestingPrefStore::MarkNeedsEmptyValue(const std::string& key) { +} + +bool TestingPrefStore::ReadOnly() const { + return read_only_; +} + +PersistentPrefStore::PrefReadError TestingPrefStore::GetReadError() const { + return PersistentPrefStore::PREF_READ_ERROR_NONE; +} + +PersistentPrefStore::PrefReadError TestingPrefStore::ReadPrefs() { + NotifyInitializationCompleted(); + return PersistentPrefStore::PREF_READ_ERROR_NONE; +} + +void TestingPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate_raw) { + scoped_ptr<ReadErrorDelegate> error_delegate(error_delegate_raw); + NotifyInitializationCompleted(); +} + +void TestingPrefStore::SetInitializationCompleted() { + init_complete_ = true; + NotifyInitializationCompleted(); +} + +void TestingPrefStore::NotifyPrefValueChanged(const std::string& key) { + FOR_EACH_OBSERVER(Observer, observers_, OnPrefValueChanged(key)); +} + +void TestingPrefStore::NotifyInitializationCompleted() { + FOR_EACH_OBSERVER(Observer, observers_, OnInitializationCompleted(true)); +} + +void TestingPrefStore::ReportValueChanged(const std::string& key) { + FOR_EACH_OBSERVER(Observer, observers_, OnPrefValueChanged(key)); +} + +void TestingPrefStore::SetString(const std::string& key, + const std::string& value) { + SetValue(key, new base::StringValue(value)); +} + +void TestingPrefStore::SetInteger(const std::string& key, int value) { + SetValue(key, new base::FundamentalValue(value)); +} + +void TestingPrefStore::SetBoolean(const std::string& key, bool value) { + SetValue(key, new base::FundamentalValue(value)); +} + +bool TestingPrefStore::GetString(const std::string& key, + std::string* value) const { + const Value* stored_value; + if (!prefs_.GetValue(key, &stored_value) || !stored_value) + return false; + + return stored_value->GetAsString(value); +} + +bool TestingPrefStore::GetInteger(const std::string& key, int* value) const { + const Value* stored_value; + if (!prefs_.GetValue(key, &stored_value) || !stored_value) + return false; + + return stored_value->GetAsInteger(value); +} + +bool TestingPrefStore::GetBoolean(const std::string& key, bool* value) const { + const Value* stored_value; + if (!prefs_.GetValue(key, &stored_value) || !stored_value) + return false; + + return stored_value->GetAsBoolean(value); +} + +void TestingPrefStore::set_read_only(bool read_only) { + read_only_ = read_only; +} + +TestingPrefStore::~TestingPrefStore() {}
diff --git a/src/base/prefs/testing_pref_store.h b/src/base/prefs/testing_pref_store.h new file mode 100644 index 0000000..a9f1e92 --- /dev/null +++ b/src/base/prefs/testing_pref_store.h
@@ -0,0 +1,84 @@ +// 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_TESTING_PREF_STORE_H_ +#define BASE_PREFS_TESTING_PREF_STORE_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/observer_list.h" +#include "base/prefs/persistent_pref_store.h" +#include "base/prefs/pref_value_map.h" + +// |TestingPrefStore| is a preference store implementation that allows tests to +// explicitly manipulate the contents of the store, triggering notifications +// where appropriate. +class TestingPrefStore : public PersistentPrefStore { + public: + TestingPrefStore(); + + // Overriden from PrefStore. + virtual bool GetValue(const std::string& key, + const base::Value** result) const OVERRIDE; + virtual void AddObserver(PrefStore::Observer* observer) OVERRIDE; + virtual void RemoveObserver(PrefStore::Observer* observer) OVERRIDE; + virtual size_t NumberOfObservers() const OVERRIDE; + virtual bool IsInitializationComplete() const OVERRIDE; + + // PersistentPrefStore overrides: + virtual bool GetMutableValue(const std::string& key, + base::Value** result) OVERRIDE; + virtual void ReportValueChanged(const std::string& key) OVERRIDE; + virtual void SetValue(const std::string& key, base::Value* value) OVERRIDE; + 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; + virtual void ReadPrefsAsync(ReadErrorDelegate* error_delegate) OVERRIDE; + virtual void CommitPendingWrite() OVERRIDE {} + + // Marks the store as having completed initialization. + void SetInitializationCompleted(); + + // Used for tests to trigger notifications explicitly. + void NotifyPrefValueChanged(const std::string& key); + void NotifyInitializationCompleted(); + + // Some convenience getters/setters. + void SetString(const std::string& key, const std::string& value); + void SetInteger(const std::string& key, int value); + void SetBoolean(const std::string& key, bool value); + + bool GetString(const std::string& key, std::string* value) const; + bool GetInteger(const std::string& key, int* value) const; + bool GetBoolean(const std::string& key, bool* value) const; + + // Getter and Setter methods for setting and getting the state of the + // |TestingPrefStore|. + virtual void set_read_only(bool read_only); + + protected: + virtual ~TestingPrefStore(); + + private: + // Stores the preference values. + PrefValueMap prefs_; + + // Flag that indicates if the PrefStore is read-only + bool read_only_; + + // Whether initialization has been completed. + bool init_complete_; + + ObserverList<PrefStore::Observer, true> observers_; + + DISALLOW_COPY_AND_ASSIGN(TestingPrefStore); +}; + +#endif // BASE_PREFS_TESTING_PREF_STORE_H_
diff --git a/src/base/prefs/value_map_pref_store.cc b/src/base/prefs/value_map_pref_store.cc new file mode 100644 index 0000000..1d58aa3 --- /dev/null +++ b/src/base/prefs/value_map_pref_store.cc
@@ -0,0 +1,45 @@ +// 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/value_map_pref_store.h" + +#include <algorithm> + +#include "base/stl_util.h" +#include "base/values.h" + +ValueMapPrefStore::ValueMapPrefStore() {} + +bool ValueMapPrefStore::GetValue(const std::string& key, + const Value** value) const { + return prefs_.GetValue(key, value); +} + +void ValueMapPrefStore::AddObserver(PrefStore::Observer* observer) { + observers_.AddObserver(observer); +} + +void ValueMapPrefStore::RemoveObserver(PrefStore::Observer* observer) { + observers_.RemoveObserver(observer); +} + +size_t ValueMapPrefStore::NumberOfObservers() const { + return observers_.size(); +} + +ValueMapPrefStore::~ValueMapPrefStore() {} + +void ValueMapPrefStore::SetValue(const std::string& key, Value* value) { + if (prefs_.SetValue(key, value)) + FOR_EACH_OBSERVER(Observer, observers_, OnPrefValueChanged(key)); +} + +void ValueMapPrefStore::RemoveValue(const std::string& key) { + if (prefs_.RemoveValue(key)) + FOR_EACH_OBSERVER(Observer, observers_, OnPrefValueChanged(key)); +} + +void ValueMapPrefStore::NotifyInitializationCompleted() { + FOR_EACH_OBSERVER(Observer, observers_, OnInitializationCompleted(true)); +}
diff --git a/src/base/prefs/value_map_pref_store.h b/src/base/prefs/value_map_pref_store.h new file mode 100644 index 0000000..c9c9b1c --- /dev/null +++ b/src/base/prefs/value_map_pref_store.h
@@ -0,0 +1,52 @@ +// 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_VALUE_MAP_PREF_STORE_H_ +#define BASE_PREFS_VALUE_MAP_PREF_STORE_H_ + +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/observer_list.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/pref_store.h" +#include "base/prefs/pref_value_map.h" + +// A basic PrefStore implementation that uses a simple name-value map for +// storing the preference values. +class BASE_PREFS_EXPORT ValueMapPrefStore : public PrefStore { + public: + ValueMapPrefStore(); + + // PrefStore overrides: + virtual bool GetValue(const std::string& key, + const base::Value** value) const OVERRIDE; + virtual void AddObserver(PrefStore::Observer* observer) OVERRIDE; + virtual void RemoveObserver(PrefStore::Observer* observer) OVERRIDE; + virtual size_t NumberOfObservers() const OVERRIDE; + + protected: + virtual ~ValueMapPrefStore(); + + // Store a |value| for |key| in the store. Also generates an notification if + // the value changed. Assumes ownership of |value|, which must be non-NULL. + void SetValue(const std::string& key, base::Value* value); + + // Remove the value for |key| from the store. Sends a notification if there + // was a value to be removed. + void RemoveValue(const std::string& key); + + // Notify observers about the initialization completed event. + void NotifyInitializationCompleted(); + + private: + PrefValueMap prefs_; + + ObserverList<PrefStore::Observer, true> observers_; + + DISALLOW_COPY_AND_ASSIGN(ValueMapPrefStore); +}; + +#endif // BASE_PREFS_VALUE_MAP_PREF_STORE_H_
diff --git a/src/base/process.h b/src/base/process.h new file mode 100644 index 0000000..4a4fe90 --- /dev/null +++ b/src/base/process.h
@@ -0,0 +1,99 @@ +// 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. + +#ifndef BASE_PROCESS_H_ +#define BASE_PROCESS_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "build/build_config.h" + +#include <sys/types.h> +#if defined(OS_WIN) +#include <windows.h> +#elif defined(OS_STARBOARD) +#include "starboard/types.h" +#endif + +namespace base { + +// ProcessHandle is a platform specific type which represents the underlying OS +// handle to a process. +// ProcessId is a number which identifies the process in the OS. +#if defined(OS_WIN) +typedef HANDLE ProcessHandle; +typedef DWORD ProcessId; +typedef HANDLE UserTokenHandle; +const ProcessHandle kNullProcessHandle = NULL; +const ProcessId kNullProcessId = 0; +#elif defined(OS_POSIX) +// On POSIX, our ProcessHandle will just be the PID. +typedef pid_t ProcessHandle; +typedef pid_t ProcessId; +const ProcessHandle kNullProcessHandle = 0; +const ProcessId kNullProcessId = 0; +#elif defined(OS_STARBOARD) +// We don't really support processes in Starboard. +typedef int32_t ProcessHandle; +typedef int32_t ProcessId; +const ProcessHandle kNullProcessHandle = 0; +const ProcessId kNullProcessId = 0; +#endif // defined(OS_WIN) + +class BASE_EXPORT Process { + public: + Process() : process_(kNullProcessHandle) { + } + + explicit Process(ProcessHandle handle) : process_(handle) { + } + + // A handle to the current process. + static Process Current(); + + static bool CanBackgroundProcesses(); + + // Get/Set the handle for this process. The handle will be 0 if the process + // is no longer running. + ProcessHandle handle() const { return process_; } + void set_handle(ProcessHandle handle) { + process_ = handle; + } + + // Get the PID for this process. + ProcessId pid() const; + + // Is the this process the current process. + bool is_current() const; + + // Close the process handle. This will not terminate the process. + void Close(); + + // Terminates the process with extreme prejudice. The given result code will + // be the exit code of the process. If the process has already exited, this + // will do nothing. + void Terminate(int result_code); + + // A process is backgrounded when it's priority is lower than normal. + // Return true if this process is backgrounded, false otherwise. + bool IsProcessBackgrounded() const; + + // Set a process as backgrounded. If value is true, the priority + // of the process will be lowered. If value is false, the priority + // of the process will be made "normal" - equivalent to default + // process priority. + // Returns true if the priority was changed, false otherwise. + bool SetProcessBackgrounded(bool value); + + // Returns an integer representing the priority of a process. The meaning + // of this value is OS dependent. + int GetPriority() const; + + private: + ProcessHandle process_; +}; + +} // namespace base + +#endif // BASE_PROCESS_H_
diff --git a/src/base/process_info.h b/src/base/process_info.h new file mode 100644 index 0000000..429d4dc --- /dev/null +++ b/src/base/process_info.h
@@ -0,0 +1,25 @@ +// 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_PROCESS_INFO_H_ +#define BASE_PROCESS_INFO_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { + +class Time; + +// Vends information about the current process. +class BASE_EXPORT CurrentProcessInfo { + public: + // Returns the time at which the process was launched or NULL if an error + // occurred retrieving the information. + static const Time* CreationTime(); +}; + +} // namespace base + +#endif // BASE_PROCESS_INFO_H_
diff --git a/src/base/process_info_mac.cc b/src/base/process_info_mac.cc new file mode 100644 index 0000000..247a144 --- /dev/null +++ b/src/base/process_info_mac.cc
@@ -0,0 +1,43 @@ +// 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/process_info.h" + +#include <sys/sysctl.h> +#include <sys/time.h> +#include <unistd.h> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/time.h" + +namespace { + +using base::Time; + +// Returns the process creation time, or NULL if an error occurred. +Time* ProcessCreationTimeInternal() { + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; + size_t len = 0; + if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) < 0) + return NULL; + + scoped_ptr_malloc<struct kinfo_proc> + proc(static_cast<struct kinfo_proc*>(malloc(len))); + if (sysctl(mib, arraysize(mib), proc.get(), &len, NULL, 0) < 0) + return NULL; + return new Time(Time::FromTimeVal(proc->kp_proc.p_un.__p_starttime)); +} + +} // namespace + +namespace base { + +//static +const Time* CurrentProcessInfo::CreationTime() { + static Time* process_creation_time = ProcessCreationTimeInternal(); + return process_creation_time; +} + +} // namespace base
diff --git a/src/base/process_info_win.cc b/src/base/process_info_win.cc new file mode 100644 index 0000000..762961f --- /dev/null +++ b/src/base/process_info_win.cc
@@ -0,0 +1,37 @@ +// 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/process_info.h" + +#include <windows.h> + +#include "base/basictypes.h" +#include "base/time.h" + +namespace { + +using base::Time; + +// Returns the process creation time, or NULL if an error occurred. +Time* ProcessCreationTimeInternal() { + FILETIME creation_time = {}; + FILETIME ignore = {}; + if (::GetProcessTimes(::GetCurrentProcess(), &creation_time, &ignore, + &ignore, &ignore) == false) + return NULL; + + return new Time(Time::FromFileTime(creation_time)); +} + +} // namespace + +namespace base { + +//static +const Time* CurrentProcessInfo::CreationTime() { + static Time* process_creation_time = ProcessCreationTimeInternal(); + return process_creation_time; +} + +} // namespace base
diff --git a/src/base/process_linux.cc b/src/base/process_linux.cc new file mode 100644 index 0000000..fcec287 --- /dev/null +++ b/src/base/process_linux.cc
@@ -0,0 +1,135 @@ +// 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/process.h" + +#include <errno.h> +#include <sys/resource.h> + +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "base/string_split.h" +#include "base/synchronization/lock.h" + +namespace { +const int kForegroundPriority = 0; + +#if defined(OS_CHROMEOS) +// We are more aggressive in our lowering of background process priority +// for chromeos as we have much more control over other processes running +// on the machine. +// +// TODO(davemoore) Refactor this by adding support for higher levels to set +// the foregrounding / backgrounding process so we don't have to keep +// chrome / chromeos specific logic here. +const int kBackgroundPriority = 19; +const char kControlPath[] = "/sys/fs/cgroup/cpu%s/cgroup.procs"; +const char kForeground[] = "/chrome_renderers/foreground"; +const char kBackground[] = "/chrome_renderers/background"; +const char kProcPath[] = "/proc/%d/cgroup"; + +struct CGroups { + // Check for cgroups files. ChromeOS supports these by default. It creates + // a cgroup mount in /sys/fs/cgroup and then configures two cpu task groups, + // one contains at most a single foreground renderer and the other contains + // all background renderers. This allows us to limit the impact of background + // renderers on foreground ones to a greater level than simple renicing. + bool enabled; + FilePath foreground_file; + FilePath background_file; + + CGroups() { + foreground_file = FilePath(StringPrintf(kControlPath, kForeground)); + background_file = FilePath(StringPrintf(kControlPath, kBackground)); + file_util::FileSystemType foreground_type; + file_util::FileSystemType background_type; + enabled = + file_util::GetFileSystemType(foreground_file, &foreground_type) && + file_util::GetFileSystemType(background_file, &background_type) && + foreground_type == file_util::FILE_SYSTEM_CGROUP && + background_type == file_util::FILE_SYSTEM_CGROUP; + } +}; + +base::LazyInstance<CGroups> cgroups = LAZY_INSTANCE_INITIALIZER; +#else +const int kBackgroundPriority = 5; +#endif +} + +namespace base { + +bool Process::IsProcessBackgrounded() const { + DCHECK(process_); + +#if defined(OS_CHROMEOS) + if (cgroups.Get().enabled) { + std::string proc; + if (file_util::ReadFileToString( + FilePath(StringPrintf(kProcPath, process_)), + &proc)) { + std::vector<std::string> proc_parts; + base::SplitString(proc, ':', &proc_parts); + DCHECK(proc_parts.size() == 3); + bool ret = proc_parts[2] == std::string(kBackground); + return ret; + } else { + return false; + } + } +#endif + return GetPriority() == kBackgroundPriority; +} + +bool Process::SetProcessBackgrounded(bool background) { + DCHECK(process_); + +#if defined(OS_CHROMEOS) + if (cgroups.Get().enabled) { + std::string pid = StringPrintf("%d", process_); + const FilePath file = + background ? + cgroups.Get().background_file : cgroups.Get().foreground_file; + return file_util::WriteFile(file, pid.c_str(), pid.size()) > 0; + } +#endif // OS_CHROMEOS + + if (!CanBackgroundProcesses()) + return false; + + int priority = background ? kBackgroundPriority : kForegroundPriority; + int result = setpriority(PRIO_PROCESS, process_, priority); + DPCHECK(result == 0); + return result == 0; +} + +struct CheckForNicePermission { + CheckForNicePermission() : can_reraise_priority(false) { + // We won't be able to raise the priority if we don't have the right rlimit. + // The limit may be adjusted in /etc/security/limits.conf for PAM systems. + struct rlimit rlim; + if ((getrlimit(RLIMIT_NICE, &rlim) == 0) && + (20 - kForegroundPriority) <= static_cast<int>(rlim.rlim_cur)) { + can_reraise_priority = true; + } + }; + + bool can_reraise_priority; +}; + +// static +bool Process::CanBackgroundProcesses() { +#if defined(OS_CHROMEOS) + if (cgroups.Get().enabled) + return true; +#endif + + static LazyInstance<CheckForNicePermission> check_for_nice_permission = + LAZY_INSTANCE_INITIALIZER; + return check_for_nice_permission.Get().can_reraise_priority; +} + +} // namespace base
diff --git a/src/base/process_posix.cc b/src/base/process_posix.cc new file mode 100644 index 0000000..20e623c --- /dev/null +++ b/src/base/process_posix.cc
@@ -0,0 +1,73 @@ +// 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/process.h" + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/resource.h> + +#include "base/process_util.h" +#include "base/logging.h" + +namespace base { + +// static +Process Process::Current() { + return Process(GetCurrentProcessHandle()); +} + +ProcessId Process::pid() const { + if (process_ == 0) + return 0; + + return GetProcId(process_); +} + +bool Process::is_current() const { + return process_ == GetCurrentProcessHandle(); +} + +void Process::Close() { + process_ = 0; + // if the process wasn't terminated (so we waited) or the state + // wasn't already collected w/ a wait from process_utils, we're gonna + // end up w/ a zombie when it does finally exit. +} + +void Process::Terminate(int result_code) { + // result_code isn't supportable. + if (!process_) + return; + // We don't wait here. It's the responsibility of other code to reap the + // child. + KillProcess(process_, result_code, false); +} + +#if !defined(OS_LINUX) +bool Process::IsProcessBackgrounded() const { + // See SetProcessBackgrounded(). + return false; +} + +bool Process::SetProcessBackgrounded(bool value) { + // POSIX only allows lowering the priority of a process, so if we + // were to lower it we wouldn't be able to raise it back to its initial + // priority. + return false; +} + +// static +bool Process::CanBackgroundProcesses() { + return false; +} + +#endif + +int Process::GetPriority() const { + DCHECK(process_); + return getpriority(PRIO_PROCESS, process_); +} + +} // namspace base
diff --git a/src/base/process_starboard.cc b/src/base/process_starboard.cc new file mode 100644 index 0000000..e753b27 --- /dev/null +++ b/src/base/process_starboard.cc
@@ -0,0 +1,61 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Starboard doesn't support the concept of processes, so this implementation +// just minimally and trivially implements the functions of this interface that +// are called, but that do not impact the functioning of the rest of the system. + +#include "base/process.h" + +#include "base/process_util.h" + +namespace base { + +// static +Process Process::Current() { + return Process(); +} + +ProcessId Process::pid() const { + return kNullProcessId; +} + +bool Process::is_current() const { + return true; +} + +void Process::Close() { + process_ = kNullProcessHandle; +} + +void Process::Terminate(int result_code) {} + +bool Process::IsProcessBackgrounded() const { + return false; +} + +bool Process::SetProcessBackgrounded(bool value) { + return false; +} + +// static +bool Process::CanBackgroundProcesses() { + return false; +} + +int Process::GetPriority() const { + return 0; +} + +} // namespace base
diff --git a/src/base/process_util.cc b/src/base/process_util.cc new file mode 100644 index 0000000..9979dfa --- /dev/null +++ b/src/base/process_util.cc
@@ -0,0 +1,79 @@ +// 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/process_util.h" + +namespace base { + +#if defined(OS_POSIX) +ProcessEntry::ProcessEntry() : pid_(0), ppid_(0), gid_(0) {} +ProcessEntry::~ProcessEntry() {} +#endif + +int GetProcessCount(const FilePath::StringType& executable_name, + const ProcessFilter* filter) { + int count = 0; + NamedProcessIterator iter(executable_name, filter); + while (iter.NextProcessEntry()) + ++count; + return count; +} + +bool KillProcesses(const FilePath::StringType& executable_name, int exit_code, + const ProcessFilter* filter) { + bool result = true; + NamedProcessIterator iter(executable_name, filter); + while (const ProcessEntry* entry = iter.NextProcessEntry()) { +#if defined(OS_WIN) + result &= KillProcessById(entry->pid(), exit_code, true); +#else + result &= KillProcess(entry->pid(), exit_code, true); +#endif + } + return result; +} + +const ProcessEntry* ProcessIterator::NextProcessEntry() { + bool result = false; + do { + result = CheckForNextProcess(); + } while (result && !IncludeEntry()); + if (result) + return &entry_; + return NULL; +} + +ProcessIterator::ProcessEntries ProcessIterator::Snapshot() { + ProcessEntries found; + while (const ProcessEntry* process_entry = NextProcessEntry()) { + found.push_back(*process_entry); + } + return found; +} + +bool ProcessIterator::IncludeEntry() { + return !filter_ || filter_->Includes(entry_); +} + +NamedProcessIterator::NamedProcessIterator( + const FilePath::StringType& executable_name, + const ProcessFilter* filter) : ProcessIterator(filter), + executable_name_(executable_name) { +#if defined(OS_ANDROID) + // On Android, the process name contains only the last 15 characters, which + // is in file /proc/<pid>/stat, the string between open parenthesis and close + // parenthesis. Please See ProcessIterator::CheckForNextProcess for details. + // Now if the length of input process name is greater than 15, only save the + // last 15 characters. + if (executable_name_.size() > 15) { + executable_name_ = FilePath::StringType(executable_name_, + executable_name_.size() - 15, 15); + } +#endif +} + +NamedProcessIterator::~NamedProcessIterator() { +} + +} // namespace base
diff --git a/src/base/process_util.h b/src/base/process_util.h new file mode 100644 index 0000000..38fbe1f --- /dev/null +++ b/src/base/process_util.h
@@ -0,0 +1,910 @@ +// 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. + +// This file/namespace contains utility functions for enumerating, ending and +// computing statistics of processes. + +#ifndef BASE_PROCESS_UTIL_H_ +#define BASE_PROCESS_UTIL_H_ + +#include "base/basictypes.h" +#include "base/time.h" + +#if defined(OS_WIN) +#include <windows.h> +#include <tlhelp32.h> +#elif defined(OS_MACOSX) || defined(OS_BSD) +// kinfo_proc is defined in <sys/sysctl.h>, but this forward declaration +// is sufficient for the vector<kinfo_proc> below. +struct kinfo_proc; +// malloc_zone_t is defined in <malloc/malloc.h>, but this forward declaration +// is sufficient for GetPurgeableZone() below. +typedef struct _malloc_zone_t malloc_zone_t; +#if !defined(OS_BSD) +#include <mach/mach.h> +#endif +#elif defined(OS_POSIX) +#include <dirent.h> +#include <limits.h> +#include <sys/types.h> +#endif + +#include <list> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "base/base_export.h" +#include "base/file_path.h" +#include "base/process.h" + +#if defined(OS_POSIX) +#include "base/posix/file_descriptor_shuffle.h" +#endif + +class CommandLine; + +namespace base { + +#if defined(OS_WIN) +struct ProcessEntry : public PROCESSENTRY32 { + ProcessId pid() const { return th32ProcessID; } + ProcessId parent_pid() const { return th32ParentProcessID; } + const wchar_t* exe_file() const { return szExeFile; } +}; + +struct IoCounters : public IO_COUNTERS { +}; + +// Process access masks. These constants provide platform-independent +// definitions for the standard Windows access masks. +// See http://msdn.microsoft.com/en-us/library/ms684880(VS.85).aspx for +// the specific semantics of each mask value. +const uint32 kProcessAccessTerminate = PROCESS_TERMINATE; +const uint32 kProcessAccessCreateThread = PROCESS_CREATE_THREAD; +const uint32 kProcessAccessSetSessionId = PROCESS_SET_SESSIONID; +const uint32 kProcessAccessVMOperation = PROCESS_VM_OPERATION; +const uint32 kProcessAccessVMRead = PROCESS_VM_READ; +const uint32 kProcessAccessVMWrite = PROCESS_VM_WRITE; +const uint32 kProcessAccessDuplicateHandle = PROCESS_DUP_HANDLE; +const uint32 kProcessAccessCreateProcess = PROCESS_CREATE_PROCESS; +const uint32 kProcessAccessSetQuota = PROCESS_SET_QUOTA; +const uint32 kProcessAccessSetInformation = PROCESS_SET_INFORMATION; +const uint32 kProcessAccessQueryInformation = PROCESS_QUERY_INFORMATION; +const uint32 kProcessAccessSuspendResume = PROCESS_SUSPEND_RESUME; +const uint32 kProcessAccessQueryLimitedInfomation = + PROCESS_QUERY_LIMITED_INFORMATION; +const uint32 kProcessAccessWaitForTermination = SYNCHRONIZE; +#elif defined(OS_POSIX) + +struct ProcessEntry { + ProcessEntry(); + ~ProcessEntry(); + + ProcessId pid() const { return pid_; } + ProcessId parent_pid() const { return ppid_; } + ProcessId gid() const { return gid_; } + const char* exe_file() const { return exe_file_.c_str(); } + const std::vector<std::string>& cmd_line_args() const { + return cmd_line_args_; + } + + ProcessId pid_; + ProcessId ppid_; + ProcessId gid_; + std::string exe_file_; + std::vector<std::string> cmd_line_args_; +}; + +struct IoCounters { + uint64_t ReadOperationCount; + uint64_t WriteOperationCount; + uint64_t OtherOperationCount; + uint64_t ReadTransferCount; + uint64_t WriteTransferCount; + uint64_t OtherTransferCount; +}; + +// Process access masks. They are not used on Posix because access checking +// does not happen during handle creation. +const uint32 kProcessAccessTerminate = 0; +const uint32 kProcessAccessCreateThread = 0; +const uint32 kProcessAccessSetSessionId = 0; +const uint32 kProcessAccessVMOperation = 0; +const uint32 kProcessAccessVMRead = 0; +const uint32 kProcessAccessVMWrite = 0; +const uint32 kProcessAccessDuplicateHandle = 0; +const uint32 kProcessAccessCreateProcess = 0; +const uint32 kProcessAccessSetQuota = 0; +const uint32 kProcessAccessSetInformation = 0; +const uint32 kProcessAccessQueryInformation = 0; +const uint32 kProcessAccessSuspendResume = 0; +const uint32 kProcessAccessQueryLimitedInfomation = 0; +const uint32 kProcessAccessWaitForTermination = 0; +#endif // defined(OS_POSIX) + +// Return status values from GetTerminationStatus. Don't use these as +// exit code arguments to KillProcess*(), use platform/application +// specific values instead. +enum TerminationStatus { + TERMINATION_STATUS_NORMAL_TERMINATION, // zero exit status + TERMINATION_STATUS_ABNORMAL_TERMINATION, // non-zero exit status + 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 + TERMINATION_STATUS_MAX_ENUM +}; + +#if defined(USE_LINUX_BREAKPAD) +BASE_EXPORT extern size_t g_oom_size; +#endif + +#if defined(OS_WIN) +// Output multi-process printf, cout, cerr, etc to the cmd.exe console that ran +// chrome. This is not thread-safe: only call from main thread. +BASE_EXPORT void RouteStdioToConsole(); +#endif + +// Returns the id of the current process. +BASE_EXPORT ProcessId GetCurrentProcId(); + +// Returns the ProcessHandle of the current process. +BASE_EXPORT ProcessHandle GetCurrentProcessHandle(); + +#if defined(OS_WIN) +// Returns the module handle to which an address belongs. The reference count +// of the module is not incremented. +BASE_EXPORT HMODULE GetModuleFromAddress(void* address); +#endif + +#if !defined(OS_STARBOARD) +// Converts a PID to a process handle. This handle must be closed by +// CloseProcessHandle when you are done with it. Returns true on success. +BASE_EXPORT bool OpenProcessHandle(ProcessId pid, ProcessHandle* handle); + +// Converts a PID to a process handle. On Windows the handle is opened +// with more access rights and must only be used by trusted code. +// You have to close returned handle using CloseProcessHandle. Returns true +// on success. +// TODO(sanjeevr): Replace all calls to OpenPrivilegedProcessHandle with the +// more specific OpenProcessHandleWithAccess method and delete this. +BASE_EXPORT bool OpenPrivilegedProcessHandle(ProcessId pid, + ProcessHandle* handle); + +// Converts a PID to a process handle using the desired access flags. Use a +// combination of the kProcessAccess* flags defined above for |access_flags|. +BASE_EXPORT bool OpenProcessHandleWithAccess(ProcessId pid, + uint32 access_flags, + ProcessHandle* handle); + +// Closes the process handle opened by OpenProcessHandle. +BASE_EXPORT void CloseProcessHandle(ProcessHandle process); + +// Returns the unique ID for the specified process. This is functionally the +// same as Windows' GetProcessId(), but works on versions of Windows before +// Win XP SP1 as well. +BASE_EXPORT ProcessId GetProcId(ProcessHandle process); +#endif // !defined(OS_STARBOARD) + +#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_BSD) +// Returns the path to the executable of the given process. +BASE_EXPORT FilePath GetProcessExecutablePath(ProcessHandle process); +#endif + +#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(__LB_ANDROID__) +// Parse the data found in /proc/<pid>/stat and return the sum of the +// CPU-related ticks. Returns -1 on parse error. +// Exposed for testing. +BASE_EXPORT int ParseProcStatCPU(const std::string& input); + +// The maximum allowed value for the OOM score. +const int kMaxOomScore = 1000; + +// This adjusts /proc/<pid>/oom_score_adj so the Linux OOM killer will +// prefer to kill certain process types over others. The range for the +// adjustment is [-1000, 1000], with [0, 1000] being user accessible. +// If the Linux system doesn't support the newer oom_score_adj range +// of [0, 1000], then we revert to using the older oom_adj, and +// translate the given value into [0, 15]. Some aliasing of values +// may occur in that case, of course. +BASE_EXPORT bool AdjustOOMScore(ProcessId process, int score); + +// /proc/self/exe refers to the current executable. +BASE_EXPORT extern const char kProcSelfExe[]; +#endif // defined(OS_LINUX) || defined(OS_ANDROID) + +#if defined(OS_POSIX) +// Returns the ID for the parent of the given process. +BASE_EXPORT ProcessId GetParentProcessId(ProcessHandle process); + +// Close all file descriptors, except those which are a destination in the +// given multimap. Only call this function in a child process where you know +// that there aren't any other threads. +BASE_EXPORT void CloseSuperfluousFds(const InjectiveMultimap& saved_map); +#endif // defined(OS_POSIX) + +typedef std::vector<std::pair<std::string, std::string> > EnvironmentVector; +typedef std::vector<std::pair<int, int> > FileHandleMappingVector; + +#if defined(OS_MACOSX) +// Used with LaunchOptions::synchronize and LaunchSynchronize, a +// LaunchSynchronizationHandle is an opaque value that LaunchProcess will +// create and set, and that LaunchSynchronize will consume and destroy. +typedef int* LaunchSynchronizationHandle; +#endif // defined(OS_MACOSX) + +// Options for launching a subprocess that are passed to LaunchProcess(). +// The default constructor constructs the object with default options. +struct LaunchOptions { + LaunchOptions() : wait(false), +#if defined(OS_WIN) + start_hidden(false), inherit_handles(false), as_user(NULL), + empty_desktop_name(false), job_handle(NULL), + force_breakaway_from_job_(false) +#else + environ(NULL), fds_to_remap(NULL), maximize_rlimits(NULL), + new_process_group(false) +#if defined(OS_LINUX) + , clone_flags(0) +#endif // OS_LINUX +#if defined(OS_CHROMEOS) + , ctrl_terminal_fd(-1) +#endif // OS_CHROMEOS +#if defined(OS_MACOSX) + , synchronize(NULL) +#endif // defined(OS_MACOSX) +#endif // !defined(OS_WIN) + {} + + // If true, wait for the process to complete. + bool wait; + +#if defined(OS_WIN) + bool start_hidden; + + // If true, the new process inherits handles from the parent. + bool inherit_handles; + + // 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. + // + // To avoid hard to diagnose problems, when specified this loads the + // environment variables associated with the user and if this operation fails + // the entire call fails as well. + UserTokenHandle as_user; + + // 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 + // be terminated immediately and LaunchProcess() will fail if assignment to + // the job object fails. + HANDLE job_handle; + + // If set to true, ensures that the child process is launched with the + // CREATE_BREAKAWAY_FROM_JOB flag which allows it to breakout of the parent + // job if any. + bool force_breakaway_from_job_; +#else + // If non-NULL, set/unset environment variables. + // See documentation of AlterEnvironment(). + // This pointer is owned by the caller and must live through the + // call to LaunchProcess(). + const EnvironmentVector* environ; + + // 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(). + const FileHandleMappingVector* fds_to_remap; + + // Each element is an RLIMIT_* constant that should be raised to its + // rlim_max. This pointer is owned by the caller and must live through + // the call to LaunchProcess(). + const std::set<int>* maximize_rlimits; + + // If true, start the process in a new process group, instead of + // inheriting the parent's process group. The pgid of the child process + // will be the same as its pid. + bool new_process_group; + +#if defined(OS_LINUX) + // If non-zero, start the process using clone(), using flags as provided. + int clone_flags; +#endif // defined(OS_LINUX) + +#if defined(OS_CHROMEOS) + // If non-negative, the specified file descriptor will be set as the launched + // process' controlling terminal. + int ctrl_terminal_fd; +#endif // defined(OS_CHROMEOS) + +#if defined(OS_MACOSX) + // When non-NULL, a new LaunchSynchronizationHandle will be created and + // stored in *synchronize whenever LaunchProcess returns true in the parent + // process. The child process will have been created (with fork) but will + // be waiting (before exec) for the parent to call LaunchSynchronize with + // this handle. Only when LaunchSynchronize is called will the child be + // permitted to continue execution and call exec. LaunchSynchronize + // destroys the handle created by LaunchProcess. + // + // When synchronize is non-NULL, the parent must call LaunchSynchronize + // whenever LaunchProcess returns true. No exceptions. + // + // Synchronization is useful when the parent process needs to guarantee that + // it can take some action (such as recording the newly-forked child's + // process ID) before the child does something (such as using its process ID + // to communicate with its parent). + // + // |synchronize| and |wait| must not both be set simultaneously. + LaunchSynchronizationHandle* synchronize; +#endif // defined(OS_MACOSX) + +#endif // !defined(OS_WIN) +}; + +#if !defined(OS_STARBOARD) +// Launch a process via the command line |cmdline|. +// See the documentation of LaunchOptions for details on |options|. +// +// Returns true upon success. +// +// 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. +// +// Unix-specific notes: +// - All file descriptors open in the parent process will be closed in the +// child process except for any preserved by options::fds_to_remap, and +// stdin, stdout, and stderr. If not remapped by options::fds_to_remap, +// stdin is reopened as /dev/null, and the child is allowed to inherit its +// parent's stdout and stderr. +// - If the first argument on the command line does not contain a slash, +// PATH will be searched. (See man execvp.) +BASE_EXPORT bool LaunchProcess(const CommandLine& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle); +#endif // !defined(OS_STARBOARD) + +#if defined(OS_WIN) + +enum IntegrityLevel { + INTEGRITY_UNKNOWN, + LOW_INTEGRITY, + MEDIUM_INTEGRITY, + HIGH_INTEGRITY, +}; +// Determine the integrity level of the specified process. Returns false +// if the system does not support integrity levels (pre-Vista) or in the case +// of an underlying system failure. +BASE_EXPORT bool GetProcessIntegrityLevel(ProcessHandle process, + IntegrityLevel* level); + +// Windows-specific LaunchProcess that takes the command line as a +// string. Useful for situations where you need to control the +// command line arguments directly, but prefer the CommandLine version +// if launching Chrome itself. +// +// The first command line argument should be the path to the process, +// and don't forget to quote it. +// +// Example (including literal quotes) +// cmdline = "c:\windows\explorer.exe" -foo "c:\bar\" +BASE_EXPORT bool LaunchProcess(const string16& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle); + +#elif defined(OS_POSIX) +// A POSIX-specific version of LaunchProcess that takes an argv array +// instead of a CommandLine. Useful for situations where you need to +// control the command line arguments directly, but prefer the +// CommandLine version if launching Chrome itself. +BASE_EXPORT bool LaunchProcess(const std::vector<std::string>& argv, + const LaunchOptions& options, + ProcessHandle* process_handle); + +// AlterEnvironment returns a modified environment vector, constructed from the +// given environment and the list of changes given in |changes|. Each key in +// the environment is matched against the first element of the pairs. In the +// event of a match, the value is replaced by the second of the pair, unless +// the second is empty, in which case the key-value is removed. +// +// The returned array is allocated using new[] and must be freed by the caller. +BASE_EXPORT char** AlterEnvironment(const EnvironmentVector& changes, + const char* const* const env); + +#if defined(OS_MACOSX) + +// After a successful call to LaunchProcess with LaunchOptions::synchronize +// set, the parent process must call LaunchSynchronize to allow the child +// process to proceed, and to destroy the LaunchSynchronizationHandle. +BASE_EXPORT void LaunchSynchronize(LaunchSynchronizationHandle handle); + +#endif // defined(OS_MACOSX) +#endif // defined(OS_POSIX) + +#if defined(OS_WIN) +// Set JOBOBJECT_EXTENDED_LIMIT_INFORMATION to JobObject |job_object|. +// As its limit_info.BasicLimitInformation.LimitFlags has +// JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. +// When the provide JobObject |job_object| is closed, the binded process will +// be terminated. +BASE_EXPORT bool SetJobObjectAsKillOnJobClose(HANDLE job_object); +#endif // defined(OS_WIN) + +#if !defined(OS_STARBOARD) +// Executes the application specified by |cl| and wait for it to exit. Stores +// the output (stdout) in |output|. Redirects stderr to /dev/null. Returns true +// on success (application launched and exited cleanly, with exit code +// indicating success). +BASE_EXPORT bool GetAppOutput(const CommandLine& cl, std::string* output); +#endif // !defined(OS_STARBOARD) + +#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 +// control the command line arguments directly. +BASE_EXPORT bool GetAppOutput(const std::vector<std::string>& argv, + std::string* output); + +// A restricted version of |GetAppOutput()| which (a) clears the environment, +// and (b) stores at most |max_output| bytes; also, it doesn't search the path +// for the command. +BASE_EXPORT bool GetAppOutputRestricted(const CommandLine& cl, + std::string* output, size_t max_output); + +// A version of |GetAppOutput()| which also returns the exit code of the +// executed command. Returns true if the application runs and exits cleanly. If +// this is the case the exit code of the application is available in +// |*exit_code|. +BASE_EXPORT bool GetAppOutputWithExitCode(const CommandLine& cl, + std::string* output, int* exit_code); +#endif // defined(OS_POSIX) + +#if !defined(OS_STARBOARD) +// Used to filter processes by process ID. +class ProcessFilter { + public: + // Returns true to indicate set-inclusion and false otherwise. This method + // should not have side-effects and should be idempotent. + virtual bool Includes(const ProcessEntry& entry) const = 0; + + protected: + virtual ~ProcessFilter() {} +}; + +// Returns the number of processes on the machine that are running from the +// given executable name. If filter is non-null, then only processes selected +// by the filter will be counted. +BASE_EXPORT int GetProcessCount(const FilePath::StringType& executable_name, + const ProcessFilter* filter); + +// Attempts to kill all the processes on the current machine that were launched +// from the given executable name, ending them with the given exit code. If +// filter is non-null, then only processes selected by the filter are killed. +// Returns true if all processes were able to be killed off, false if at least +// one couldn't be killed. +BASE_EXPORT bool KillProcesses(const FilePath::StringType& executable_name, + int exit_code, const ProcessFilter* filter); + +// Attempts to kill the process identified by the given process +// entry structure, giving it the specified exit code. If |wait| is true, wait +// for the process to be actually terminated before returning. +// Returns true if this is successful, false otherwise. +BASE_EXPORT bool KillProcess(ProcessHandle process, int exit_code, bool wait); +#endif // !defined(OS_STARBOARD) + +#if defined(OS_POSIX) +// Attempts to kill the process group identified by |process_group_id|. Returns +// true on success. +BASE_EXPORT bool KillProcessGroup(ProcessHandle process_group_id); +#endif // defined(OS_POSIX) + +#if defined(OS_WIN) +BASE_EXPORT bool KillProcessById(ProcessId process_id, int exit_code, + bool wait); +#endif // defined(OS_WIN) + +#if !defined(OS_STARBOARD) +// Get the termination status of the process by interpreting the +// circumstances of the child process' death. |exit_code| is set to +// the status returned by waitpid() on POSIX, and from +// GetExitCodeProcess() on Windows. |exit_code| may be NULL if the +// caller is not interested in it. Note that on Linux, this function +// will only return a useful result the first time it is called after +// the child exits (because it will reap the child and the information +// will no longer be available). +BASE_EXPORT TerminationStatus GetTerminationStatus(ProcessHandle handle, + int* exit_code); +#endif // !defined(OS_STARBOARD) + +#if defined(OS_POSIX) +// Wait for the process to exit and get the termination status. See +// GetTerminationStatus for more information. On POSIX systems, we can't call +// WaitForExitCode and then GetTerminationStatus as the child will be reaped +// when WaitForExitCode return and this information will be lost. +BASE_EXPORT TerminationStatus WaitForTerminationStatus(ProcessHandle handle, + int* exit_code); +#endif // defined(OS_POSIX) + +#if !defined(OS_STARBOARD) +// Waits for process to exit. On POSIX systems, if the process hasn't been +// signaled then puts the exit code in |exit_code|; otherwise it's considered +// a failure. On Windows |exit_code| is always filled. Returns true on success, +// and closes |handle| in any case. +BASE_EXPORT bool WaitForExitCode(ProcessHandle handle, int* exit_code); + +// Waits for process to exit. If it did exit within |timeout_milliseconds|, +// then puts the exit code in |exit_code|, and returns true. +// In POSIX systems, if the process has been signaled then |exit_code| is set +// to -1. Returns false on failure (the caller is then responsible for closing +// |handle|). +// The caller is always responsible for closing the |handle|. +BASE_EXPORT bool WaitForExitCodeWithTimeout(ProcessHandle handle, + int* exit_code, + base::TimeDelta timeout); + +// Wait for all the processes based on the named executable to exit. If filter +// is non-null, then only processes selected by the filter are waited on. +// Returns after all processes have exited or wait_milliseconds have expired. +// Returns true if all the processes exited, false otherwise. +BASE_EXPORT bool WaitForProcessesToExit( + const FilePath::StringType& executable_name, + base::TimeDelta wait, + const ProcessFilter* filter); + +// Wait for a single process to exit. Return true if it exited cleanly within +// the given time limit. On Linux |handle| must be a child process, however +// on Mac and Windows it can be any process. +BASE_EXPORT bool WaitForSingleProcess(ProcessHandle handle, + base::TimeDelta wait); + +// Waits a certain amount of time (can be 0) for all the processes with a given +// executable name to exit, then kills off any of them that are still around. +// If filter is non-null, then only processes selected by the filter are waited +// on. Killed processes are ended with the given exit code. Returns false if +// any processes needed to be killed, true if they all exited cleanly within +// the wait_milliseconds delay. +BASE_EXPORT bool CleanupProcesses(const FilePath::StringType& executable_name, + base::TimeDelta wait, + int exit_code, + const ProcessFilter* filter); + +// This method ensures that the specified process eventually terminates, and +// then it closes the given process handle. +// +// It assumes that the process has already been signalled to exit, and it +// begins by waiting a small amount of time for it to exit. If the process +// does not appear to have exited, then this function starts to become +// aggressive about ensuring that the process terminates. +// +// On Linux this method does not block the calling thread. +// On OS X this method may block for up to 2 seconds. +// +// NOTE: The process handle must have been opened with the PROCESS_TERMINATE +// and SYNCHRONIZE permissions. +// +BASE_EXPORT void EnsureProcessTerminated(ProcessHandle process_handle); +#endif // !defined(OS_STARBOARD) + +#if defined(OS_POSIX) && !defined(OS_MACOSX) +// The nicer version of EnsureProcessTerminated() that is patient and will +// wait for |process_handle| to finish and then reap it. +BASE_EXPORT void EnsureProcessGetsReaped(ProcessHandle process_handle); +#endif + +#if !defined(OS_STARBOARD) +// This class provides a way to iterate through a list of processes on the +// current machine with a specified filter. +// To use, create an instance and then call NextProcessEntry() until it returns +// false. +class BASE_EXPORT ProcessIterator { + public: + typedef std::list<ProcessEntry> ProcessEntries; + + explicit ProcessIterator(const ProcessFilter* filter); + virtual ~ProcessIterator(); + + // If there's another process that matches the given executable name, + // returns a const pointer to the corresponding PROCESSENTRY32. + // If there are no more matching processes, returns NULL. + // The returned pointer will remain valid until NextProcessEntry() + // is called again or this NamedProcessIterator goes out of scope. + const ProcessEntry* NextProcessEntry(); + + // Takes a snapshot of all the ProcessEntry found. + ProcessEntries Snapshot(); + + protected: + virtual bool IncludeEntry(); + const ProcessEntry& entry() { return entry_; } + + private: + // Determines whether there's another process (regardless of executable) + // left in the list of all processes. Returns true and sets entry_ to + // that process's info if there is one, false otherwise. + bool CheckForNextProcess(); + + // Initializes a PROCESSENTRY32 data structure so that it's ready for + // use with Process32First/Process32Next. + void InitProcessEntry(ProcessEntry* entry); + +#if defined(OS_WIN) + HANDLE snapshot_; + bool started_iteration_; +#elif defined(OS_MACOSX) || defined(OS_BSD) + std::vector<kinfo_proc> kinfo_procs_; + size_t index_of_kinfo_proc_; +#elif defined(OS_POSIX) + DIR* procfs_dir_; +#endif + ProcessEntry entry_; + const ProcessFilter* filter_; + + DISALLOW_COPY_AND_ASSIGN(ProcessIterator); +}; + +// This class provides a way to iterate through the list of processes +// on the current machine that were started from the given executable +// name. To use, create an instance and then call NextProcessEntry() +// until it returns false. +class BASE_EXPORT NamedProcessIterator : public ProcessIterator { + public: + NamedProcessIterator(const FilePath::StringType& executable_name, + const ProcessFilter* filter); + virtual ~NamedProcessIterator(); + + protected: + virtual bool IncludeEntry() OVERRIDE; + + private: + FilePath::StringType executable_name_; + + DISALLOW_COPY_AND_ASSIGN(NamedProcessIterator); +}; + +// Working Set (resident) memory usage broken down by +// +// On Windows: +// priv (private): These pages (kbytes) cannot be shared with any other process. +// shareable: These pages (kbytes) can be shared with other processes under +// the right circumstances. +// shared : These pages (kbytes) are currently shared with at least one +// other process. +// +// On Linux: +// priv: Pages mapped only by this process +// shared: PSS or 0 if the kernel doesn't support this +// shareable: 0 +// +// On OS X: TODO(thakis): Revise. +// priv: Memory. +// shared: 0 +// shareable: 0 +struct WorkingSetKBytes { + WorkingSetKBytes() : priv(0), shareable(0), shared(0) {} + size_t priv; + size_t shareable; + size_t shared; +}; + +// Committed (resident + paged) memory usage broken down by +// private: These pages cannot be shared with any other process. +// mapped: These pages are mapped into the view of a section (backed by +// pagefile.sys) +// image: These pages are mapped into the view of an image section (backed by +// file system) +struct CommittedKBytes { + CommittedKBytes() : priv(0), mapped(0), image(0) {} + size_t priv; + size_t mapped; + size_t image; +}; + +// Free memory (Megabytes marked as free) in the 2G process address space. +// total : total amount in megabytes marked as free. Maximum value is 2048. +// largest : size of the largest contiguous amount of memory found. It is +// always smaller or equal to FreeMBytes::total. +// largest_ptr: starting address of the largest memory block. +struct FreeMBytes { + size_t total; + size_t largest; + void* largest_ptr; +}; + +// Convert a POSIX timeval to microseconds. +BASE_EXPORT int64 TimeValToMicroseconds(const struct timeval& tv); + +// Provides performance metrics for a specified process (CPU usage, memory and +// IO counters). To use it, invoke CreateProcessMetrics() to get an instance +// for a specific process, then access the information with the different get +// methods. +class BASE_EXPORT ProcessMetrics { + public: + ~ProcessMetrics(); + + // Creates a ProcessMetrics for the specified process. + // The caller owns the returned object. +#if !defined(OS_MACOSX) || defined(OS_IOS) + static ProcessMetrics* CreateProcessMetrics(ProcessHandle process); +#else + class PortProvider { + public: + virtual ~PortProvider() {} + + // Should return the mach task for |process| if possible, or else + // |MACH_PORT_NULL|. Only processes that this returns tasks for will have + // metrics on OS X (except for the current process, which always gets + // metrics). + virtual mach_port_t TaskForPid(ProcessHandle process) const = 0; + }; + + // The port provider needs to outlive the ProcessMetrics object returned by + // this function. If NULL is passed as provider, the returned object + // only returns valid metrics if |process| is the current process. + static ProcessMetrics* CreateProcessMetrics(ProcessHandle process, + PortProvider* port_provider); +#endif // !defined(OS_MACOSX) || defined(OS_IOS) + + // Returns the current space allocated for the pagefile, in bytes (these pages + // may or may not be in memory). On Linux, this returns the total virtual + // memory size. + size_t GetPagefileUsage() const; + // Returns the peak space allocated for the pagefile, in bytes. + size_t GetPeakPagefileUsage() const; + // Returns the current working set size, in bytes. On Linux, this returns + // the resident set size. + size_t GetWorkingSetSize() const; + // Returns the peak working set size, in bytes. + size_t GetPeakWorkingSetSize() const; + // Returns private and sharedusage, in bytes. Private bytes is the amount of + // memory currently allocated to a process that cannot be shared. Returns + // false on platform specific error conditions. Note: |private_bytes| + // returns 0 on unsupported OSes: prior to XP SP2. + bool GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes); + // Fills a CommittedKBytes with both resident and paged + // memory usage as per definition of CommittedBytes. + void GetCommittedKBytes(CommittedKBytes* usage) const; + // Fills a WorkingSetKBytes containing resident private and shared memory + // usage in bytes, as per definition of WorkingSetBytes. + bool GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const; + + // Computes the current process available memory for allocation. + // It does a linear scan of the address space querying each memory region + // for its free (unallocated) status. It is useful for estimating the memory + // 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. + double GetCPUUsage(); + + // Retrieves accounting information for all I/O operations performed by the + // process. + // If IO information is retrieved successfully, the function returns true + // and fills in the IO_COUNTERS passed in. The function returns false + // otherwise. + bool GetIOCounters(IoCounters* io_counters) const; + + private: +#if !defined(OS_MACOSX) || defined(OS_IOS) + explicit ProcessMetrics(ProcessHandle process); +#else + ProcessMetrics(ProcessHandle process, PortProvider* port_provider); +#endif // !defined(OS_MACOSX) || defined(OS_IOS) + + ProcessHandle process_; + + int processor_count_; + + // Used to store the previous times and CPU usage counts so we can + // compute the CPU usage between calls. + int64 last_time_; + int64 last_system_time_; + +#if !defined(OS_IOS) +#if defined(OS_MACOSX) + // Queries the port provider if it's set. + mach_port_t TaskForPid(ProcessHandle process) const; + + PortProvider* port_provider_; +#elif defined(OS_POSIX) + // Jiffie count at the last_time_ we updated. + int last_cpu_; +#endif // defined(OS_POSIX) +#endif // !defined(OS_IOS) + + DISALLOW_COPY_AND_ASSIGN(ProcessMetrics); +}; +#endif // !defined(OS_STARBOARD) + +#if defined(OS_LINUX) || defined(OS_ANDROID) +// Data from /proc/meminfo about system-wide memory consumption. +// Values are in KB. +struct BASE_EXPORT SystemMemoryInfoKB { + SystemMemoryInfoKB(); + + int total; + int free; + int buffers; + int cached; + int active_anon; + int inactive_anon; + int active_file; + int inactive_file; + int shmem; + + // Gem data will be -1 if not supported. + int gem_objects; + long long gem_size; +}; +// Retrieves data from /proc/meminfo about system-wide memory consumption. +// Fills in the provided |meminfo| structure. Returns true on success. +// Exposed for memory debugging widget. +BASE_EXPORT bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo); +#endif // defined(OS_LINUX) || defined(OS_ANDROID) + +#if !defined(OS_STARBOARD) +// Returns the memory committed by the system in KBytes. +// Returns 0 if it can't compute the commit charge. +BASE_EXPORT size_t GetSystemCommitCharge(); + +// Enables low fragmentation heap (LFH) for every heaps of this process. This +// won't have any effect on heaps created after this function call. It will not +// modify data allocated in the heaps before calling this function. So it is +// better to call this function early in initialization and again before +// entering the main loop. +// Note: Returns true on Windows 2000 without doing anything. +BASE_EXPORT bool EnableLowFragmentationHeap(); +#endif // !defined(OS_STARBOARD) + +// Enables 'terminate on heap corruption' flag. Helps protect against heap +// overflow. Has no effect if the OS doesn't provide the necessary facility. +BASE_EXPORT void EnableTerminationOnHeapCorruption(); + +#if !defined(OS_STARBOARD) +// Turns on process termination if memory runs out. +BASE_EXPORT void EnableTerminationOnOutOfMemory(); + +// If supported on the platform, and the user has sufficent rights, increase +// the current process's scheduling priority to a high priority. +BASE_EXPORT void RaiseProcessToHighPriority(); +#endif // !defined(OS_STARBOARD) + +#if defined(OS_MACOSX) +// Restore the default exception handler, setting it to Apple Crash Reporter +// (ReportCrash). When forking and execing a new process, the child will +// inherit the parent's exception ports, which may be set to the Breakpad +// instance running inside the parent. The parent's Breakpad instance should +// not handle the child's exceptions. Calling RestoreDefaultExceptionHandler +// in the child after forking will restore the standard exception handler. +// See http://crbug.com/20371/ for more details. +void RestoreDefaultExceptionHandler(); +#endif // defined(OS_MACOSX) + +#if defined(OS_MACOSX) +// Very large images or svg canvases can cause huge mallocs. Skia +// does tricks on tcmalloc-based systems to allow malloc to fail with +// a NULL rather than hit the oom crasher. This replicates that for +// OSX. +// +// IF YOU USE THIS WITHOUT CONSULTING YOUR FRIENDLY OSX DEVELOPER, +// YOUR CODE IS LIKELY TO BE REVERTED. THANK YOU. +// +// TODO(shess): Weird place to put it, but this is where the OOM +// killer currently lives. +BASE_EXPORT void* UncheckedMalloc(size_t size); +#endif // defined(OS_MACOSX) + +} // namespace base + +#endif // BASE_PROCESS_UTIL_H_
diff --git a/src/base/process_util_freebsd.cc b/src/base/process_util_freebsd.cc new file mode 100644 index 0000000..1157fb2 --- /dev/null +++ b/src/base/process_util_freebsd.cc
@@ -0,0 +1,293 @@ +// 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/process_util.h" + +#include <ctype.h> +#include <dirent.h> +#include <dlfcn.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/sysctl.h> +#include <sys/user.h> +#include <time.h> +#include <unistd.h> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/string_number_conversions.h" +#include "base/string_split.h" +#include "base/string_tokenizer.h" +#include "base/string_util.h" +#include "base/sys_info.h" + +namespace base { + +ProcessId GetParentProcessId(ProcessHandle process) { + struct kinfo_proc info; + size_t length; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process }; + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return -1; + + return info.ki_ppid; +} + +FilePath GetProcessExecutablePath(ProcessHandle process) { + char pathname[PATH_MAX]; + size_t length; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, process }; + + length = sizeof(pathname); + + if (sysctl(mib, arraysize(mib), pathname, &length, NULL, 0) < 0 || + length == 0) { + return FilePath(); + } + + return FilePath(std::string(pathname)); +} + +ProcessIterator::ProcessIterator(const ProcessFilter* filter) + : index_of_kinfo_proc_(), + filter_(filter) { + + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, getuid() }; + + bool done = false; + int try_num = 1; + const int max_tries = 10; + + do { + size_t len = 0; + if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) <0 ){ + LOG(ERROR) << "failed to get the size needed for the process list"; + kinfo_procs_.resize(0); + done = true; + } else { + size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc); + // Leave some spare room for process table growth (more could show up + // between when we check and now) + num_of_kinfo_proc += 16; + kinfo_procs_.resize(num_of_kinfo_proc); + len = num_of_kinfo_proc * sizeof(struct kinfo_proc); + if (sysctl(mib, arraysize(mib), &kinfo_procs_[0], &len, NULL, 0) <0) { + // If we get a mem error, it just means we need a bigger buffer, so + // loop around again. Anything else is a real error and give up. + if (errno != ENOMEM) { + LOG(ERROR) << "failed to get the process list"; + kinfo_procs_.resize(0); + done = true; + } + } else { + // Got the list, just make sure we're sized exactly right + size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc); + kinfo_procs_.resize(num_of_kinfo_proc); + done = true; + } + } + } while (!done && (try_num++ < max_tries)); + + if (!done) { + LOG(ERROR) << "failed to collect the process list in a few tries"; + kinfo_procs_.resize(0); + } +} + +ProcessIterator::~ProcessIterator() { +} + +bool ProcessIterator::CheckForNextProcess() { + std::string data; + + for (; index_of_kinfo_proc_ < kinfo_procs_.size(); ++ index_of_kinfo_proc_) { + size_t length; + struct kinfo_proc kinfo = kinfo_procs_[index_of_kinfo_proc_]; + int mib[] = { CTL_KERN, KERN_PROC_ARGS, kinfo.ki_pid }; + + if ((kinfo.ki_pid > 0) && (kinfo.ki_stat == SZOMB)) + continue; + + length = 0; + if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0) { + LOG(ERROR) << "failed to figure out the buffer size for a command line"; + continue; + } + + data.resize(length); + + if (sysctl(mib, arraysize(mib), &data[0], &length, NULL, 0) < 0) { + LOG(ERROR) << "failed to fetch a commandline"; + continue; + } + + std::string delimiters; + delimiters.push_back('\0'); + Tokenize(data, delimiters, &entry_.cmd_line_args_); + + size_t exec_name_end = data.find('\0'); + if (exec_name_end == std::string::npos) { + LOG(ERROR) << "command line data didn't match expected format"; + continue; + } + + entry_.pid_ = kinfo.ki_pid; + entry_.ppid_ = kinfo.ki_ppid; + entry_.gid_ = kinfo.ki_pgid; + + size_t last_slash = data.rfind('/', exec_name_end); + if (last_slash == std::string::npos) { + entry_.exe_file_.assign(data, 0, exec_name_end); + } else { + entry_.exe_file_.assign(data, last_slash + 1, + exec_name_end - last_slash - 1); + } + + // Start w/ the next entry next time through + ++index_of_kinfo_proc_; + + return true; + } + return false; +} + +bool NamedProcessIterator::IncludeEntry() { + if(executable_name_ != entry().exe_file()) + return false; + + return ProcessIterator::IncludeEntry(); +} + + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + last_time_(0), + last_system_time_(0), + last_cpu_(0) { + processor_count_ = base::SysInfo::NumberOfProcessors(); +} + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + struct kinfo_proc info; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_ }; + size_t length = sizeof(info); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return 0; + + return info.ki_size; +} + +size_t ProcessMetrics::GetPeakPagefileUsage() const { + return 0; +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + struct kinfo_proc info; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_ }; + size_t length = sizeof(info); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return 0; + + return info.ki_rssize * getpagesize(); +} + +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + return 0; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + WorkingSetKBytes ws_usage; + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + if (private_bytes) + *private_bytes = ws_usage.priv << 10; + + if (shared_bytes) + *shared_bytes = ws_usage.shared * 1024; + + return true; +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { +// TODO(bapt) be sure we can't be precise + size_t priv = GetWorkingSetSize(); + if (!priv) + return false; + ws_usage->priv = priv / 1024; + ws_usage->shareable = 0; + ws_usage->shared = 0; + + return true; +} + +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + return false; +} + +double ProcessMetrics::GetCPUUsage() { + struct kinfo_proc info; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_ }; + size_t length = sizeof(info); + + struct timeval now; + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return 0; + + return (info.ki_pctcpu / FSCALE) * 100.0; +} + +size_t GetSystemCommitCharge() { + int mib[2], pagesize; + unsigned long mem_total, mem_free, mem_inactive; + size_t length = sizeof(mem_total); + + if (sysctl(mib, arraysize(mib), &mem_total, &length, NULL, 0) < 0) + return 0; + + length = sizeof(mem_free); + if (sysctlbyname("vm.stats.vm.v_free_count", &mem_free, &length, NULL, 0) < 0) + return 0; + + length = sizeof(mem_inactive); + if (sysctlbyname("vm.stats.vm.v_inactive_count", &mem_inactive, &length, + NULL, 0) < 0) { + return 0; + } + + pagesize = getpagesize(); + + return mem_total - (mem_free*pagesize) - (mem_inactive*pagesize); +} + +void EnableTerminationOnOutOfMemory() { + DLOG(WARNING) << "Not feasible."; +} + +void EnableTerminationOnHeapCorruption() { + // Nothing to do. +} + +bool AdjustOOMScore(ProcessId process, int score) { + NOTIMPLEMENTED(); + return false; +} + +} // namespace base
diff --git a/src/base/process_util_ios.mm b/src/base/process_util_ios.mm new file mode 100644 index 0000000..161d396 --- /dev/null +++ b/src/base/process_util_ios.mm
@@ -0,0 +1,67 @@ +// 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/process_util.h" + +#import <Foundation/Foundation.h> +#include <mach/task.h> +#include <stdio.h> + +#include "base/logging.h" + +// This is just enough of a shim to let the support needed by test_support +// link. In general, process_util isn't valid on iOS. + +namespace base { + +namespace { + +bool GetTaskInfo(task_basic_info_64* task_info_data) { + mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT; + kern_return_t kr = task_info(mach_task_self(), + TASK_BASIC_INFO_64, + reinterpret_cast<task_info_t>(task_info_data), + &count); + return kr == KERN_SUCCESS; +} + +} // namespace + +ProcessId GetCurrentProcId() { + return getpid(); +} + +ProcessHandle GetCurrentProcessHandle() { + return GetCurrentProcId(); +} + +void EnableTerminationOnHeapCorruption() { + // On iOS, there nothing to do AFAIK. +} + +void EnableTerminationOnOutOfMemory() { + // iOS provides this for free! +} + +void RaiseProcessToHighPriority() { + // Impossible on iOS. Do nothing. +} + +ProcessMetrics::ProcessMetrics(ProcessHandle process) {} + +ProcessMetrics::~ProcessMetrics() {} + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(&task_info_data)) + return 0; + return task_info_data.resident_size; +} + +} // namespace base
diff --git a/src/base/process_util_linux.cc b/src/base/process_util_linux.cc new file mode 100644 index 0000000..11937f0 --- /dev/null +++ b/src/base/process_util_linux.cc
@@ -0,0 +1,886 @@ +// 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/process_util.h" + +#include <dirent.h> +#include <malloc.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/string_number_conversions.h" +#include "base/string_split.h" +#include "base/string_tokenizer.h" +#include "base/string_util.h" +#include "base/sys_info.h" +#include "base/threading/thread_restrictions.h" + +namespace { + +enum ParsingState { + KEY_NAME, + KEY_VALUE +}; + +const char kProcDir[] = "/proc"; +const char kStatFile[] = "stat"; + +// Returns a FilePath to "/proc/pid". +FilePath GetProcPidDir(pid_t pid) { + return FilePath(kProcDir).Append(base::IntToString(pid)); +} + +// Fields from /proc/<pid>/stat, 0-based. See man 5 proc. +// If the ordering ever changes, carefully review functions that use these +// values. +enum ProcStatsFields { + VM_COMM = 1, // Filename of executable, without parentheses. + VM_STATE = 2, // Letter indicating the state of the process. + VM_PPID = 3, // PID of the parent. + VM_PGRP = 4, // Process group id. + VM_UTIME = 13, // Time scheduled in user mode in clock ticks. + VM_STIME = 14, // Time scheduled in kernel mode in clock ticks. + VM_VSIZE = 22, // Virtual memory size in bytes. + VM_RSS = 23, // Resident Set Size in pages. +}; + +// Reads /proc/<pid>/stat into |buffer|. Returns true if the file can be read +// and is non-empty. +bool ReadProcStats(pid_t pid, std::string* buffer) { + buffer->clear(); + // Synchronously reading files in /proc is safe. + base::ThreadRestrictions::ScopedAllowIO allow_io; + + FilePath stat_file = GetProcPidDir(pid).Append(kStatFile); + if (!file_util::ReadFileToString(stat_file, buffer)) { + DLOG(WARNING) << "Failed to get process stats."; + return false; + } + return !buffer->empty(); +} + +// Takes |stats_data| and populates |proc_stats| with the values split by +// spaces. Taking into account the 2nd field may, in itself, contain spaces. +// Returns true if successful. +bool ParseProcStats(const std::string& stats_data, + std::vector<std::string>* proc_stats) { + // |stats_data| may be empty if the process disappeared somehow. + // e.g. http://crbug.com/145811 + if (stats_data.empty()) + return false; + + // The stat file is formatted as: + // pid (process name) data1 data2 .... dataN + // Look for the closing paren by scanning backwards, to avoid being fooled by + // processes with ')' in the name. + size_t open_parens_idx = stats_data.find(" ("); + size_t close_parens_idx = stats_data.rfind(") "); + if (open_parens_idx == std::string::npos || + close_parens_idx == std::string::npos || + open_parens_idx > close_parens_idx) { + DLOG(WARNING) << "Failed to find matched parens in '" << stats_data << "'"; + NOTREACHED(); + return false; + } + open_parens_idx++; + + proc_stats->clear(); + // PID. + proc_stats->push_back(stats_data.substr(0, open_parens_idx)); + // Process name without parentheses. + proc_stats->push_back( + stats_data.substr(open_parens_idx + 1, + close_parens_idx - (open_parens_idx + 1))); + + // Split the rest. + std::vector<std::string> other_stats; + base::SplitString(stats_data.substr(close_parens_idx + 2), ' ', &other_stats); + for (size_t i = 0; i < other_stats.size(); ++i) + proc_stats->push_back(other_stats[i]); + return true; +} + +// Reads the |field_num|th field from |proc_stats|. Returns 0 on failure. +// This version does not handle the first 3 values, since the first value is +// simply |pid|, and the next two values are strings. +int GetProcStatsFieldAsInt(const std::vector<std::string>& proc_stats, + ProcStatsFields field_num) { + DCHECK_GE(field_num, VM_PPID); + CHECK_LT(static_cast<size_t>(field_num), proc_stats.size()); + + int value; + return base::StringToInt(proc_stats[field_num], &value) ? value : 0; +} + +// Same as GetProcStatsFieldAsInt(), but for size_t values. +size_t GetProcStatsFieldAsSizeT(const std::vector<std::string>& proc_stats, + ProcStatsFields field_num) { + DCHECK_GE(field_num, VM_PPID); + CHECK_LT(static_cast<size_t>(field_num), proc_stats.size()); + + size_t value; + return base::StringToSizeT(proc_stats[field_num], &value) ? value : 0; +} + +// Convenience wrapper around GetProcStatsFieldAsInt(), ParseProcStats() and +// ReadProcStats(). See GetProcStatsFieldAsInt() for details. +int ReadProcStatsAndGetFieldAsInt(pid_t pid, ProcStatsFields field_num) { + std::string stats_data; + if (!ReadProcStats(pid, &stats_data)) + return 0; + std::vector<std::string> proc_stats; + if (!ParseProcStats(stats_data, &proc_stats)) + return 0; + return GetProcStatsFieldAsInt(proc_stats, field_num); +} + +// Same as ReadProcStatsAndGetFieldAsInt() but for size_t values. +size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid, ProcStatsFields field_num) { + std::string stats_data; + if (!ReadProcStats(pid, &stats_data)) + return 0; + std::vector<std::string> proc_stats; + if (!ParseProcStats(stats_data, &proc_stats)) + return 0; + return GetProcStatsFieldAsSizeT(proc_stats, field_num); +} + +// Reads the |field_num|th field from |proc_stats|. +// Returns an empty string on failure. +// This version only handles VM_COMM and VM_STATE, which are the only fields +// that are strings. +std::string GetProcStatsFieldAsString( + const std::vector<std::string>& proc_stats, + ProcStatsFields field_num) { + if (field_num < VM_COMM || field_num > VM_STATE) { + NOTREACHED(); + return ""; + } + + if (proc_stats.size() > static_cast<size_t>(field_num)) + return proc_stats[field_num]; + + NOTREACHED(); + return 0; +} + +// Reads /proc/<pid>/cmdline and populates |proc_cmd_line_args| with the command +// line arguments. Returns true if successful. +// Note: /proc/<pid>/cmdline contains command line arguments separated by single +// null characters. We tokenize it into a vector of strings using '\0' as a +// delimiter. +bool GetProcCmdline(pid_t pid, std::vector<std::string>* proc_cmd_line_args) { + // Synchronously reading files in /proc is safe. + base::ThreadRestrictions::ScopedAllowIO allow_io; + + FilePath cmd_line_file = GetProcPidDir(pid).Append("cmdline"); + std::string cmd_line; + if (!file_util::ReadFileToString(cmd_line_file, &cmd_line)) + return false; + std::string delimiters; + delimiters.push_back('\0'); + Tokenize(cmd_line, delimiters, proc_cmd_line_args); + return true; +} + +// Take a /proc directory entry named |d_name|, and if it is the directory for +// a process, convert it to a pid_t. +// Returns 0 on failure. +// e.g. /proc/self/ will return 0, whereas /proc/1234 will return 1234. +pid_t ProcDirSlotToPid(const char* d_name) { + int i; + for (i = 0; i < NAME_MAX && d_name[i]; ++i) { + if (!IsAsciiDigit(d_name[i])) { + return 0; + } + } + if (i == NAME_MAX) + return 0; + + // Read the process's command line. + pid_t pid; + std::string pid_string(d_name); + if (!base::StringToInt(pid_string, &pid)) { + NOTREACHED(); + return 0; + } + return pid; +} + +// Get the total CPU of a single process. Return value is number of jiffies +// on success or -1 on error. +int GetProcessCPU(pid_t pid) { + // Use /proc/<pid>/task to find all threads and parse their /stat file. + FilePath task_path = GetProcPidDir(pid).Append("task"); + + DIR* dir = opendir(task_path.value().c_str()); + if (!dir) { + DPLOG(ERROR) << "opendir(" << task_path.value() << ")"; + return -1; + } + + int total_cpu = 0; + while (struct dirent* ent = readdir(dir)) { + pid_t tid = ProcDirSlotToPid(ent->d_name); + if (!tid) + continue; + + // Synchronously reading files in /proc is safe. + base::ThreadRestrictions::ScopedAllowIO allow_io; + + std::string stat; + FilePath stat_path = task_path.Append(ent->d_name).Append(kStatFile); + if (file_util::ReadFileToString(stat_path, &stat)) { + int cpu = base::ParseProcStatCPU(stat); + if (cpu > 0) + total_cpu += cpu; + } + } + closedir(dir); + + return total_cpu; +} + +// Read /proc/<pid>/status and returns the value for |field|, or 0 on failure. +// Only works for fields in the form of "Field: value kB". +size_t ReadProcStatusAndGetFieldAsSizeT(pid_t pid, const std::string& field) { + FilePath stat_file = GetProcPidDir(pid).Append("status"); + std::string status; + { + // Synchronously reading files in /proc is safe. + base::ThreadRestrictions::ScopedAllowIO allow_io; + if (!file_util::ReadFileToString(stat_file, &status)) + return 0; + } + + StringTokenizer tokenizer(status, ":\n"); + ParsingState state = KEY_NAME; + base::StringPiece last_key_name; + while (tokenizer.GetNext()) { + switch (state) { + case KEY_NAME: + last_key_name = tokenizer.token_piece(); + state = KEY_VALUE; + break; + case KEY_VALUE: + DCHECK(!last_key_name.empty()); + if (last_key_name == field) { + std::string value_str; + tokenizer.token_piece().CopyToString(&value_str); + std::string value_str_trimmed; + TrimWhitespaceASCII(value_str, TRIM_ALL, &value_str_trimmed); + std::vector<std::string> split_value_str; + base::SplitString(value_str_trimmed, ' ', &split_value_str); + if (split_value_str.size() != 2 || split_value_str[1] != "kB") { + NOTREACHED(); + return 0; + } + size_t value; + if (!base::StringToSizeT(split_value_str[0], &value)) { + NOTREACHED(); + return 0; + } + return value; + } + state = KEY_NAME; + break; + } + } + NOTREACHED(); + return 0; +} + +} // namespace + +namespace base { + +#if defined(USE_LINUX_BREAKPAD) +size_t g_oom_size = 0U; +#endif + +const char kProcSelfExe[] = "/proc/self/exe"; + +ProcessId GetParentProcessId(ProcessHandle process) { + ProcessId pid = ReadProcStatsAndGetFieldAsInt(process, VM_PPID); + if (pid) + return pid; + return -1; +} + +FilePath GetProcessExecutablePath(ProcessHandle process) { + FilePath stat_file = GetProcPidDir(process).Append("exe"); + FilePath exe_name; + if (!file_util::ReadSymbolicLink(stat_file, &exe_name)) { + // No such process. Happens frequently in e.g. TerminateAllChromeProcesses + return FilePath(); + } + return exe_name; +} + +ProcessIterator::ProcessIterator(const ProcessFilter* filter) + : filter_(filter) { + procfs_dir_ = opendir(kProcDir); +} + +ProcessIterator::~ProcessIterator() { + if (procfs_dir_) { + closedir(procfs_dir_); + procfs_dir_ = NULL; + } +} + +bool ProcessIterator::CheckForNextProcess() { + // TODO(port): skip processes owned by different UID + + pid_t pid = kNullProcessId; + std::vector<std::string> cmd_line_args; + std::string stats_data; + std::vector<std::string> proc_stats; + + // Arbitrarily guess that there will never be more than 200 non-process + // files in /proc. Hardy has 53 and Lucid has 61. + int skipped = 0; + const int kSkipLimit = 200; + while (skipped < kSkipLimit) { + dirent* slot = readdir(procfs_dir_); + // all done looking through /proc? + if (!slot) + return false; + + // If not a process, keep looking for one. + pid = ProcDirSlotToPid(slot->d_name); + if (!pid) { + skipped++; + continue; + } + + if (!GetProcCmdline(pid, &cmd_line_args)) + continue; + + if (!ReadProcStats(pid, &stats_data)) + continue; + if (!ParseProcStats(stats_data, &proc_stats)) + continue; + + std::string runstate = GetProcStatsFieldAsString(proc_stats, VM_STATE); + if (runstate.size() != 1) { + NOTREACHED(); + continue; + } + + // Is the process in 'Zombie' state, i.e. dead but waiting to be reaped? + // Allowed values: D R S T Z + if (runstate[0] != 'Z') + break; + + // Nope, it's a zombie; somebody isn't cleaning up after their children. + // (e.g. WaitForProcessesToExit doesn't clean up after dead children yet.) + // There could be a lot of zombies, can't really decrement i here. + } + if (skipped >= kSkipLimit) { + NOTREACHED(); + return false; + } + + entry_.pid_ = pid; + entry_.ppid_ = GetProcStatsFieldAsInt(proc_stats, VM_PPID); + entry_.gid_ = GetProcStatsFieldAsInt(proc_stats, VM_PGRP); + entry_.cmd_line_args_.assign(cmd_line_args.begin(), cmd_line_args.end()); + + // TODO(port): read pid's commandline's $0, like killall does. Using the + // short name between openparen and closeparen won't work for long names! + entry_.exe_file_ = GetProcStatsFieldAsString(proc_stats, VM_COMM); + return true; +} + +bool NamedProcessIterator::IncludeEntry() { + if (executable_name_ != entry().exe_file()) + return false; + return ProcessIterator::IncludeEntry(); +} + + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +// On linux, we return vsize. +size_t ProcessMetrics::GetPagefileUsage() const { + return ReadProcStatsAndGetFieldAsSizeT(process_, VM_VSIZE); +} + +// On linux, we return the high water mark of vsize. +size_t ProcessMetrics::GetPeakPagefileUsage() const { + return ReadProcStatusAndGetFieldAsSizeT(process_, "VmPeak") * 1024; +} + +// On linux, we return RSS. +size_t ProcessMetrics::GetWorkingSetSize() const { + return ReadProcStatsAndGetFieldAsSizeT(process_, VM_RSS) * getpagesize(); +} + +// On linux, we return the high water mark of RSS. +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + return ReadProcStatusAndGetFieldAsSizeT(process_, "VmHWM") * 1024; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + WorkingSetKBytes ws_usage; + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + if (private_bytes) + *private_bytes = ws_usage.priv * 1024; + + if (shared_bytes) + *shared_bytes = ws_usage.shared * 1024; + + return true; +} + +// Private and Shared working set sizes are obtained from /proc/<pid>/statm. +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { + // Use statm instead of smaps because smaps is: + // a) Large and slow to parse. + // b) Unavailable in the SUID sandbox. + + // First we need to get the page size, since everything is measured in pages. + // For details, see: man 5 proc. + const int page_size_kb = getpagesize() / 1024; + if (page_size_kb <= 0) + return false; + + std::string statm; + { + FilePath statm_file = GetProcPidDir(process_).Append("statm"); + // Synchronously reading files in /proc is safe. + base::ThreadRestrictions::ScopedAllowIO allow_io; + bool ret = file_util::ReadFileToString(statm_file, &statm); + if (!ret || statm.length() == 0) + return false; + } + + std::vector<std::string> statm_vec; + base::SplitString(statm, ' ', &statm_vec); + if (statm_vec.size() != 7) + return false; // Not the format we expect. + + int statm_rss, statm_shared; + base::StringToInt(statm_vec[1], &statm_rss); + base::StringToInt(statm_vec[2], &statm_shared); + + ws_usage->priv = (statm_rss - statm_shared) * page_size_kb; + ws_usage->shared = statm_shared * page_size_kb; + + // Sharable is not calculated, as it does not provide interesting data. + ws_usage->shareable = 0; + + return true; +} + +double ProcessMetrics::GetCPUUsage() { + // This queries the /proc-specific scaling factor which is + // conceptually the system hertz. To dump this value on another + // system, try + // od -t dL /proc/self/auxv + // and look for the number after 17 in the output; mine is + // 0000040 17 100 3 134512692 + // which means the answer is 100. + // It may be the case that this value is always 100. + static const int kHertz = sysconf(_SC_CLK_TCK); + + struct timeval now; + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + int64 time = TimeValToMicroseconds(now); + + if (last_time_ == 0) { + // First call, just set the last values. + last_time_ = time; + last_cpu_ = GetProcessCPU(process_); + return 0; + } + + int64 time_delta = time - last_time_; + DCHECK_NE(time_delta, 0); + if (time_delta == 0) + return 0; + + int cpu = GetProcessCPU(process_); + + // We have the number of jiffies in the time period. Convert to percentage. + // Note this means we will go *over* 100 in the case where multiple threads + // are together adding to more than one CPU's worth. + int percentage = 100 * (cpu - last_cpu_) / + (kHertz * TimeDelta::FromMicroseconds(time_delta).InSecondsF()); + + last_time_ = time; + last_cpu_ = cpu; + + return percentage; +} + +// To have /proc/self/io file you must enable CONFIG_TASK_IO_ACCOUNTING +// in your kernel configuration. +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + // Synchronously reading files in /proc is safe. + base::ThreadRestrictions::ScopedAllowIO allow_io; + + std::string proc_io_contents; + FilePath io_file = GetProcPidDir(process_).Append("io"); + if (!file_util::ReadFileToString(io_file, &proc_io_contents)) + return false; + + (*io_counters).OtherOperationCount = 0; + (*io_counters).OtherTransferCount = 0; + + StringTokenizer tokenizer(proc_io_contents, ": \n"); + ParsingState state = KEY_NAME; + StringPiece last_key_name; + while (tokenizer.GetNext()) { + switch (state) { + case KEY_NAME: + last_key_name = tokenizer.token_piece(); + state = KEY_VALUE; + break; + case KEY_VALUE: + DCHECK(!last_key_name.empty()); + if (last_key_name == "syscr") { + base::StringToInt64(tokenizer.token_piece(), + reinterpret_cast<int64*>(&(*io_counters).ReadOperationCount)); + } else if (last_key_name == "syscw") { + base::StringToInt64(tokenizer.token_piece(), + reinterpret_cast<int64*>(&(*io_counters).WriteOperationCount)); + } else if (last_key_name == "rchar") { + base::StringToInt64(tokenizer.token_piece(), + reinterpret_cast<int64*>(&(*io_counters).ReadTransferCount)); + } else if (last_key_name == "wchar") { + base::StringToInt64(tokenizer.token_piece(), + reinterpret_cast<int64*>(&(*io_counters).WriteTransferCount)); + } + state = KEY_NAME; + break; + } + } + return true; +} + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + last_time_(0), + last_system_time_(0), + last_cpu_(0) { + processor_count_ = base::SysInfo::NumberOfProcessors(); +} + + +// Exposed for testing. +int ParseProcStatCPU(const std::string& input) { + std::vector<std::string> proc_stats; + if (!ParseProcStats(input, &proc_stats)) + return -1; + + if (proc_stats.size() <= VM_STIME) + return -1; + int utime = GetProcStatsFieldAsInt(proc_stats, VM_UTIME); + int stime = GetProcStatsFieldAsInt(proc_stats, VM_STIME); + return utime + stime; +} + +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; + +} // namespace + +SystemMemoryInfoKB::SystemMemoryInfoKB() + : total(0), + free(0), + buffers(0), + cached(0), + active_anon(0), + inactive_anon(0), + active_file(0), + inactive_file(0), + shmem(0), + gem_objects(-1), + gem_size(-1) { +} + +bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) { + // Synchronously reading files in /proc is safe. + base::ThreadRestrictions::ScopedAllowIO allow_io; + + // Used memory is: total - free - buffers - caches + FilePath meminfo_file("/proc/meminfo"); + std::string meminfo_data; + if (!file_util::ReadFileToString(meminfo_file, &meminfo_data)) { + 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."; + 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):"); + + base::StringToInt(meminfo_fields[kMemTotalIndex], &meminfo->total); + base::StringToInt(meminfo_fields[kMemFreeIndex], &meminfo->free); + base::StringToInt(meminfo_fields[kMemBuffersIndex], &meminfo->buffers); + base::StringToInt(meminfo_fields[kMemCachedIndex], &meminfo->cached); + base::StringToInt(meminfo_fields[kMemActiveAnonIndex], &meminfo->active_anon); + base::StringToInt(meminfo_fields[kMemInactiveAnonIndex], + &meminfo->inactive_anon); + base::StringToInt(meminfo_fields[kMemActiveFileIndex], &meminfo->active_file); + base::StringToInt(meminfo_fields[kMemInactiveFileIndex], + &meminfo->inactive_file); +#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:") { + base::StringToInt(meminfo_fields[i+1], &meminfo->shmem); + break; + } + } +#endif + + // Check for graphics memory data and report if present. Synchronously + // reading files in /sys is fast. +#if defined(ARCH_CPU_ARM_FAMILY) + FilePath geminfo_file("/sys/kernel/debug/dri/0/exynos_gem_objects"); +#else + FilePath geminfo_file("/sys/kernel/debug/dri/0/i915_gem_objects"); +#endif + std::string geminfo_data; + meminfo->gem_objects = -1; + meminfo->gem_size = -1; + if (file_util::ReadFileToString(geminfo_file, &geminfo_data)) { + int gem_objects = -1; + long long gem_size = -1; + int num_res = sscanf(geminfo_data.c_str(), + "%d objects, %lld bytes", + &gem_objects, &gem_size); + if (num_res == 2) { + meminfo->gem_objects = gem_objects; + meminfo->gem_size = gem_size; + } + } + +#if defined(ARCH_CPU_ARM_FAMILY) + // Incorporate Mali graphics memory if present. + FilePath mali_memory_file("/sys/devices/platform/mali.0/memory"); + std::string mali_memory_data; + if (file_util::ReadFileToString(mali_memory_file, &mali_memory_data)) { + long long mali_size = -1; + int num_res = sscanf(mali_memory_data.c_str(), "%lld bytes", &mali_size); + if (num_res == 1) + meminfo->gem_size += mali_size; + } +#endif // defined(ARCH_CPU_ARM_FAMILY) + + return true; +} + +size_t GetSystemCommitCharge() { + SystemMemoryInfoKB meminfo; + if (!GetSystemMemoryInfo(&meminfo)) + return 0; + return meminfo.total - meminfo.free - meminfo.buffers - meminfo.cached; +} + +namespace { + +void OnNoMemorySize(size_t size) { +#if defined(USE_LINUX_BREAKPAD) + g_oom_size = size; +#endif + + if (size != 0) + LOG(FATAL) << "Out of memory, size = " << size; + LOG(FATAL) << "Out of memory."; +} + +void OnNoMemory() { + OnNoMemorySize(0); +} + +} // namespace + +extern "C" { +#if !defined(USE_TCMALLOC) && !defined(ADDRESS_SANITIZER) && \ + !defined(OS_ANDROID) && !defined(THREAD_SANITIZER) + +extern "C" { +void* __libc_malloc(size_t size); +void* __libc_realloc(void* ptr, size_t size); +void* __libc_calloc(size_t nmemb, size_t size); +void* __libc_valloc(size_t size); +void* __libc_pvalloc(size_t size); +void* __libc_memalign(size_t alignment, size_t size); +} // extern "C" + +// Overriding the system memory allocation functions: +// +// For security reasons, we want malloc failures to be fatal. Too much code +// doesn't check for a NULL return value from malloc and unconditionally uses +// the resulting pointer. If the first offset that they try to access is +// attacker controlled, then the attacker can direct the code to access any +// part of memory. +// +// Thus, we define all the standard malloc functions here and mark them as +// visibility 'default'. This means that they replace the malloc functions for +// all Chromium code and also for all code in shared libraries. There are tests +// for this in process_util_unittest.cc. +// +// If we are using tcmalloc, then the problem is moot since tcmalloc handles +// this for us. Thus this code is in a !defined(USE_TCMALLOC) block. +// +// If we are testing the binary with AddressSanitizer, we should not +// redefine malloc and let AddressSanitizer do it instead. +// +// We call the real libc functions in this code by using __libc_malloc etc. +// Previously we tried using dlsym(RTLD_NEXT, ...) but that failed depending on +// the link order. Since ld.so needs calloc during symbol resolution, it +// defines its own versions of several of these functions in dl-minimal.c. +// Depending on the runtime library order, dlsym ended up giving us those +// functions and bad things happened. See crbug.com/31809 +// +// This means that any code which calls __libc_* gets the raw libc versions of +// these functions. + +#define DIE_ON_OOM_1(function_name) \ + void* function_name(size_t) __attribute__ ((visibility("default"))); \ + \ + void* function_name(size_t size) { \ + void* ret = __libc_##function_name(size); \ + if (ret == NULL && size != 0) \ + OnNoMemorySize(size); \ + return ret; \ + } + +#define DIE_ON_OOM_2(function_name, arg1_type) \ + void* function_name(arg1_type, size_t) \ + __attribute__ ((visibility("default"))); \ + \ + void* function_name(arg1_type arg1, size_t size) { \ + void* ret = __libc_##function_name(arg1, size); \ + if (ret == NULL && size != 0) \ + OnNoMemorySize(size); \ + return ret; \ + } + +DIE_ON_OOM_1(malloc) +DIE_ON_OOM_1(valloc) +DIE_ON_OOM_1(pvalloc) + +DIE_ON_OOM_2(calloc, size_t) +DIE_ON_OOM_2(realloc, void*) +DIE_ON_OOM_2(memalign, size_t) + +// posix_memalign has a unique signature and doesn't have a __libc_ variant. +int posix_memalign(void** ptr, size_t alignment, size_t size) + __attribute__ ((visibility("default"))); + +int posix_memalign(void** ptr, size_t alignment, size_t size) { + // This will use the safe version of memalign, above. + *ptr = memalign(alignment, size); + return 0; +} + +#endif // !defined(USE_TCMALLOC) +} // extern C + +void EnableTerminationOnHeapCorruption() { + // On Linux, there nothing to do AFAIK. +} + +void EnableTerminationOnOutOfMemory() { +#if defined(OS_ANDROID) + // Android doesn't support setting a new handler. + DLOG(WARNING) << "Not feasible."; +#else + // Set the new-out of memory handler. + std::set_new_handler(&OnNoMemory); + // If we're using glibc's allocator, the above functions will override + // malloc and friends and make them die on out of memory. +#endif +} + +// NOTE: This is not the only version of this function in the source: +// the setuid sandbox (in process_util_linux.c, in the sandbox source) +// also has its own C version. +bool AdjustOOMScore(ProcessId process, int score) { + if (score < 0 || score > kMaxOomScore) + return false; + + FilePath oom_path(GetProcPidDir(process)); + + // Attempt to write the newer oom_score_adj file first. + FilePath oom_file = oom_path.AppendASCII("oom_score_adj"); + if (file_util::PathExists(oom_file)) { + std::string score_str = base::IntToString(score); + DVLOG(1) << "Adjusting oom_score_adj of " << process << " to " + << score_str; + int score_len = static_cast<int>(score_str.length()); + return (score_len == file_util::WriteFile(oom_file, + score_str.c_str(), + score_len)); + } + + // If the oom_score_adj file doesn't exist, then we write the old + // style file and translate the oom_adj score to the range 0-15. + oom_file = oom_path.AppendASCII("oom_adj"); + if (file_util::PathExists(oom_file)) { + // Max score for the old oom_adj range. Used for conversion of new + // values to old values. + const int kMaxOldOomScore = 15; + + int converted_score = score * kMaxOldOomScore / kMaxOomScore; + std::string score_str = base::IntToString(converted_score); + DVLOG(1) << "Adjusting oom_adj of " << process << " to " << score_str; + int score_len = static_cast<int>(score_str.length()); + return (score_len == file_util::WriteFile(oom_file, + score_str.c_str(), + score_len)); + } + + return false; +} + +} // namespace base
diff --git a/src/base/process_util_mac.mm b/src/base/process_util_mac.mm new file mode 100644 index 0000000..cf1e7a8 --- /dev/null +++ b/src/base/process_util_mac.mm
@@ -0,0 +1,1265 @@ +// 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/process_util.h" + +#import <Cocoa/Cocoa.h> +#include <crt_externs.h> +#include <dlfcn.h> +#include <errno.h> +#include <mach/mach.h> +#include <mach/mach_init.h> +#include <mach/mach_vm.h> +#include <mach/shared_region.h> +#include <mach/task.h> +#include <mach-o/nlist.h> +#include <malloc/malloc.h> +#import <objc/runtime.h> +#include <signal.h> +#include <spawn.h> +#include <sys/event.h> +#include <sys/mman.h> +#include <sys/sysctl.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <new> +#include <string> + +#include "base/debug/debugger.h" +#include "base/file_util.h" +#include "base/hash_tables.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_mach_port.h" +#include "base/posix/eintr_wrapper.h" +#include "base/string_util.h" +#include "base/sys_info.h" +#include "base/threading/thread_local.h" +#include "third_party/apple_apsl/CFBase.h" +#include "third_party/apple_apsl/malloc.h" +#include "third_party/mach_override/mach_override.h" + +namespace base { + +void RestoreDefaultExceptionHandler() { + // This function is tailored to remove the Breakpad exception handler. + // exception_mask matches s_exception_mask in + // breakpad/src/client/mac/handler/exception_handler.cc + const exception_mask_t exception_mask = EXC_MASK_BAD_ACCESS | + EXC_MASK_BAD_INSTRUCTION | + EXC_MASK_ARITHMETIC | + EXC_MASK_BREAKPOINT; + + // Setting the exception port to MACH_PORT_NULL may not be entirely + // kosher to restore the default exception handler, but in practice, + // it results in the exception port being set to Apple Crash Reporter, + // the desired behavior. + task_set_exception_ports(mach_task_self(), exception_mask, MACH_PORT_NULL, + EXCEPTION_DEFAULT, THREAD_STATE_NONE); +} + +ProcessIterator::ProcessIterator(const ProcessFilter* filter) + : index_of_kinfo_proc_(0), + filter_(filter) { + // Get a snapshot of all of my processes (yes, as we loop it can go stale, but + // but trying to find where we were in a constantly changing list is basically + // impossible. + + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, geteuid() }; + + // Since more processes could start between when we get the size and when + // we get the list, we do a loop to keep trying until we get it. + bool done = false; + int try_num = 1; + const int max_tries = 10; + do { + // Get the size of the buffer + size_t len = 0; + if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) < 0) { + DLOG(ERROR) << "failed to get the size needed for the process list"; + kinfo_procs_.resize(0); + done = true; + } else { + size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc); + // Leave some spare room for process table growth (more could show up + // between when we check and now) + num_of_kinfo_proc += 16; + kinfo_procs_.resize(num_of_kinfo_proc); + len = num_of_kinfo_proc * sizeof(struct kinfo_proc); + // Load the list of processes + if (sysctl(mib, arraysize(mib), &kinfo_procs_[0], &len, NULL, 0) < 0) { + // If we get a mem error, it just means we need a bigger buffer, so + // loop around again. Anything else is a real error and give up. + if (errno != ENOMEM) { + DLOG(ERROR) << "failed to get the process list"; + kinfo_procs_.resize(0); + done = true; + } + } else { + // Got the list, just make sure we're sized exactly right + size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc); + kinfo_procs_.resize(num_of_kinfo_proc); + done = true; + } + } + } while (!done && (try_num++ < max_tries)); + + if (!done) { + DLOG(ERROR) << "failed to collect the process list in a few tries"; + kinfo_procs_.resize(0); + } +} + +ProcessIterator::~ProcessIterator() { +} + +bool ProcessIterator::CheckForNextProcess() { + std::string data; + for (; index_of_kinfo_proc_ < kinfo_procs_.size(); ++index_of_kinfo_proc_) { + kinfo_proc& kinfo = kinfo_procs_[index_of_kinfo_proc_]; + + // Skip processes just awaiting collection + if ((kinfo.kp_proc.p_pid > 0) && (kinfo.kp_proc.p_stat == SZOMB)) + continue; + + int mib[] = { CTL_KERN, KERN_PROCARGS, kinfo.kp_proc.p_pid }; + + // Find out what size buffer we need. + size_t data_len = 0; + if (sysctl(mib, arraysize(mib), NULL, &data_len, NULL, 0) < 0) { + DVPLOG(1) << "failed to figure out the buffer size for a commandline"; + continue; + } + + data.resize(data_len); + if (sysctl(mib, arraysize(mib), &data[0], &data_len, NULL, 0) < 0) { + DVPLOG(1) << "failed to fetch a commandline"; + continue; + } + + // |data| contains all the command line parameters of the process, separated + // by blocks of one or more null characters. We tokenize |data| into a + // vector of strings using '\0' as a delimiter and populate + // |entry_.cmd_line_args_|. + std::string delimiters; + delimiters.push_back('\0'); + Tokenize(data, delimiters, &entry_.cmd_line_args_); + + // |data| starts with the full executable path followed by a null character. + // We search for the first instance of '\0' and extract everything before it + // to populate |entry_.exe_file_|. + size_t exec_name_end = data.find('\0'); + if (exec_name_end == std::string::npos) { + DLOG(ERROR) << "command line data didn't match expected format"; + continue; + } + + entry_.pid_ = kinfo.kp_proc.p_pid; + entry_.ppid_ = kinfo.kp_eproc.e_ppid; + entry_.gid_ = kinfo.kp_eproc.e_pgid; + size_t last_slash = data.rfind('/', exec_name_end); + if (last_slash == std::string::npos) + entry_.exe_file_.assign(data, 0, exec_name_end); + else + entry_.exe_file_.assign(data, last_slash + 1, + exec_name_end - last_slash - 1); + // Start w/ the next entry next time through + ++index_of_kinfo_proc_; + // Done + return true; + } + return false; +} + +bool NamedProcessIterator::IncludeEntry() { + return (executable_name_ == entry().exe_file() && + ProcessIterator::IncludeEntry()); +} + + +// ------------------------------------------------------------------------ +// NOTE: about ProcessMetrics +// +// Getting a mach task from a pid for another process requires permissions in +// general, so there doesn't really seem to be a way to do these (and spinning +// up ps to fetch each stats seems dangerous to put in a base api for anyone to +// call). Child processes ipc their port, so return something if available, +// otherwise return 0. +// + +ProcessMetrics::ProcessMetrics(ProcessHandle process, + ProcessMetrics::PortProvider* port_provider) + : process_(process), + last_time_(0), + last_system_time_(0), + port_provider_(port_provider) { + processor_count_ = SysInfo::NumberOfProcessors(); +} + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics( + ProcessHandle process, + ProcessMetrics::PortProvider* port_provider) { + return new ProcessMetrics(process, port_provider); +} + +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + return false; +} + +static bool GetTaskInfo(mach_port_t task, task_basic_info_64* task_info_data) { + if (task == MACH_PORT_NULL) + return false; + mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT; + kern_return_t kr = task_info(task, + TASK_BASIC_INFO_64, + reinterpret_cast<task_info_t>(task_info_data), + &count); + // Most likely cause for failure: |task| is a zombie. + return kr == KERN_SUCCESS; +} + +size_t ProcessMetrics::GetPagefileUsage() const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(TaskForPid(process_), &task_info_data)) + return 0; + return task_info_data.virtual_size; +} + +size_t ProcessMetrics::GetPeakPagefileUsage() const { + return 0; +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(TaskForPid(process_), &task_info_data)) + return 0; + return task_info_data.resident_size; +} + +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + return 0; +} + +static bool GetCPUTypeForProcess(pid_t pid, cpu_type_t* cpu_type) { + size_t len = sizeof(*cpu_type); + int result = sysctlbyname("sysctl.proc_cputype", + cpu_type, + &len, + NULL, + 0); + if (result != 0) { + DPLOG(ERROR) << "sysctlbyname(""sysctl.proc_cputype"")"; + return false; + } + + return true; +} + +static bool IsAddressInSharedRegion(mach_vm_address_t addr, cpu_type_t type) { + if (type == CPU_TYPE_I386) + return addr >= SHARED_REGION_BASE_I386 && + addr < (SHARED_REGION_BASE_I386 + SHARED_REGION_SIZE_I386); + else if (type == CPU_TYPE_X86_64) + return addr >= SHARED_REGION_BASE_X86_64 && + addr < (SHARED_REGION_BASE_X86_64 + SHARED_REGION_SIZE_X86_64); + else + return false; +} + +// This is a rough approximation of the algorithm that libtop uses. +// private_bytes is the size of private resident memory. +// shared_bytes is the size of shared resident memory. +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + kern_return_t kr; + size_t private_pages_count = 0; + size_t shared_pages_count = 0; + + if (!private_bytes && !shared_bytes) + return true; + + mach_port_t task = TaskForPid(process_); + if (task == MACH_PORT_NULL) { + DLOG(ERROR) << "Invalid process"; + return false; + } + + cpu_type_t cpu_type; + if (!GetCPUTypeForProcess(process_, &cpu_type)) + return false; + + // The same region can be referenced multiple times. To avoid double counting + // we need to keep track of which regions we've already counted. + base::hash_set<int> seen_objects; + + // We iterate through each VM region in the task's address map. For shared + // memory we add up all the pages that are marked as shared. Like libtop we + // try to avoid counting pages that are also referenced by other tasks. Since + // we don't have access to the VM regions of other tasks the only hint we have + // is if the address is in the shared region area. + // + // Private memory is much simpler. We simply count the pages that are marked + // as private or copy on write (COW). + // + // See libtop_update_vm_regions in + // http://www.opensource.apple.com/source/top/top-67/libtop.c + mach_vm_size_t size = 0; + for (mach_vm_address_t address = MACH_VM_MIN_ADDRESS;; address += size) { + vm_region_top_info_data_t info; + mach_msg_type_number_t info_count = VM_REGION_TOP_INFO_COUNT; + mach_port_t object_name; + kr = mach_vm_region(task, + &address, + &size, + VM_REGION_TOP_INFO, + (vm_region_info_t)&info, + &info_count, + &object_name); + if (kr == KERN_INVALID_ADDRESS) { + // We're at the end of the address space. + break; + } else if (kr != KERN_SUCCESS) { + DLOG(ERROR) << "Calling mach_vm_region failed with error: " + << mach_error_string(kr); + return false; + } + + if (IsAddressInSharedRegion(address, cpu_type) && + info.share_mode != SM_PRIVATE) + continue; + + if (info.share_mode == SM_COW && info.ref_count == 1) + info.share_mode = SM_PRIVATE; + + switch (info.share_mode) { + case SM_PRIVATE: + private_pages_count += info.private_pages_resident; + private_pages_count += info.shared_pages_resident; + break; + case SM_COW: + private_pages_count += info.private_pages_resident; + // Fall through + case SM_SHARED: + if (seen_objects.count(info.obj_id) == 0) { + // Only count the first reference to this region. + seen_objects.insert(info.obj_id); + shared_pages_count += info.shared_pages_resident; + } + break; + default: + break; + } + } + + vm_size_t page_size; + kr = host_page_size(task, &page_size); + if (kr != KERN_SUCCESS) { + DLOG(ERROR) << "Failed to fetch host page size, error: " + << mach_error_string(kr); + return false; + } + + if (private_bytes) + *private_bytes = private_pages_count * page_size; + if (shared_bytes) + *shared_bytes = shared_pages_count * page_size; + + return true; +} + +void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) const { +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { + size_t priv = GetWorkingSetSize(); + if (!priv) + return false; + ws_usage->priv = priv / 1024; + ws_usage->shareable = 0; + ws_usage->shared = 0; + return true; +} + +#define TIME_VALUE_TO_TIMEVAL(a, r) do { \ + (r)->tv_sec = (a)->seconds; \ + (r)->tv_usec = (a)->microseconds; \ +} while (0) + +double ProcessMetrics::GetCPUUsage() { + mach_port_t task = TaskForPid(process_); + if (task == MACH_PORT_NULL) + return 0; + + kern_return_t kr; + + // Libtop explicitly loops over the threads (libtop_pinfo_update_cpu_usage() + // in libtop.c), but this is more concise and gives the same results: + task_thread_times_info thread_info_data; + mach_msg_type_number_t thread_info_count = TASK_THREAD_TIMES_INFO_COUNT; + kr = task_info(task, + TASK_THREAD_TIMES_INFO, + reinterpret_cast<task_info_t>(&thread_info_data), + &thread_info_count); + if (kr != KERN_SUCCESS) { + // Most likely cause: |task| is a zombie. + return 0; + } + + task_basic_info_64 task_info_data; + if (!GetTaskInfo(task, &task_info_data)) + return 0; + + /* Set total_time. */ + // thread info contains live time... + struct timeval user_timeval, system_timeval, task_timeval; + TIME_VALUE_TO_TIMEVAL(&thread_info_data.user_time, &user_timeval); + TIME_VALUE_TO_TIMEVAL(&thread_info_data.system_time, &system_timeval); + timeradd(&user_timeval, &system_timeval, &task_timeval); + + // ... task info contains terminated time. + TIME_VALUE_TO_TIMEVAL(&task_info_data.user_time, &user_timeval); + TIME_VALUE_TO_TIMEVAL(&task_info_data.system_time, &system_timeval); + timeradd(&user_timeval, &task_timeval, &task_timeval); + timeradd(&system_timeval, &task_timeval, &task_timeval); + + struct timeval now; + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + + int64 time = TimeValToMicroseconds(now); + int64 task_time = TimeValToMicroseconds(task_timeval); + + if ((last_system_time_ == 0) || (last_time_ == 0)) { + // First call, just set the last values. + last_system_time_ = task_time; + last_time_ = time; + return 0; + } + + int64 system_time_delta = task_time - last_system_time_; + int64 time_delta = time - last_time_; + DCHECK_NE(0U, time_delta); + if (time_delta == 0) + return 0; + + // We add time_delta / 2 so the result is rounded. + double cpu = static_cast<double>((system_time_delta * 100.0) / time_delta); + + last_system_time_ = task_time; + last_time_ = time; + + return cpu; +} + +mach_port_t ProcessMetrics::TaskForPid(ProcessHandle process) const { + mach_port_t task = MACH_PORT_NULL; + if (port_provider_) + task = port_provider_->TaskForPid(process_); + if (task == MACH_PORT_NULL && process_ == getpid()) + task = mach_task_self(); + return task; +} + +// ------------------------------------------------------------------------ + +// Bytes committed by the system. +size_t GetSystemCommitCharge() { + base::mac::ScopedMachPort host(mach_host_self()); + mach_msg_type_number_t count = HOST_VM_INFO_COUNT; + vm_statistics_data_t data; + kern_return_t kr = host_statistics(host, HOST_VM_INFO, + reinterpret_cast<host_info_t>(&data), + &count); + if (kr) { + DLOG(WARNING) << "Failed to fetch host statistics."; + return 0; + } + + vm_size_t page_size; + kr = host_page_size(host, &page_size); + if (kr) { + DLOG(ERROR) << "Failed to fetch host page size."; + return 0; + } + + return (data.active_count * page_size) / 1024; +} + +namespace { + +// Finds the library path for malloc() and thus the libC part of libSystem, +// which in Lion is in a separate image. +const char* LookUpLibCPath() { + const void* addr = reinterpret_cast<void*>(&malloc); + + Dl_info info; + if (dladdr(addr, &info)) + return info.dli_fname; + + DLOG(WARNING) << "Could not find image path for malloc()"; + return NULL; +} + +typedef void(*malloc_error_break_t)(void); +malloc_error_break_t g_original_malloc_error_break = NULL; + +// Returns the function pointer for malloc_error_break. This symbol is declared +// as __private_extern__ and cannot be dlsym()ed. Instead, use nlist() to +// get it. +malloc_error_break_t LookUpMallocErrorBreak() { +#if ARCH_CPU_32_BITS + const char* lib_c_path = LookUpLibCPath(); + if (!lib_c_path) + return NULL; + + // Only need to look up two symbols, but nlist() requires a NULL-terminated + // array and takes no count. + struct nlist nl[3]; + bzero(&nl, sizeof(nl)); + + // The symbol to find. + nl[0].n_un.n_name = const_cast<char*>("_malloc_error_break"); + + // A reference symbol by which the address of the desired symbol will be + // calculated. + nl[1].n_un.n_name = const_cast<char*>("_malloc"); + + int rv = nlist(lib_c_path, nl); + if (rv != 0 || nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF) { + return NULL; + } + + // nlist() returns addresses as offsets in the image, not the instruction + // pointer in memory. Use the known in-memory address of malloc() + // to compute the offset for malloc_error_break(). + uintptr_t reference_addr = reinterpret_cast<uintptr_t>(&malloc); + reference_addr -= nl[1].n_value; + reference_addr += nl[0].n_value; + + return reinterpret_cast<malloc_error_break_t>(reference_addr); +#endif // ARCH_CPU_32_BITS + + return NULL; +} + +// Simple scoper that saves the current value of errno, resets it to 0, and on +// destruction puts the old value back. This is so that CrMallocErrorBreak can +// safely test errno free from the effects of other routines. +class ScopedClearErrno { + public: + ScopedClearErrno() : old_errno_(errno) { + errno = 0; + } + ~ScopedClearErrno() { + if (errno == 0) + errno = old_errno_; + } + + private: + int old_errno_; + + DISALLOW_COPY_AND_ASSIGN(ScopedClearErrno); +}; + +// Combines ThreadLocalBoolean with AutoReset. It would be convenient +// to compose ThreadLocalPointer<bool> with base::AutoReset<bool>, but that +// would require allocating some storage for the bool. +class ThreadLocalBooleanAutoReset { + public: + ThreadLocalBooleanAutoReset(ThreadLocalBoolean* tlb, bool new_value) + : scoped_tlb_(tlb), + original_value_(tlb->Get()) { + scoped_tlb_->Set(new_value); + } + ~ThreadLocalBooleanAutoReset() { + scoped_tlb_->Set(original_value_); + } + + private: + ThreadLocalBoolean* scoped_tlb_; + bool original_value_; + + DISALLOW_COPY_AND_ASSIGN(ThreadLocalBooleanAutoReset); +}; + +base::LazyInstance<ThreadLocalBoolean>::Leaky + g_unchecked_malloc = 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. +void CrMallocErrorBreak() { + g_original_malloc_error_break(); + + // 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())) + return; + + // A unit test checks this error message, so it needs to be in release builds. + char buf[1024] = + "Terminating process due to a potential for future heap corruption: " + "errno="; + char errnobuf[] = { + '0' + ((errno / 100) % 10), + '0' + ((errno / 10) % 10), + '0' + (errno % 10), + '\000' + }; + COMPILE_ASSERT(ELAST <= 999, errno_too_large_to_encode); + strlcat(buf, errnobuf, sizeof(buf)); + RAW_LOG(ERROR, buf); + + // Crash by writing to NULL+errno to allow analyzing errno from + // crash dump info (setting a breakpad key would re-enter the malloc + // library). Max documented errno in intro(2) is actually 102, but + // it really just needs to be "small" to stay on the right vm page. + const int kMaxErrno = 256; + char* volatile death_ptr = NULL; + death_ptr += std::min(errno, kMaxErrno); + *death_ptr = '!'; +} + +} // namespace + +void EnableTerminationOnHeapCorruption() { +#ifdef ADDRESS_SANITIZER + // Don't do anything special on heap corruption, because it should be handled + // by AddressSanitizer. + return; +#endif + + // Only override once, otherwise CrMallocErrorBreak() will recurse + // to itself. + if (g_original_malloc_error_break) + return; + + malloc_error_break_t malloc_error_break = LookUpMallocErrorBreak(); + if (!malloc_error_break) { + DLOG(WARNING) << "Could not find malloc_error_break"; + return; + } + + mach_error_t err = mach_override_ptr( + (void*)malloc_error_break, + (void*)&CrMallocErrorBreak, + (void**)&g_original_malloc_error_break); + + if (err != err_none) + DLOG(WARNING) << "Could not override malloc_error_break; error = " << err; +} + +// ------------------------------------------------------------------------ + +namespace { + +bool g_oom_killer_enabled; + +// === C malloc/calloc/valloc/realloc/posix_memalign === + +typedef void* (*malloc_type)(struct _malloc_zone_t* zone, + size_t size); +typedef void* (*calloc_type)(struct _malloc_zone_t* zone, + size_t num_items, + size_t size); +typedef void* (*valloc_type)(struct _malloc_zone_t* zone, + size_t size); +typedef void (*free_type)(struct _malloc_zone_t* zone, + void* ptr); +typedef void* (*realloc_type)(struct _malloc_zone_t* zone, + void* ptr, + size_t size); +typedef void* (*memalign_type)(struct _malloc_zone_t* zone, + size_t alignment, + size_t size); + +malloc_type g_old_malloc; +calloc_type g_old_calloc; +valloc_type g_old_valloc; +free_type g_old_free; +realloc_type g_old_realloc; +memalign_type g_old_memalign; + +malloc_type g_old_malloc_purgeable; +calloc_type g_old_calloc_purgeable; +valloc_type g_old_valloc_purgeable; +free_type g_old_free_purgeable; +realloc_type g_old_realloc_purgeable; +memalign_type g_old_memalign_purgeable; + +void* oom_killer_malloc(struct _malloc_zone_t* zone, + size_t size) { + ScopedClearErrno clear_errno; + void* result = g_old_malloc(zone, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_calloc(struct _malloc_zone_t* zone, + size_t num_items, + size_t size) { + ScopedClearErrno clear_errno; + void* result = g_old_calloc(zone, num_items, size); + if (!result && num_items && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_valloc(struct _malloc_zone_t* zone, + size_t size) { + ScopedClearErrno clear_errno; + void* result = g_old_valloc(zone, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void oom_killer_free(struct _malloc_zone_t* zone, + void* ptr) { + ScopedClearErrno clear_errno; + g_old_free(zone, ptr); +} + +void* oom_killer_realloc(struct _malloc_zone_t* zone, + void* ptr, + size_t size) { + ScopedClearErrno clear_errno; + void* result = g_old_realloc(zone, ptr, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_memalign(struct _malloc_zone_t* zone, + size_t alignment, + size_t size) { + ScopedClearErrno clear_errno; + void* result = g_old_memalign(zone, alignment, size); + // Only die if posix_memalign would have returned ENOMEM, since there are + // other reasons why NULL might be returned (see + // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c ). + if (!result && size && alignment >= sizeof(void*) + && (alignment & (alignment - 1)) == 0) { + debug::BreakDebugger(); + } + return result; +} + +void* oom_killer_malloc_purgeable(struct _malloc_zone_t* zone, + size_t size) { + ScopedClearErrno clear_errno; + void* result = g_old_malloc_purgeable(zone, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_calloc_purgeable(struct _malloc_zone_t* zone, + size_t num_items, + size_t size) { + ScopedClearErrno clear_errno; + void* result = g_old_calloc_purgeable(zone, num_items, size); + if (!result && num_items && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_valloc_purgeable(struct _malloc_zone_t* zone, + size_t size) { + ScopedClearErrno clear_errno; + void* result = g_old_valloc_purgeable(zone, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void oom_killer_free_purgeable(struct _malloc_zone_t* zone, + void* ptr) { + ScopedClearErrno clear_errno; + g_old_free_purgeable(zone, ptr); +} + +void* oom_killer_realloc_purgeable(struct _malloc_zone_t* zone, + void* ptr, + size_t size) { + ScopedClearErrno clear_errno; + void* result = g_old_realloc_purgeable(zone, ptr, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_memalign_purgeable(struct _malloc_zone_t* zone, + size_t alignment, + size_t size) { + ScopedClearErrno clear_errno; + void* result = g_old_memalign_purgeable(zone, alignment, size); + // Only die if posix_memalign would have returned ENOMEM, since there are + // other reasons why NULL might be returned (see + // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c ). + if (!result && size && alignment >= sizeof(void*) + && (alignment & (alignment - 1)) == 0) { + debug::BreakDebugger(); + } + return result; +} + +// === C++ operator new === + +void oom_killer_new() { + debug::BreakDebugger(); +} + +// === Core Foundation CFAllocators === + +bool CanGetContextForCFAllocator() { + return !base::mac::IsOSLaterThanMountainLion_DontCallThis(); +} + +CFAllocatorContext* ContextForCFAllocator(CFAllocatorRef allocator) { + if (base::mac::IsOSSnowLeopard()) { + ChromeCFAllocatorLeopards* our_allocator = + const_cast<ChromeCFAllocatorLeopards*>( + reinterpret_cast<const ChromeCFAllocatorLeopards*>(allocator)); + return &our_allocator->_context; + } else if (base::mac::IsOSLion() || base::mac::IsOSMountainLion()) { + ChromeCFAllocatorLions* our_allocator = + const_cast<ChromeCFAllocatorLions*>( + reinterpret_cast<const ChromeCFAllocatorLions*>(allocator)); + return &our_allocator->_context; + } else { + return NULL; + } +} + +CFAllocatorAllocateCallBack g_old_cfallocator_system_default; +CFAllocatorAllocateCallBack g_old_cfallocator_malloc; +CFAllocatorAllocateCallBack g_old_cfallocator_malloc_zone; + +void* oom_killer_cfallocator_system_default(CFIndex alloc_size, + CFOptionFlags hint, + void* info) { + void* result = g_old_cfallocator_system_default(alloc_size, hint, info); + if (!result) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_cfallocator_malloc(CFIndex alloc_size, + CFOptionFlags hint, + void* info) { + void* result = g_old_cfallocator_malloc(alloc_size, hint, info); + if (!result) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_cfallocator_malloc_zone(CFIndex alloc_size, + CFOptionFlags hint, + void* info) { + void* result = g_old_cfallocator_malloc_zone(alloc_size, hint, info); + if (!result) + debug::BreakDebugger(); + return result; +} + +// === Cocoa NSObject allocation === + +typedef id (*allocWithZone_t)(id, SEL, NSZone*); +allocWithZone_t g_old_allocWithZone; + +id oom_killer_allocWithZone(id self, SEL _cmd, NSZone* zone) +{ + id result = g_old_allocWithZone(self, _cmd, zone); + if (!result) + debug::BreakDebugger(); + return result; +} + +} // namespace + +void* UncheckedMalloc(size_t size) { + if (g_old_malloc) { + ScopedClearErrno clear_errno; + ThreadLocalBooleanAutoReset flag(g_unchecked_malloc.Pointer(), true); + return g_old_malloc(malloc_default_zone(), size); + } + return malloc(size); +} + +void EnableTerminationOnOutOfMemory() { + if (g_oom_killer_enabled) + return; + + g_oom_killer_enabled = true; + + // === C malloc/calloc/valloc/realloc/posix_memalign === + + // This approach is not perfect, as requests for amounts of memory larger than + // MALLOC_ABSOLUTE_MAX_SIZE (currently SIZE_T_MAX - (2 * PAGE_SIZE)) will + // still fail with a NULL rather than dying (see + // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c for details). + // Unfortunately, it's the best we can do. Also note that this does not affect + // allocations from non-default zones. + + CHECK(!g_old_malloc && !g_old_calloc && !g_old_valloc && !g_old_realloc && + !g_old_memalign) << "Old allocators unexpectedly non-null"; + + CHECK(!g_old_malloc_purgeable && !g_old_calloc_purgeable && + !g_old_valloc_purgeable && !g_old_realloc_purgeable && + !g_old_memalign_purgeable) << "Old allocators unexpectedly non-null"; + +#if !defined(ADDRESS_SANITIZER) + // Don't do anything special on OOM for the malloc zones replaced by + // AddressSanitizer, as modifying or protecting them may not work correctly. + + // See http://trac.webkit.org/changeset/53362/trunk/Tools/DumpRenderTree/mac + bool zone_allocators_protected = base::mac::IsOSLionOrLater(); + + ChromeMallocZone* default_zone = + reinterpret_cast<ChromeMallocZone*>(malloc_default_zone()); + ChromeMallocZone* purgeable_zone = + reinterpret_cast<ChromeMallocZone*>(malloc_default_purgeable_zone()); + + vm_address_t page_start_default = 0; + vm_address_t page_start_purgeable = 0; + vm_size_t len_default = 0; + vm_size_t len_purgeable = 0; + if (zone_allocators_protected) { + page_start_default = reinterpret_cast<vm_address_t>(default_zone) & + static_cast<vm_size_t>(~(getpagesize() - 1)); + len_default = reinterpret_cast<vm_address_t>(default_zone) - + page_start_default + sizeof(ChromeMallocZone); + mprotect(reinterpret_cast<void*>(page_start_default), len_default, + PROT_READ | PROT_WRITE); + + if (purgeable_zone) { + page_start_purgeable = reinterpret_cast<vm_address_t>(purgeable_zone) & + static_cast<vm_size_t>(~(getpagesize() - 1)); + len_purgeable = reinterpret_cast<vm_address_t>(purgeable_zone) - + page_start_purgeable + sizeof(ChromeMallocZone); + mprotect(reinterpret_cast<void*>(page_start_purgeable), len_purgeable, + PROT_READ | PROT_WRITE); + } + } + + // Default zone + + g_old_malloc = default_zone->malloc; + g_old_calloc = default_zone->calloc; + g_old_valloc = default_zone->valloc; + g_old_free = default_zone->free; + g_old_realloc = default_zone->realloc; + CHECK(g_old_malloc && g_old_calloc && g_old_valloc && g_old_free && + g_old_realloc) + << "Failed to get system allocation functions."; + + default_zone->malloc = oom_killer_malloc; + default_zone->calloc = oom_killer_calloc; + default_zone->valloc = oom_killer_valloc; + default_zone->free = oom_killer_free; + default_zone->realloc = oom_killer_realloc; + + if (default_zone->version >= 5) { + g_old_memalign = default_zone->memalign; + if (g_old_memalign) + default_zone->memalign = oom_killer_memalign; + } + + // Purgeable zone (if it exists) + + if (purgeable_zone) { + g_old_malloc_purgeable = purgeable_zone->malloc; + g_old_calloc_purgeable = purgeable_zone->calloc; + g_old_valloc_purgeable = purgeable_zone->valloc; + g_old_free_purgeable = purgeable_zone->free; + g_old_realloc_purgeable = purgeable_zone->realloc; + CHECK(g_old_malloc_purgeable && g_old_calloc_purgeable && + g_old_valloc_purgeable && g_old_free_purgeable && + g_old_realloc_purgeable) + << "Failed to get system allocation functions."; + + purgeable_zone->malloc = oom_killer_malloc_purgeable; + purgeable_zone->calloc = oom_killer_calloc_purgeable; + purgeable_zone->valloc = oom_killer_valloc_purgeable; + purgeable_zone->free = oom_killer_free_purgeable; + purgeable_zone->realloc = oom_killer_realloc_purgeable; + + if (purgeable_zone->version >= 5) { + g_old_memalign_purgeable = purgeable_zone->memalign; + if (g_old_memalign_purgeable) + purgeable_zone->memalign = oom_killer_memalign_purgeable; + } + } + + if (zone_allocators_protected) { + mprotect(reinterpret_cast<void*>(page_start_default), len_default, + PROT_READ); + if (purgeable_zone) { + mprotect(reinterpret_cast<void*>(page_start_purgeable), len_purgeable, + PROT_READ); + } + } +#endif + + // === C malloc_zone_batch_malloc === + + // batch_malloc is omitted because the default malloc zone's implementation + // only supports batch_malloc for "tiny" allocations from the free list. It + // will fail for allocations larger than "tiny", and will only allocate as + // many blocks as it's able to from the free list. These factors mean that it + // can return less than the requested memory even in a non-out-of-memory + // situation. There's no good way to detect whether a batch_malloc failure is + // due to these other factors, or due to genuine memory or address space + // exhaustion. The fact that it only allocates space from the "tiny" free list + // means that it's likely that a failure will not be due to memory exhaustion. + // Similarly, these constraints on batch_malloc mean that callers must always + // be expecting to receive less memory than was requested, even in situations + // where memory pressure is not a concern. Finally, the only public interface + // to batch_malloc is malloc_zone_batch_malloc, which is specific to the + // system's malloc implementation. It's unlikely that anyone's even heard of + // it. + + // === C++ operator new === + + // Yes, operator new does call through to malloc, but this will catch failures + // that our imperfect handling of malloc cannot. + + std::set_new_handler(oom_killer_new); + +#ifndef ADDRESS_SANITIZER + // === Core Foundation CFAllocators === + + // This will not catch allocation done by custom allocators, but will catch + // all allocation done by system-provided ones. + + CHECK(!g_old_cfallocator_system_default && !g_old_cfallocator_malloc && + !g_old_cfallocator_malloc_zone) + << "Old allocators unexpectedly non-null"; + + bool cf_allocator_internals_known = CanGetContextForCFAllocator(); + + if (cf_allocator_internals_known) { + CFAllocatorContext* context = + ContextForCFAllocator(kCFAllocatorSystemDefault); + CHECK(context) << "Failed to get context for kCFAllocatorSystemDefault."; + g_old_cfallocator_system_default = context->allocate; + CHECK(g_old_cfallocator_system_default) + << "Failed to get kCFAllocatorSystemDefault allocation function."; + context->allocate = oom_killer_cfallocator_system_default; + + context = ContextForCFAllocator(kCFAllocatorMalloc); + CHECK(context) << "Failed to get context for kCFAllocatorMalloc."; + g_old_cfallocator_malloc = context->allocate; + CHECK(g_old_cfallocator_malloc) + << "Failed to get kCFAllocatorMalloc allocation function."; + context->allocate = oom_killer_cfallocator_malloc; + + context = ContextForCFAllocator(kCFAllocatorMallocZone); + CHECK(context) << "Failed to get context for kCFAllocatorMallocZone."; + g_old_cfallocator_malloc_zone = context->allocate; + CHECK(g_old_cfallocator_malloc_zone) + << "Failed to get kCFAllocatorMallocZone allocation function."; + context->allocate = oom_killer_cfallocator_malloc_zone; + } else { + NSLog(@"Internals of CFAllocator not known; out-of-memory failures via " + "CFAllocator will not result in termination. http://crbug.com/45650"); + } +#endif + + // === Cocoa NSObject allocation === + + // Note that both +[NSObject new] and +[NSObject alloc] call through to + // +[NSObject allocWithZone:]. + + CHECK(!g_old_allocWithZone) + << "Old allocator unexpectedly non-null"; + + Class nsobject_class = [NSObject class]; + Method orig_method = class_getClassMethod(nsobject_class, + @selector(allocWithZone:)); + g_old_allocWithZone = reinterpret_cast<allocWithZone_t>( + method_getImplementation(orig_method)); + CHECK(g_old_allocWithZone) + << "Failed to get allocWithZone allocation function."; + method_setImplementation(orig_method, + reinterpret_cast<IMP>(oom_killer_allocWithZone)); +} + +ProcessId GetParentProcessId(ProcessHandle process) { + struct kinfo_proc info; + size_t length = sizeof(struct kinfo_proc); + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process }; + if (sysctl(mib, 4, &info, &length, NULL, 0) < 0) { + DPLOG(ERROR) << "sysctl"; + return -1; + } + if (length == 0) + return -1; + return info.kp_eproc.e_ppid; +} + +namespace { + +const int kWaitBeforeKillSeconds = 2; + +// Reap |child| process. This call blocks until completion. +void BlockingReap(pid_t child) { + const pid_t result = HANDLE_EINTR(waitpid(child, NULL, 0)); + if (result == -1) { + DPLOG(ERROR) << "waitpid(" << child << ", NULL, 0)"; + } +} + +// Waits for |timeout| seconds for the given |child| to exit and reap it. If +// the child doesn't exit within the time specified, kills it. +// +// This function takes two approaches: first, it tries to use kqueue to +// observe when the process exits. kevent can monitor a kqueue with a +// timeout, so this method is preferred to wait for a specified period of +// time. Once the kqueue indicates the process has exited, waitpid will reap +// the exited child. If the kqueue doesn't provide an exit event notification, +// before the timeout expires, or if the kqueue fails or misbehaves, the +// process will be mercilessly killed and reaped. +// +// A child process passed to this function may be in one of several states: +// running, terminated and not yet reaped, and (apparently, and unfortunately) +// terminated and already reaped. Normally, a process will at least have been +// asked to exit before this function is called, but this is not required. +// If a process is terminating and unreaped, there may be a window between the +// time that kqueue will no longer recognize it and when it becomes an actual +// zombie that a non-blocking (WNOHANG) waitpid can reap. This condition is +// detected when kqueue indicates that the process is not running and a +// non-blocking waitpid fails to reap the process but indicates that it is +// still running. In this event, a blocking attempt to reap the process +// collects the known-dying child, preventing zombies from congregating. +// +// In the event that the kqueue misbehaves entirely, as it might under a +// EMFILE condition ("too many open files", or out of file descriptors), this +// function will forcibly kill and reap the child without delay. This +// eliminates another potential zombie vector. (If you're out of file +// descriptors, you're probably deep into something else, but that doesn't +// mean that zombies be allowed to kick you while you're down.) +// +// The fact that this function seemingly can be called to wait on a child +// that's not only already terminated but already reaped is a bit of a +// problem: a reaped child's pid can be reclaimed and may refer to a distinct +// process in that case. The fact that this function can seemingly be called +// to wait on a process that's not even a child is also a problem: kqueue will +// work in that case, but waitpid won't, and killing a non-child might not be +// the best approach. +void WaitForChildToDie(pid_t child, int timeout) { + DCHECK(child > 0); + DCHECK(timeout > 0); + + // DON'T ADD ANY EARLY RETURNS TO THIS FUNCTION without ensuring that + // |child| has been reaped. Specifically, even if a kqueue, kevent, or other + // call fails, this function should fall back to the last resort of trying + // to kill and reap the process. Not observing this rule will resurrect + // zombies. + + int result; + + int kq = HANDLE_EINTR(kqueue()); + if (kq == -1) { + DPLOG(ERROR) << "kqueue()"; + } else { + file_util::ScopedFD auto_close_kq(&kq); + + struct kevent change = {0}; + EV_SET(&change, child, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); + result = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL)); + + if (result == -1) { + if (errno != ESRCH) { + DPLOG(ERROR) << "kevent (setup " << child << ")"; + } else { + // At this point, one of the following has occurred: + // 1. The process has died but has not yet been reaped. + // 2. The process has died and has already been reaped. + // 3. The process is in the process of dying. It's no longer + // kqueueable, but it may not be waitable yet either. Mark calls + // this case the "zombie death race". + + result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG)); + + if (result != 0) { + // A positive result indicates case 1. waitpid succeeded and reaped + // the child. A result of -1 indicates case 2. The child has already + // been reaped. In both of these cases, no further action is + // necessary. + return; + } + + // |result| is 0, indicating case 3. The process will be waitable in + // short order. Fall back out of the kqueue code to kill it (for good + // measure) and reap it. + } + } else { + // Keep track of the elapsed time to be able to restart kevent if it's + // interrupted. + TimeDelta remaining_delta = TimeDelta::FromSeconds(timeout); + Time deadline = Time::Now() + remaining_delta; + result = -1; + struct kevent event = {0}; + while (remaining_delta.InMilliseconds() > 0) { + const struct timespec remaining_timespec = remaining_delta.ToTimeSpec(); + result = kevent(kq, NULL, 0, &event, 1, &remaining_timespec); + if (result == -1 && errno == EINTR) { + remaining_delta = deadline - Time::Now(); + result = 0; + } else { + break; + } + } + + if (result == -1) { + DPLOG(ERROR) << "kevent (wait " << child << ")"; + } else if (result > 1) { + DLOG(ERROR) << "kevent (wait " << child << "): unexpected result " + << result; + } else if (result == 1) { + if ((event.fflags & NOTE_EXIT) && + (event.ident == static_cast<uintptr_t>(child))) { + // The process is dead or dying. This won't block for long, if at + // all. + BlockingReap(child); + return; + } else { + DLOG(ERROR) << "kevent (wait " << child + << "): unexpected event: fflags=" << event.fflags + << ", ident=" << event.ident; + } + } + } + } + + // The child is still alive, or is very freshly dead. Be sure by sending it + // a signal. This is safe even if it's freshly dead, because it will be a + // zombie (or on the way to zombiedom) and kill will return 0 even if the + // signal is not delivered to a live process. + result = kill(child, SIGKILL); + if (result == -1) { + DPLOG(ERROR) << "kill(" << child << ", SIGKILL)"; + } else { + // The child is definitely on the way out now. BlockingReap won't need to + // wait for long, if at all. + BlockingReap(child); + } +} + +} // namespace + +void EnsureProcessTerminated(ProcessHandle process) { + WaitForChildToDie(process, kWaitBeforeKillSeconds); +} + +} // namespace base
diff --git a/src/base/process_util_openbsd.cc b/src/base/process_util_openbsd.cc new file mode 100644 index 0000000..2b2bd25 --- /dev/null +++ b/src/base/process_util_openbsd.cc
@@ -0,0 +1,346 @@ +// 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/process_util.h" + +#include <ctype.h> +#include <dirent.h> +#include <dlfcn.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/param.h> +#include <sys/sysctl.h> +#include <sys/user.h> +#include <time.h> +#include <unistd.h> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/string_number_conversions.h" +#include "base/string_split.h" +#include "base/string_tokenizer.h" +#include "base/string_util.h" +#include "base/sys_info.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +ProcessId GetParentProcessId(ProcessHandle process) { + struct kinfo_proc info; + size_t length; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process, + sizeof(struct kinfo_proc), 0 }; + + if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0) + return -1; + + mib[5] = (length / sizeof(struct kinfo_proc)); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return -1; + + return info.p_ppid; +} + +FilePath GetProcessExecutablePath(ProcessHandle process) { + struct kinfo_proc kp; + size_t len; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process, + sizeof(struct kinfo_proc), 0 }; + + if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) == -1) + return FilePath(); + mib[5] = (len / sizeof(struct kinfo_proc)); + if (sysctl(mib, arraysize(mib), &kp, &len, NULL, 0) < 0) + return FilePath(); + if ((kp.p_flag & P_SYSTEM) != 0) + return FilePath(); + if (strcmp(kp.p_comm, "chrome") == 0) + return FilePath(kp.p_comm); + + return FilePath(); +} + +ProcessIterator::ProcessIterator(const ProcessFilter* filter) + : index_of_kinfo_proc_(), + filter_(filter) { + + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, getuid(), + sizeof(struct kinfo_proc), 0 }; + + bool done = false; + int try_num = 1; + const int max_tries = 10; + + do { + size_t len = 0; + if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) < 0) { + DLOG(ERROR) << "failed to get the size needed for the process list"; + kinfo_procs_.resize(0); + done = true; + } else { + size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc); + // Leave some spare room for process table growth (more could show up + // between when we check and now) + num_of_kinfo_proc += 16; + kinfo_procs_.resize(num_of_kinfo_proc); + len = num_of_kinfo_proc * sizeof(struct kinfo_proc); + if (sysctl(mib, arraysize(mib), &kinfo_procs_[0], &len, NULL, 0) < 0) { + // If we get a mem error, it just means we need a bigger buffer, so + // loop around again. Anything else is a real error and give up. + if (errno != ENOMEM) { + DLOG(ERROR) << "failed to get the process list"; + kinfo_procs_.resize(0); + done = true; + } + } else { + // Got the list, just make sure we're sized exactly right + size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc); + kinfo_procs_.resize(num_of_kinfo_proc); + done = true; + } + } + } while (!done && (try_num++ < max_tries)); + + if (!done) { + DLOG(ERROR) << "failed to collect the process list in a few tries"; + kinfo_procs_.resize(0); + } +} + +ProcessIterator::~ProcessIterator() { +} + +bool ProcessIterator::CheckForNextProcess() { + std::string data; + for (; index_of_kinfo_proc_ < kinfo_procs_.size(); ++index_of_kinfo_proc_) { + kinfo_proc& kinfo = kinfo_procs_[index_of_kinfo_proc_]; + + // Skip processes just awaiting collection + if ((kinfo.p_pid > 0) && (kinfo.p_stat == SZOMB)) + continue; + + int mib[] = { CTL_KERN, KERN_PROC_ARGS, kinfo.p_pid }; + + // Find out what size buffer we need. + size_t data_len = 0; + if (sysctl(mib, arraysize(mib), NULL, &data_len, NULL, 0) < 0) { + DVPLOG(1) << "failed to figure out the buffer size for a commandline"; + continue; + } + + data.resize(data_len); + if (sysctl(mib, arraysize(mib), &data[0], &data_len, NULL, 0) < 0) { + DVPLOG(1) << "failed to fetch a commandline"; + continue; + } + + // |data| contains all the command line parameters of the process, separated + // by blocks of one or more null characters. We tokenize |data| into a + // vector of strings using '\0' as a delimiter and populate + // |entry_.cmd_line_args_|. + std::string delimiters; + delimiters.push_back('\0'); + Tokenize(data, delimiters, &entry_.cmd_line_args_); + + // |data| starts with the full executable path followed by a null character. + // We search for the first instance of '\0' and extract everything before it + // to populate |entry_.exe_file_|. + size_t exec_name_end = data.find('\0'); + if (exec_name_end == std::string::npos) { + DLOG(ERROR) << "command line data didn't match expected format"; + continue; + } + + entry_.pid_ = kinfo.p_pid; + entry_.ppid_ = kinfo.p_ppid; + entry_.gid_ = kinfo.p__pgid; + size_t last_slash = data.rfind('/', exec_name_end); + if (last_slash == std::string::npos) + entry_.exe_file_.assign(data, 0, exec_name_end); + else + entry_.exe_file_.assign(data, last_slash + 1, + exec_name_end - last_slash - 1); + // Start w/ the next entry next time through + ++index_of_kinfo_proc_; + // Done + return true; + } + return false; +} + +bool NamedProcessIterator::IncludeEntry() { + return (executable_name_ == entry().exe_file() && + ProcessIterator::IncludeEntry()); +} + + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + last_time_(0), + last_system_time_(0), + last_cpu_(0) { + + processor_count_ = base::SysInfo::NumberOfProcessors(); +} + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + struct kinfo_proc info; + size_t length; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_, + sizeof(struct kinfo_proc), 0 }; + + if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0) + return -1; + + mib[5] = (length / sizeof(struct kinfo_proc)); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return -1; + + return (info.p_vm_tsize + info.p_vm_dsize + info.p_vm_ssize); +} + +size_t ProcessMetrics::GetPeakPagefileUsage() const { + + return 0; +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + struct kinfo_proc info; + size_t length; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_, + sizeof(struct kinfo_proc), 0 }; + + if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0) + return -1; + + mib[5] = (length / sizeof(struct kinfo_proc)); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return -1; + + return info.p_vm_rssize * getpagesize(); +} + +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + + return 0; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + WorkingSetKBytes ws_usage; + + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + if (private_bytes) + *private_bytes = ws_usage.priv << 10; + + if (shared_bytes) + *shared_bytes = ws_usage.shared * 1024; + + return true; +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { +// TODO(bapt) be sure we can't be precise + size_t priv = GetWorkingSetSize(); + if (!priv) + return false; + ws_usage->priv = priv / 1024; + ws_usage->shareable = 0; + ws_usage->shared = 0; + + return true; +} + +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + return false; +} + +static int GetProcessCPU(pid_t pid) { + struct kinfo_proc info; + size_t length; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid, + sizeof(struct kinfo_proc), 0 }; + + if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0) + return -1; + + mib[5] = (length / sizeof(struct kinfo_proc)); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return 0; + + return info.p_pctcpu; +} + +double ProcessMetrics::GetCPUUsage() { + struct timeval now; + + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + + int64 time = TimeValToMicroseconds(now); + + if (last_time_ == 0) { + // First call, just set the last values. + last_time_ = time; + last_cpu_ = GetProcessCPU(process_); + return 0; + } + + int64 time_delta = time - last_time_; + DCHECK_NE(time_delta, 0); + + if (time_delta == 0) + return 0; + + int cpu = GetProcessCPU(process_); + + last_time_ = time; + last_cpu_ = cpu; + + double percentage = static_cast<double>((cpu * 100.0) / FSCALE); + + return percentage; +} + +size_t GetSystemCommitCharge() { + int mib[] = { CTL_VM, VM_METER }; + int pagesize; + struct vmtotal vmtotal; + unsigned long mem_total, mem_free, mem_inactive; + size_t len = sizeof(vmtotal); + + if (sysctl(mib, arraysize(mib), &vmtotal, &len, NULL, 0) < 0) + return 0; + + mem_total = vmtotal.t_vm; + mem_free = vmtotal.t_free; + mem_inactive = vmtotal.t_vm - vmtotal.t_avm; + + pagesize = getpagesize(); + + return mem_total - (mem_free*pagesize) - (mem_inactive*pagesize); +} + +void EnableTerminationOnOutOfMemory() { +} + +void EnableTerminationOnHeapCorruption() { +} + +} // namespace base
diff --git a/src/base/process_util_posix.cc b/src/base/process_util_posix.cc new file mode 100644 index 0000000..284d00f --- /dev/null +++ b/src/base/process_util_posix.cc
@@ -0,0 +1,1280 @@ +// 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 <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdlib.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <iterator> +#include <limits> +#include <set> + +#include "base/allocator/type_profiler_control.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/debug/debugger.h" +#include "base/debug/stack_trace.h" +#include "base/file_util.h" +#include "base/files/dir_reader_posix.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process_util.h" +#include "base/stringprintf.h" +#include "base/synchronization/waitable_event.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread_restrictions.h" + +#if defined(OS_CHROMEOS) +#include <sys/ioctl.h> +#endif + +#if defined(OS_FREEBSD) +#include <sys/event.h> +#include <sys/ucontext.h> +#endif + +#if defined(OS_MACOSX) +#include <crt_externs.h> +#include <sys/event.h> +#else +extern char** environ; +#endif + +namespace base { + +namespace { + +// Get the process's "environment" (i.e. the thing that setenv/getenv +// work with). +char** GetEnvironment() { +#if defined(OS_MACOSX) + return *_NSGetEnviron(); +#else + return environ; +#endif +} + +// Set the process's "environment" (i.e. the thing that setenv/getenv +// work with). +void SetEnvironment(char** env) { +#if defined(OS_MACOSX) + *_NSGetEnviron() = env; +#else + environ = env; +#endif +} + +int WaitpidWithTimeout(ProcessHandle handle, int64 wait_milliseconds, + bool* success) { + // This POSIX version of this function only guarantees that we wait no less + // than |wait_milliseconds| for the process to exit. The child process may + // exit sometime before the timeout has ended but we may still block for up + // to 256 milliseconds after the fact. + // + // waitpid() has no direct support on POSIX for specifying a timeout, you can + // either ask it to block indefinitely or return immediately (WNOHANG). + // When a child process terminates a SIGCHLD signal is sent to the parent. + // Catching this signal would involve installing a signal handler which may + // affect other parts of the application and would be difficult to debug. + // + // Our strategy is to call waitpid() once up front to check if the process + // has already exited, otherwise to loop for wait_milliseconds, sleeping for + // at most 256 milliseconds each time using usleep() and then calling + // waitpid(). The amount of time we sleep starts out at 1 milliseconds, and + // we double it every 4 sleep cycles. + // + // usleep() is speced to exit if a signal is received for which a handler + // has been installed. This means that when a SIGCHLD is sent, it will exit + // depending on behavior external to this function. + // + // This function is used primarily for unit tests, if we want to use it in + // the application itself it would probably be best to examine other routes. + int status = -1; + pid_t ret_pid = HANDLE_EINTR(waitpid(handle, &status, WNOHANG)); + static const int64 kMaxSleepInMicroseconds = 1 << 18; // ~256 milliseconds. + int64 max_sleep_time_usecs = 1 << 10; // ~1 milliseconds. + int64 double_sleep_time = 0; + + // If the process hasn't exited yet, then sleep and try again. + Time wakeup_time = Time::Now() + + TimeDelta::FromMilliseconds(wait_milliseconds); + while (ret_pid == 0) { + Time now = Time::Now(); + if (now > wakeup_time) + break; + // Guaranteed to be non-negative! + int64 sleep_time_usecs = (wakeup_time - now).InMicroseconds(); + // Sleep for a bit while we wait for the process to finish. + if (sleep_time_usecs > max_sleep_time_usecs) + sleep_time_usecs = max_sleep_time_usecs; + + // usleep() will return 0 and set errno to EINTR on receipt of a signal + // such as SIGCHLD. + usleep(sleep_time_usecs); + ret_pid = HANDLE_EINTR(waitpid(handle, &status, WNOHANG)); + + if ((max_sleep_time_usecs < kMaxSleepInMicroseconds) && + (double_sleep_time++ % 4 == 0)) { + max_sleep_time_usecs *= 2; + } + } + + if (success) + *success = (ret_pid != -1); + + return status; +} + +void ResetChildSignalHandlersToDefaults() { + // The previous signal handlers are likely to be meaningless in the child's + // context so we reset them to the defaults for now. http://crbug.com/44953 + // These signal handlers are set up at least in browser_main_posix.cc: + // BrowserMainPartsPosix::PreEarlyInitialization and stack_trace_posix.cc: + // EnableInProcessStackDumping. + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGILL, SIG_DFL); + signal(SIGABRT, SIG_DFL); + signal(SIGFPE, SIG_DFL); + signal(SIGBUS, SIG_DFL); + signal(SIGSEGV, SIG_DFL); + signal(SIGSYS, SIG_DFL); + signal(SIGTERM, SIG_DFL); +} + +TerminationStatus GetTerminationStatusImpl(ProcessHandle handle, + bool can_block, + int* exit_code) { + int status = 0; + const pid_t result = HANDLE_EINTR(waitpid(handle, &status, + can_block ? 0 : WNOHANG)); + if (result == -1) { + DPLOG(ERROR) << "waitpid(" << handle << ")"; + if (exit_code) + *exit_code = 0; + return TERMINATION_STATUS_NORMAL_TERMINATION; + } else if (result == 0) { + // the child hasn't exited yet. + if (exit_code) + *exit_code = 0; + return TERMINATION_STATUS_STILL_RUNNING; + } + + if (exit_code) + *exit_code = status; + + if (WIFSIGNALED(status)) { + switch (WTERMSIG(status)) { + case SIGABRT: + case SIGBUS: + case SIGFPE: + case SIGILL: + case SIGSEGV: + return TERMINATION_STATUS_PROCESS_CRASHED; + case SIGINT: + case SIGKILL: + case SIGTERM: + return TERMINATION_STATUS_PROCESS_WAS_KILLED; + default: + break; + } + } + + if (WIFEXITED(status) && WEXITSTATUS(status) != 0) + return TERMINATION_STATUS_ABNORMAL_TERMINATION; + + return TERMINATION_STATUS_NORMAL_TERMINATION; +} + +} // anonymous namespace + +ProcessId GetCurrentProcId() { + return getpid(); +} + +ProcessHandle GetCurrentProcessHandle() { + return GetCurrentProcId(); +} + +bool OpenProcessHandle(ProcessId pid, ProcessHandle* handle) { + // On Posix platforms, process handles are the same as PIDs, so we + // don't need to do anything. + *handle = pid; + return true; +} + +bool OpenPrivilegedProcessHandle(ProcessId pid, ProcessHandle* handle) { + // On POSIX permissions are checked for each operation on process, + // not when opening a "handle". + return OpenProcessHandle(pid, handle); +} + +bool OpenProcessHandleWithAccess(ProcessId pid, + uint32 access_flags, + ProcessHandle* handle) { + // On POSIX permissions are checked for each operation on process, + // not when opening a "handle". + return OpenProcessHandle(pid, handle); +} + +void CloseProcessHandle(ProcessHandle process) { + // See OpenProcessHandle, nothing to do. + return; +} + +ProcessId GetProcId(ProcessHandle process) { + return process; +} + +// Attempts to kill the process identified by the given process +// entry structure. Ignores specified exit_code; posix can't force that. +// Returns true if this is successful, false otherwise. +bool KillProcess(ProcessHandle process_id, int exit_code, bool wait) { + DCHECK_GT(process_id, 1) << " tried to kill invalid process_id"; + if (process_id <= 1) + return false; + bool result = kill(process_id, SIGTERM) == 0; + if (result && wait) { + int tries = 60; + + if (RunningOnValgrind()) { + // Wait for some extra time when running under Valgrind since the child + // processes may take some time doing leak checking. + tries *= 2; + } + + unsigned sleep_ms = 4; + + // The process may not end immediately due to pending I/O + bool exited = false; + while (tries-- > 0) { + pid_t pid = HANDLE_EINTR(waitpid(process_id, NULL, WNOHANG)); + if (pid == process_id) { + exited = true; + break; + } + if (pid == -1) { + if (errno == ECHILD) { + // The wait may fail with ECHILD if another process also waited for + // the same pid, causing the process state to get cleaned up. + exited = true; + break; + } + DPLOG(ERROR) << "Error waiting for process " << process_id; + } + + usleep(sleep_ms * 1000); + const unsigned kMaxSleepMs = 1000; + if (sleep_ms < kMaxSleepMs) + sleep_ms *= 2; + } + + // If we're waiting and the child hasn't died by now, force it + // with a SIGKILL. + if (!exited) + result = kill(process_id, SIGKILL) == 0; + } + + if (!result) + DPLOG(ERROR) << "Unable to terminate process " << process_id; + + return result; +} + +bool KillProcessGroup(ProcessHandle process_group_id) { + bool result = kill(-1 * process_group_id, SIGKILL) == 0; + if (!result) + DPLOG(ERROR) << "Unable to terminate process group " << process_group_id; + return result; +} + +// A class to handle auto-closing of DIR*'s. +class ScopedDIRClose { + public: + inline void operator()(DIR* x) const { + if (x) { + closedir(x); + } + } +}; +typedef scoped_ptr_malloc<DIR, ScopedDIRClose> ScopedDIR; + +#if defined(OS_LINUX) + static const rlim_t kSystemDefaultMaxFds = 8192; + static const char kFDDir[] = "/proc/self/fd"; +#elif defined(OS_MACOSX) + static const rlim_t kSystemDefaultMaxFds = 256; + static const char kFDDir[] = "/dev/fd"; +#elif defined(OS_SOLARIS) + static const rlim_t kSystemDefaultMaxFds = 8192; + static const char kFDDir[] = "/dev/fd"; +#elif defined(OS_FREEBSD) + static const rlim_t kSystemDefaultMaxFds = 8192; + static const char kFDDir[] = "/dev/fd"; +#elif defined(OS_OPENBSD) + static const rlim_t kSystemDefaultMaxFds = 256; + static const char kFDDir[] = "/dev/fd"; +#elif defined(OS_ANDROID) + static const rlim_t kSystemDefaultMaxFds = 1024; + static const char kFDDir[] = "/proc/self/fd"; +#endif + +void CloseSuperfluousFds(const base::InjectiveMultimap& saved_mapping) { + // DANGER: no calls to malloc are allowed from now on: + // http://crbug.com/36678 + + // Get the maximum number of FDs possible. + struct rlimit nofile; + rlim_t max_fds; + if (getrlimit(RLIMIT_NOFILE, &nofile)) { + // getrlimit failed. Take a best guess. + max_fds = kSystemDefaultMaxFds; + RAW_LOG(ERROR, "getrlimit(RLIMIT_NOFILE) failed"); + } else { + max_fds = nofile.rlim_cur; + } + + if (max_fds > INT_MAX) + max_fds = INT_MAX; + + DirReaderPosix fd_dir(kFDDir); + + if (!fd_dir.IsValid()) { + // Fallback case: Try every possible fd. + for (rlim_t i = 0; i < max_fds; ++i) { + const int fd = static_cast<int>(i); + if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) + continue; + InjectiveMultimap::const_iterator j; + for (j = saved_mapping.begin(); j != saved_mapping.end(); j++) { + if (fd == j->dest) + break; + } + if (j != saved_mapping.end()) + continue; + + // Since we're just trying to close anything we can find, + // ignore any error return values of close(). + ignore_result(HANDLE_EINTR(close(fd))); + } + return; + } + + const int dir_fd = fd_dir.fd(); + + for ( ; fd_dir.Next(); ) { + // Skip . and .. entries. + if (fd_dir.name()[0] == '.') + continue; + + char *endptr; + errno = 0; + const long int fd = strtol(fd_dir.name(), &endptr, 10); + if (fd_dir.name()[0] == 0 || *endptr || fd < 0 || errno) + continue; + if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) + continue; + InjectiveMultimap::const_iterator i; + for (i = saved_mapping.begin(); i != saved_mapping.end(); i++) { + if (fd == i->dest) + break; + } + if (i != saved_mapping.end()) + continue; + if (fd == dir_fd) + continue; + + // When running under Valgrind, Valgrind opens several FDs for its + // own use and will complain if we try to close them. All of + // 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)); + DPCHECK(ret == 0); + } + } +} + +char** AlterEnvironment(const EnvironmentVector& changes, + const char* const* const env) { + unsigned count = 0; + unsigned size = 0; + + // First assume that all of the current environment will be included. + for (unsigned i = 0; env[i]; i++) { + const char *const pair = env[i]; + count++; + size += strlen(pair) + 1 /* terminating NUL */; + } + + for (EnvironmentVector::const_iterator j = changes.begin(); + j != changes.end(); + ++j) { + bool found = false; + const char *pair; + + for (unsigned i = 0; env[i]; i++) { + pair = env[i]; + const char *const equals = strchr(pair, '='); + if (!equals) + continue; + const unsigned keylen = equals - pair; + if (keylen == j->first.size() && + memcmp(pair, j->first.data(), keylen) == 0) { + found = true; + break; + } + } + + // if found, we'll either be deleting or replacing this element. + if (found) { + count--; + size -= strlen(pair) + 1; + if (j->second.size()) + found = false; + } + + // if !found, then we have a new element to add. + if (!found && !j->second.empty()) { + count++; + size += j->first.size() + 1 /* '=' */ + j->second.size() + 1 /* NUL */; + } + } + + count++; // for the final NULL + uint8_t *buffer = new uint8_t[sizeof(char*) * count + size]; + char **const ret = reinterpret_cast<char**>(buffer); + unsigned k = 0; + char *scratch = reinterpret_cast<char*>(buffer + sizeof(char*) * count); + + for (unsigned i = 0; env[i]; i++) { + const char *const pair = env[i]; + const char *const equals = strchr(pair, '='); + if (!equals) { + const unsigned len = strlen(pair); + ret[k++] = scratch; + memcpy(scratch, pair, len + 1); + scratch += len + 1; + continue; + } + const unsigned keylen = equals - pair; + bool handled = false; + for (EnvironmentVector::const_iterator + j = changes.begin(); j != changes.end(); j++) { + if (j->first.size() == keylen && + memcmp(j->first.data(), pair, keylen) == 0) { + if (!j->second.empty()) { + ret[k++] = scratch; + memcpy(scratch, pair, keylen + 1); + scratch += keylen + 1; + memcpy(scratch, j->second.c_str(), j->second.size() + 1); + scratch += j->second.size() + 1; + } + handled = true; + break; + } + } + + if (!handled) { + const unsigned len = strlen(pair); + ret[k++] = scratch; + memcpy(scratch, pair, len + 1); + scratch += len + 1; + } + } + + // Now handle new elements + for (EnvironmentVector::const_iterator + j = changes.begin(); j != changes.end(); j++) { + if (j->second.empty()) + continue; + + bool found = false; + for (unsigned i = 0; env[i]; i++) { + const char *const pair = env[i]; + const char *const equals = strchr(pair, '='); + if (!equals) + continue; + const unsigned keylen = equals - pair; + if (keylen == j->first.size() && + memcmp(pair, j->first.data(), keylen) == 0) { + found = true; + break; + } + } + + if (!found) { + ret[k++] = scratch; + memcpy(scratch, j->first.data(), j->first.size()); + scratch += j->first.size(); + *scratch++ = '='; + memcpy(scratch, j->second.c_str(), j->second.size() + 1); + scratch += j->second.size() + 1; + } + } + + ret[k] = NULL; + return ret; +} + +bool LaunchProcess(const std::vector<std::string>& argv, + const LaunchOptions& options, + ProcessHandle* process_handle) { + size_t fd_shuffle_size = 0; + if (options.fds_to_remap) { + fd_shuffle_size = options.fds_to_remap->size(); + } + +#if defined(OS_MACOSX) + if (options.synchronize) { + // When synchronizing, the "read" end of the synchronization pipe needs + // to make it to the child process. This is handled by mapping it back to + // itself. + ++fd_shuffle_size; + } +#endif // defined(OS_MACOSX) + + InjectiveMultimap fd_shuffle1; + InjectiveMultimap fd_shuffle2; + fd_shuffle1.reserve(fd_shuffle_size); + fd_shuffle2.reserve(fd_shuffle_size); + + scoped_array<char*> argv_cstr(new char*[argv.size() + 1]); + scoped_array<char*> new_environ; + if (options.environ) + new_environ.reset(AlterEnvironment(*options.environ, GetEnvironment())); + +#if defined(OS_MACOSX) + int synchronization_pipe_fds[2]; + file_util::ScopedFD synchronization_read_fd; + file_util::ScopedFD synchronization_write_fd; + + if (options.synchronize) { + // wait means "don't return from LaunchProcess until the child exits", and + // synchronize means "return from LaunchProcess but don't let the child + // run until LaunchSynchronize is called". These two options are highly + // incompatible. + DCHECK(!options.wait); + + // Create the pipe used for synchronization. + if (HANDLE_EINTR(pipe(synchronization_pipe_fds)) != 0) { + DPLOG(ERROR) << "pipe"; + return false; + } + + // The parent process will only use synchronization_write_fd as the write + // side of the pipe. It can close the read side as soon as the child + // process has forked off. The child process will only use + // synchronization_read_fd as the read side of the pipe. In that process, + // the write side can be closed as soon as it has forked. + synchronization_read_fd.reset(&synchronization_pipe_fds[0]); + synchronization_write_fd.reset(&synchronization_pipe_fds[1]); + } +#endif // OS_MACOSX + + pid_t pid; +#if defined(OS_LINUX) + if (options.clone_flags) { + pid = syscall(__NR_clone, options.clone_flags, 0, 0, 0); + } else +#endif + { + pid = fork(); + } + + if (pid < 0) { + DPLOG(ERROR) << "fork"; + return false; + } else if (pid == 0) { + // Child process + + // DANGER: fork() rule: in the child, if you don't end up doing exec*(), + // you call _exit() instead of exit(). This is because _exit() does not + // call any previously-registered (in the parent) exit handlers, which + // might do things like block waiting for threads that don't even exist + // in the child. + + // If a child process uses the readline library, the process block forever. + // In BSD like OSes including OS X it is safe to assign /dev/null as stdin. + // See http://crbug.com/56596. + int null_fd = HANDLE_EINTR(open("/dev/null", O_RDONLY)); + if (null_fd < 0) { + RAW_LOG(ERROR, "Failed to open /dev/null"); + _exit(127); + } + + file_util::ScopedFD null_fd_closer(&null_fd); + int new_fd = HANDLE_EINTR(dup2(null_fd, STDIN_FILENO)); + if (new_fd != STDIN_FILENO) { + RAW_LOG(ERROR, "Failed to dup /dev/null for stdin"); + _exit(127); + } + + if (options.new_process_group) { + // Instead of inheriting the process group ID of the parent, the child + // starts off a new process group with pgid equal to its process ID. + if (setpgid(0, 0) < 0) { + RAW_LOG(ERROR, "setpgid failed"); + _exit(127); + } + } + + // Stop type-profiler. + // The profiler should be stopped between fork and exec since it inserts + // locks at new/delete expressions. See http://crbug.com/36678. + base::type_profiler::Controller::Stop(); + + if (options.maximize_rlimits) { + // Some resource limits need to be maximal in this child. + std::set<int>::const_iterator resource; + for (resource = options.maximize_rlimits->begin(); + resource != options.maximize_rlimits->end(); + ++resource) { + struct rlimit limit; + if (getrlimit(*resource, &limit) < 0) { + RAW_LOG(WARNING, "getrlimit failed"); + } else if (limit.rlim_cur < limit.rlim_max) { + limit.rlim_cur = limit.rlim_max; + if (setrlimit(*resource, &limit) < 0) { + RAW_LOG(WARNING, "setrlimit failed"); + } + } + } + } + +#if defined(OS_MACOSX) + RestoreDefaultExceptionHandler(); +#endif // defined(OS_MACOSX) + + ResetChildSignalHandlersToDefaults(); + +#if defined(OS_MACOSX) + if (options.synchronize) { + // The "write" side of the synchronization pipe belongs to the parent. + synchronization_write_fd.reset(); // closes synchronization_pipe_fds[1] + } +#endif // defined(OS_MACOSX) + +#if 0 + // When debugging it can be helpful to check that we really aren't making + // any hidden calls to malloc. + void *malloc_thunk = + reinterpret_cast<void*>(reinterpret_cast<intptr_t>(malloc) & ~4095); + mprotect(malloc_thunk, 4096, PROT_READ | PROT_WRITE | PROT_EXEC); + memset(reinterpret_cast<void*>(malloc), 0xff, 8); +#endif // 0 + + // DANGER: no calls to malloc are allowed from now on: + // http://crbug.com/36678 + +#if defined(OS_CHROMEOS) + if (options.ctrl_terminal_fd >= 0) { + // Set process' controlling terminal. + if (HANDLE_EINTR(setsid()) != -1) { + if (HANDLE_EINTR( + ioctl(options.ctrl_terminal_fd, TIOCSCTTY, NULL)) == -1) { + RAW_LOG(WARNING, "ioctl(TIOCSCTTY), ctrl terminal not set"); + } + } else { + RAW_LOG(WARNING, "setsid failed, ctrl terminal not set"); + } + } +#endif // defined(OS_CHROMEOS) + + if (options.fds_to_remap) { + for (FileHandleMappingVector::const_iterator + it = options.fds_to_remap->begin(); + it != options.fds_to_remap->end(); ++it) { + fd_shuffle1.push_back(InjectionArc(it->first, it->second, false)); + fd_shuffle2.push_back(InjectionArc(it->first, it->second, false)); + } + } + +#if defined(OS_MACOSX) + if (options.synchronize) { + // Remap the read side of the synchronization pipe back onto itself, + // ensuring that it won't be closed by CloseSuperfluousFds. + int keep_fd = *synchronization_read_fd.get(); + fd_shuffle1.push_back(InjectionArc(keep_fd, keep_fd, false)); + fd_shuffle2.push_back(InjectionArc(keep_fd, keep_fd, false)); + } +#endif // defined(OS_MACOSX) + + if (options.environ) + SetEnvironment(new_environ.get()); + + // fd_shuffle1 is mutated by this call because it cannot malloc. + if (!ShuffleFileDescriptors(&fd_shuffle1)) + _exit(127); + + CloseSuperfluousFds(fd_shuffle2); + +#if defined(OS_MACOSX) + if (options.synchronize) { + // Do a blocking read to wait until the parent says it's OK to proceed. + // The byte that's read here is written by LaunchSynchronize. + char read_char; + int read_result = + HANDLE_EINTR(read(*synchronization_read_fd.get(), &read_char, 1)); + if (read_result != 1) { + RAW_LOG(ERROR, "LaunchProcess: synchronization read: error"); + _exit(127); + } + + // The pipe is no longer useful. Don't let it live on in the new process + // after exec. + synchronization_read_fd.reset(); // closes synchronization_pipe_fds[0] + } +#endif // defined(OS_MACOSX) + + for (size_t i = 0; i < argv.size(); i++) + argv_cstr[i] = const_cast<char*>(argv[i].c_str()); + argv_cstr[argv.size()] = NULL; + execvp(argv_cstr[0], argv_cstr.get()); + + RAW_LOG(ERROR, "LaunchProcess: failed to execvp:"); + RAW_LOG(ERROR, argv_cstr[0]); + _exit(127); + } else { + // Parent process + if (options.wait) { + // While this isn't strictly disk IO, waiting for another process to + // finish is the sort of thing ThreadRestrictions is trying to prevent. + base::ThreadRestrictions::AssertIOAllowed(); + pid_t ret = HANDLE_EINTR(waitpid(pid, 0, 0)); + DPCHECK(ret > 0); + } + + if (process_handle) + *process_handle = pid; + +#if defined(OS_MACOSX) + if (options.synchronize) { + // The "read" side of the synchronization pipe belongs to the child. + synchronization_read_fd.reset(); // closes synchronization_pipe_fds[0] + *options.synchronize = new int(*synchronization_write_fd.release()); + } +#endif // defined(OS_MACOSX) + } + + return true; +} + + +bool LaunchProcess(const CommandLine& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle) { + return LaunchProcess(cmdline.argv(), options, process_handle); +} + +#if defined(OS_MACOSX) +void LaunchSynchronize(LaunchSynchronizationHandle handle) { + int synchronization_fd = *handle; + file_util::ScopedFD synchronization_fd_closer(&synchronization_fd); + delete handle; + + // Write a '\0' character to the pipe. + if (HANDLE_EINTR(write(synchronization_fd, "", 1)) != 1) { + DPLOG(ERROR) << "write"; + } +} +#endif // defined(OS_MACOSX) + +ProcessMetrics::~ProcessMetrics() { } + +void RaiseProcessToHighPriority() { + // On POSIX, we don't actually do anything here. We could try to nice() or + // setpriority() or sched_getscheduler, but these all require extra rights. +} + +TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) { + return GetTerminationStatusImpl(handle, false /* can_block */, exit_code); +} + +TerminationStatus WaitForTerminationStatus(ProcessHandle handle, + int* exit_code) { + return GetTerminationStatusImpl(handle, true /* can_block */, exit_code); +} + +bool WaitForExitCode(ProcessHandle handle, int* exit_code) { + int status; + if (HANDLE_EINTR(waitpid(handle, &status, 0)) == -1) { + NOTREACHED(); + return false; + } + + if (WIFEXITED(status)) { + *exit_code = WEXITSTATUS(status); + return true; + } + + // If it didn't exit cleanly, it must have been signaled. + DCHECK(WIFSIGNALED(status)); + return false; +} + +bool WaitForExitCodeWithTimeout(ProcessHandle handle, int* exit_code, + base::TimeDelta timeout) { + bool waitpid_success = false; + int status = WaitpidWithTimeout(handle, timeout.InMilliseconds(), + &waitpid_success); + if (status == -1) + return false; + if (!waitpid_success) + return false; + if (WIFSIGNALED(status)) { + *exit_code = -1; + return true; + } + if (WIFEXITED(status)) { + *exit_code = WEXITSTATUS(status); + return true; + } + return false; +} + +#if defined(OS_MACOSX) +// Using kqueue on Mac so that we can wait on non-child processes. +// We can't use kqueues on child processes because we need to reap +// our own children using wait. +static bool WaitForSingleNonChildProcess(ProcessHandle handle, + base::TimeDelta wait) { + DCHECK_GT(handle, 0); + DCHECK(wait.InMilliseconds() == base::kNoTimeout || wait > base::TimeDelta()); + + int kq = kqueue(); + if (kq == -1) { + DPLOG(ERROR) << "kqueue"; + return false; + } + file_util::ScopedFD kq_closer(&kq); + + struct kevent change = {0}; + EV_SET(&change, handle, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); + int result = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL)); + if (result == -1) { + if (errno == ESRCH) { + // If the process wasn't found, it must be dead. + return true; + } + + DPLOG(ERROR) << "kevent (setup " << handle << ")"; + return false; + } + + // Keep track of the elapsed time to be able to restart kevent if it's + // interrupted. + bool wait_forever = wait.InMilliseconds() == base::kNoTimeout; + base::TimeDelta remaining_delta; + base::Time deadline; + if (!wait_forever) { + remaining_delta = wait; + deadline = base::Time::Now() + remaining_delta; + } + + result = -1; + struct kevent event = {0}; + + while (wait_forever || remaining_delta > base::TimeDelta()) { + struct timespec remaining_timespec; + struct timespec* remaining_timespec_ptr; + if (wait_forever) { + remaining_timespec_ptr = NULL; + } else { + remaining_timespec = remaining_delta.ToTimeSpec(); + remaining_timespec_ptr = &remaining_timespec; + } + + result = kevent(kq, NULL, 0, &event, 1, remaining_timespec_ptr); + + if (result == -1 && errno == EINTR) { + if (!wait_forever) { + remaining_delta = deadline - base::Time::Now(); + } + result = 0; + } else { + break; + } + } + + if (result < 0) { + DPLOG(ERROR) << "kevent (wait " << handle << ")"; + return false; + } else if (result > 1) { + DLOG(ERROR) << "kevent (wait " << handle << "): unexpected result " + << result; + return false; + } else if (result == 0) { + // Timed out. + return false; + } + + DCHECK_EQ(result, 1); + + if (event.filter != EVFILT_PROC || + (event.fflags & NOTE_EXIT) == 0 || + event.ident != static_cast<uintptr_t>(handle)) { + DLOG(ERROR) << "kevent (wait " << handle + << "): unexpected event: filter=" << event.filter + << ", fflags=" << event.fflags + << ", ident=" << event.ident; + return false; + } + + return true; +} +#endif // OS_MACOSX + +bool WaitForSingleProcess(ProcessHandle handle, base::TimeDelta wait) { + ProcessHandle parent_pid = GetParentProcessId(handle); + ProcessHandle our_pid = Process::Current().handle(); + if (parent_pid != our_pid) { +#if defined(OS_MACOSX) + // On Mac we can wait on non child processes. + return WaitForSingleNonChildProcess(handle, wait); +#else + // Currently on Linux we can't handle non child processes. + NOTIMPLEMENTED(); +#endif // OS_MACOSX + } + + bool waitpid_success; + int status = -1; + if (wait.InMilliseconds() == base::kNoTimeout) { + waitpid_success = (HANDLE_EINTR(waitpid(handle, &status, 0)) != -1); + } else { + status = WaitpidWithTimeout( + handle, wait.InMilliseconds(), &waitpid_success); + } + + if (status != -1) { + DCHECK(waitpid_success); + return WIFEXITED(status); + } else { + return false; + } +} + +int64 TimeValToMicroseconds(const struct timeval& tv) { + static const int kMicrosecondsPerSecond = 1000000; + int64 ret = tv.tv_sec; // Avoid (int * int) integer overflow. + ret *= kMicrosecondsPerSecond; + ret += tv.tv_usec; + return ret; +} + +// Return value used by GetAppOutputInternal to encapsulate the various exit +// scenarios from the function. +enum GetAppOutputInternalResult { + EXECUTE_FAILURE, + EXECUTE_SUCCESS, + GOT_MAX_OUTPUT, +}; + +// Executes the application specified by |argv| and wait for it to exit. Stores +// the output (stdout) in |output|. If |do_search_path| is set, it searches the +// path for the application; in that case, |envp| must be null, and it will use +// the current environment. If |do_search_path| is false, |argv[0]| should fully +// specify the path of the application, and |envp| will be used as the +// environment. Redirects stderr to /dev/null. +// If we successfully start the application and get all requested output, we +// return GOT_MAX_OUTPUT, or if there is a problem starting or exiting +// the application we return RUN_FAILURE. Otherwise we return EXECUTE_SUCCESS. +// The GOT_MAX_OUTPUT return value exists so a caller that asks for limited +// output can treat this as a success, despite having an exit code of SIG_PIPE +// due to us closing the output pipe. +// In the case of EXECUTE_SUCCESS, the application exit code will be returned +// in |*exit_code|, which should be checked to determine if the application +// ran successfully. +static GetAppOutputInternalResult GetAppOutputInternal( + const std::vector<std::string>& argv, + char* const envp[], + std::string* output, + size_t max_output, + bool do_search_path, + int* exit_code) { + // Doing a blocking wait for another command to finish counts as IO. + base::ThreadRestrictions::AssertIOAllowed(); + // exit_code must be supplied so calling function can determine success. + DCHECK(exit_code); + *exit_code = EXIT_FAILURE; + + int pipe_fd[2]; + pid_t pid; + InjectiveMultimap fd_shuffle1, fd_shuffle2; + scoped_array<char*> argv_cstr(new char*[argv.size() + 1]); + + fd_shuffle1.reserve(3); + fd_shuffle2.reserve(3); + + // Either |do_search_path| should be false or |envp| should be null, but not + // both. + DCHECK(!do_search_path ^ !envp); + + if (pipe(pipe_fd) < 0) + return EXECUTE_FAILURE; + + switch (pid = fork()) { + case -1: // error + close(pipe_fd[0]); + close(pipe_fd[1]); + return EXECUTE_FAILURE; + case 0: // child + { +#if defined(OS_MACOSX) + RestoreDefaultExceptionHandler(); +#endif + // DANGER: no calls to malloc are allowed from now on: + // http://crbug.com/36678 + + // Obscure fork() rule: in the child, if you don't end up doing exec*(), + // you call _exit() instead of exit(). This is because _exit() does not + // call any previously-registered (in the parent) exit handlers, which + // might do things like block waiting for threads that don't even exist + // in the child. + int dev_null = open("/dev/null", O_WRONLY); + if (dev_null < 0) + _exit(127); + + // Stop type-profiler. + // The profiler should be stopped between fork and exec since it inserts + // locks at new/delete expressions. See http://crbug.com/36678. + base::type_profiler::Controller::Stop(); + + fd_shuffle1.push_back(InjectionArc(pipe_fd[1], STDOUT_FILENO, true)); + fd_shuffle1.push_back(InjectionArc(dev_null, STDERR_FILENO, true)); + fd_shuffle1.push_back(InjectionArc(dev_null, STDIN_FILENO, true)); + // Adding another element here? Remeber to increase the argument to + // reserve(), above. + + std::copy(fd_shuffle1.begin(), fd_shuffle1.end(), + std::back_inserter(fd_shuffle2)); + + if (!ShuffleFileDescriptors(&fd_shuffle1)) + _exit(127); + + CloseSuperfluousFds(fd_shuffle2); + + for (size_t i = 0; i < argv.size(); i++) + argv_cstr[i] = const_cast<char*>(argv[i].c_str()); + argv_cstr[argv.size()] = NULL; + if (do_search_path) + execvp(argv_cstr[0], argv_cstr.get()); + else + execve(argv_cstr[0], argv_cstr.get(), envp); + _exit(127); + } + default: // parent + { + // Close our writing end of pipe now. Otherwise later read would not + // be able to detect end of child's output (in theory we could still + // write to the pipe). + close(pipe_fd[1]); + + output->clear(); + char buffer[256]; + size_t output_buf_left = max_output; + ssize_t bytes_read = 1; // A lie to properly handle |max_output == 0| + // case in the logic below. + + while (output_buf_left > 0) { + bytes_read = HANDLE_EINTR(read(pipe_fd[0], buffer, + std::min(output_buf_left, sizeof(buffer)))); + if (bytes_read <= 0) + break; + output->append(buffer, bytes_read); + output_buf_left -= static_cast<size_t>(bytes_read); + } + close(pipe_fd[0]); + + // Always wait for exit code (even if we know we'll declare + // GOT_MAX_OUTPUT). + bool success = WaitForExitCode(pid, exit_code); + + // If we stopped because we read as much as we wanted, we return + // GOT_MAX_OUTPUT (because the child may exit due to |SIGPIPE|). + if (!output_buf_left && bytes_read > 0) + return GOT_MAX_OUTPUT; + else if (success) + return EXECUTE_SUCCESS; + return EXECUTE_FAILURE; + } + } +} + +bool GetAppOutput(const CommandLine& cl, std::string* output) { + return GetAppOutput(cl.argv(), output); +} + +bool GetAppOutput(const std::vector<std::string>& argv, std::string* output) { + // Run |execve()| with the current environment and store "unlimited" data. + int exit_code; + GetAppOutputInternalResult result = GetAppOutputInternal( + argv, NULL, output, std::numeric_limits<std::size_t>::max(), true, + &exit_code); + return result == EXECUTE_SUCCESS && exit_code == EXIT_SUCCESS; +} + +// TODO(viettrungluu): Conceivably, we should have a timeout as well, so we +// don't hang if what we're calling hangs. +bool GetAppOutputRestricted(const CommandLine& cl, + std::string* output, size_t max_output) { + // Run |execve()| with the empty environment. + char* const empty_environ = NULL; + int exit_code; + GetAppOutputInternalResult result = GetAppOutputInternal( + cl.argv(), &empty_environ, output, max_output, false, &exit_code); + return result == GOT_MAX_OUTPUT || (result == EXECUTE_SUCCESS && + exit_code == EXIT_SUCCESS); +} + +bool GetAppOutputWithExitCode(const CommandLine& cl, + std::string* output, + int* exit_code) { + // Run |execve()| with the current environment and store "unlimited" data. + GetAppOutputInternalResult result = GetAppOutputInternal( + cl.argv(), NULL, output, std::numeric_limits<std::size_t>::max(), true, + exit_code); + return result == EXECUTE_SUCCESS; +} + +bool WaitForProcessesToExit(const FilePath::StringType& executable_name, + base::TimeDelta wait, + const ProcessFilter* filter) { + bool result = false; + + // TODO(port): This is inefficient, but works if there are multiple procs. + // TODO(port): use waitpid to avoid leaving zombies around + + base::Time end_time = base::Time::Now() + wait; + do { + NamedProcessIterator iter(executable_name, filter); + if (!iter.NextProcessEntry()) { + result = true; + break; + } + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); + } while ((end_time - base::Time::Now()) > base::TimeDelta()); + + return result; +} + +bool CleanupProcesses(const FilePath::StringType& executable_name, + base::TimeDelta wait, + int exit_code, + const ProcessFilter* filter) { + bool exited_cleanly = WaitForProcessesToExit(executable_name, wait, filter); + if (!exited_cleanly) + KillProcesses(executable_name, exit_code, filter); + return exited_cleanly; +} + +#if !defined(OS_MACOSX) + +namespace { + +// Return true if the given child is dead. This will also reap the process. +// Doesn't block. +static bool IsChildDead(pid_t child) { + const pid_t result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG)); + if (result == -1) { + DPLOG(ERROR) << "waitpid(" << child << ")"; + NOTREACHED(); + } else if (result > 0) { + // The child has died. + return true; + } + + return false; +} + +// A thread class which waits for the given child to exit and reaps it. +// If the child doesn't exit within a couple of seconds, kill it. +class BackgroundReaper : public PlatformThread::Delegate { + public: + BackgroundReaper(pid_t child, unsigned timeout) + : child_(child), + timeout_(timeout) { + } + + // Overridden from PlatformThread::Delegate: + virtual void ThreadMain() OVERRIDE { + WaitForChildToDie(); + delete this; + } + + void WaitForChildToDie() { + // Wait forever case. + if (timeout_ == 0) { + pid_t r = HANDLE_EINTR(waitpid(child_, NULL, 0)); + if (r != child_) { + DPLOG(ERROR) << "While waiting for " << child_ + << " to terminate, we got the following result: " << r; + } + return; + } + + // There's no good way to wait for a specific child to exit in a timed + // fashion. (No kqueue on Linux), so we just loop and sleep. + + // Wait for 2 * timeout_ 500 milliseconds intervals. + for (unsigned i = 0; i < 2 * timeout_; ++i) { + PlatformThread::Sleep(TimeDelta::FromMilliseconds(500)); + if (IsChildDead(child_)) + return; + } + + if (kill(child_, SIGKILL) == 0) { + // SIGKILL is uncatchable. Since the signal was delivered, we can + // just wait for the process to die now in a blocking manner. + if (HANDLE_EINTR(waitpid(child_, NULL, 0)) < 0) + DPLOG(WARNING) << "waitpid"; + } else { + DLOG(ERROR) << "While waiting for " << child_ << " to terminate we" + << " failed to deliver a SIGKILL signal (" << errno << ")."; + } + } + + private: + const pid_t child_; + // Number of seconds to wait, if 0 then wait forever and do not attempt to + // kill |child_|. + const unsigned timeout_; + + DISALLOW_COPY_AND_ASSIGN(BackgroundReaper); +}; + +} // namespace + +void EnsureProcessTerminated(ProcessHandle process) { + // If the child is already dead, then there's nothing to do. + if (IsChildDead(process)) + return; + + const unsigned timeout = 2; // seconds + BackgroundReaper* reaper = new BackgroundReaper(process, timeout); + PlatformThread::CreateNonJoinable(0, reaper); +} + +void EnsureProcessGetsReaped(ProcessHandle process) { + // If the child is already dead, then there's nothing to do. + if (IsChildDead(process)) + return; + + BackgroundReaper* reaper = new BackgroundReaper(process, 0); + PlatformThread::CreateNonJoinable(0, reaper); +} + +#endif // !defined(OS_MACOSX) + +} // namespace base
diff --git a/src/base/process_util_starboard.cc b/src/base/process_util_starboard.cc new file mode 100644 index 0000000..0848acf --- /dev/null +++ b/src/base/process_util_starboard.cc
@@ -0,0 +1,35 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Starboard doesn't support the concept of processes, so this implementation +// just minimally and trivially implements the functions of this interface that +// are called, but that do not impact the functioning of the rest of the system. + +#include "base/process_util.h" + +#include "base/process.h" + +namespace base { + +ProcessHandle GetCurrentProcessHandle() { + return kNullProcessHandle; +} + +ProcessId GetCurrentProcId() { + return kNullProcessId; +} + +void EnableTerminationOnHeapCorruption() {} + +} // namespace base
diff --git a/src/base/process_util_unittest.cc b/src/base/process_util_unittest.cc new file mode 100644 index 0000000..4c1eff4 --- /dev/null +++ b/src/base/process_util_unittest.cc
@@ -0,0 +1,1220 @@ +// 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. + +#define _CRT_SECURE_NO_WARNINGS + +#include <limits> + +#include "base/command_line.h" +#include "base/debug/alias.h" +#include "base/debug/stack_trace.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process_util.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_timeouts.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/platform_thread.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +#if defined(OS_LINUX) +#include <malloc.h> +#include <glib.h> +#include <sched.h> +#endif +#if defined(OS_POSIX) +#include <errno.h> +#include <dlfcn.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/resource.h> +#include <sys/socket.h> +#include <sys/wait.h> +#endif +#if defined(OS_WIN) +#include <windows.h> +#endif +#if defined(OS_MACOSX) +#include <mach/vm_param.h> +#include <malloc/malloc.h> +#include "base/process_util_unittest_mac.h" +#endif + +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"; +#else +const char kShellPath[] = "/bin/sh"; +const char kPosixShell[] = "bash"; +#endif + +const char kSignalFileSlow[] = "SlowChildProcess.die"; +const char kSignalFileCrash[] = "CrashingChildProcess.die"; +const char kSignalFileKill[] = "KilledChildProcess.die"; + +#if defined(OS_WIN) +const int kExpectedStillRunningExitCode = 0x102; +const int kExpectedKilledExitCode = 1; +#else +const int kExpectedStillRunningExitCode = 0; +#endif + +#if defined(OS_WIN) +// HeapQueryInformation function pointer. +typedef BOOL (WINAPI* HeapQueryFn) \ + (HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T, PSIZE_T); +#endif + +// Sleeps until file filename is created. +void WaitToDie(const char* filename) { + FILE* fp; + do { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); + fp = fopen(filename, "r"); + } while (!fp); + fclose(fp); +} + +// Signals children they should die now. +void SignalChildren(const char* filename) { + FILE* fp = fopen(filename, "w"); + fclose(fp); +} + +// Using a pipe to the child to wait for an event was considered, but +// there were cases in the past where pipes caused problems (other +// libraries closing the fds, child deadlocking). This is a simple +// case, so it's not worth the risk. Using wait loops is discouraged +// in most instances. +base::TerminationStatus WaitForChildTermination(base::ProcessHandle handle, + int* exit_code) { + // Now we wait until the result is something other than STILL_RUNNING. + base::TerminationStatus status = base::TERMINATION_STATUS_STILL_RUNNING; + const base::TimeDelta kInterval = base::TimeDelta::FromMilliseconds(20); + base::TimeDelta waited; + do { + status = base::GetTerminationStatus(handle, exit_code); + base::PlatformThread::Sleep(kInterval); + waited += kInterval; + } while (status == base::TERMINATION_STATUS_STILL_RUNNING && +// Waiting for more time for process termination on android devices. +#if defined(OS_ANDROID) + waited < TestTimeouts::large_test_timeout()); +#else + waited < TestTimeouts::action_max_timeout()); +#endif + + return status; +} + +} // namespace + +class ProcessUtilTest : public base::MultiProcessTest { + public: +#if defined(OS_POSIX) + // Spawn a child process that counts how many file descriptors are open. + int CountOpenFDsInChild(); +#endif + // Converts the filename to a platform specific filepath. + // On Android files can not be created in arbitrary directories. + static std::string GetSignalFilePath(const char* filename); +}; + +std::string ProcessUtilTest::GetSignalFilePath(const char* filename) { +#if !defined(OS_ANDROID) + return filename; +#else + FilePath tmp_dir; + PathService::Get(base::DIR_CACHE, &tmp_dir); + tmp_dir = tmp_dir.Append(filename); + return tmp_dir.value(); +#endif +} + +MULTIPROCESS_TEST_MAIN(SimpleChildProcess) { + return 0; +} + +TEST_F(ProcessUtilTest, SpawnChild) { + base::ProcessHandle handle = this->SpawnChild("SimpleChildProcess", false); + ASSERT_NE(base::kNullProcessHandle, handle); + EXPECT_TRUE(base::WaitForSingleProcess( + handle, TestTimeouts::action_max_timeout())); + base::CloseProcessHandle(handle); +} + +MULTIPROCESS_TEST_MAIN(SlowChildProcess) { + WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileSlow).c_str()); + return 0; +} + +TEST_F(ProcessUtilTest, KillSlowChild) { + const std::string signal_file = + ProcessUtilTest::GetSignalFilePath(kSignalFileSlow); + remove(signal_file.c_str()); + base::ProcessHandle handle = this->SpawnChild("SlowChildProcess", false); + ASSERT_NE(base::kNullProcessHandle, handle); + SignalChildren(signal_file.c_str()); + EXPECT_TRUE(base::WaitForSingleProcess( + handle, TestTimeouts::action_max_timeout())); + base::CloseProcessHandle(handle); + remove(signal_file.c_str()); +} + +// Times out on Linux and Win, flakes on other platforms, http://crbug.com/95058 +TEST_F(ProcessUtilTest, DISABLED_GetTerminationStatusExit) { + const std::string signal_file = + ProcessUtilTest::GetSignalFilePath(kSignalFileSlow); + remove(signal_file.c_str()); + base::ProcessHandle handle = this->SpawnChild("SlowChildProcess", false); + ASSERT_NE(base::kNullProcessHandle, handle); + + int exit_code = 42; + EXPECT_EQ(base::TERMINATION_STATUS_STILL_RUNNING, + base::GetTerminationStatus(handle, &exit_code)); + EXPECT_EQ(kExpectedStillRunningExitCode, exit_code); + + SignalChildren(signal_file.c_str()); + exit_code = 42; + base::TerminationStatus status = + WaitForChildTermination(handle, &exit_code); + EXPECT_EQ(base::TERMINATION_STATUS_NORMAL_TERMINATION, status); + EXPECT_EQ(0, exit_code); + base::CloseProcessHandle(handle); + remove(signal_file.c_str()); +} + +#if defined(OS_WIN) +// TODO(cpu): figure out how to test this in other platforms. +TEST_F(ProcessUtilTest, GetProcId) { + base::ProcessId id1 = base::GetProcId(GetCurrentProcess()); + EXPECT_NE(0ul, id1); + base::ProcessHandle handle = this->SpawnChild("SimpleChildProcess", false); + ASSERT_NE(base::kNullProcessHandle, handle); + base::ProcessId id2 = base::GetProcId(handle); + EXPECT_NE(0ul, id2); + EXPECT_NE(id1, id2); + base::CloseProcessHandle(handle); +} + +TEST_F(ProcessUtilTest, GetModuleFromAddress) { + // Since the unit tests are their own EXE, this should be + // equivalent to the EXE's HINSTANCE. + // + // kExpectedKilledExitCode is a constant in this file and + // therefore within the unit test EXE. + EXPECT_EQ(::GetModuleHandle(NULL), + base::GetModuleFromAddress( + const_cast<int*>(&kExpectedKilledExitCode))); + + // Any address within the kernel32 module should return + // kernel32's HMODULE. Our only assumption here is that + // kernel32 is larger than 4 bytes. + HMODULE kernel32 = ::GetModuleHandle(L"kernel32.dll"); + HMODULE kernel32_from_address = + base::GetModuleFromAddress(reinterpret_cast<DWORD*>(kernel32) + 1); + EXPECT_EQ(kernel32, kernel32_from_address); +} +#endif + +#if !defined(OS_MACOSX) +// This test is disabled on Mac, since it's flaky due to ReportCrash +// taking a variable amount of time to parse and load the debug and +// symbol data for this unit test's executable before firing the +// signal handler. +// +// 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. + +MULTIPROCESS_TEST_MAIN(CrashingChildProcess) { + WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileCrash).c_str()); +#if defined(OS_POSIX) + // Have to disable to signal handler for segv so we can get a crash + // instead of an abnormal termination through the crash dump handler. + ::signal(SIGSEGV, SIG_DFL); +#endif + // Make this process have a segmentation fault. + volatile int* oops = NULL; + *oops = 0xDEAD; + return 1; +} + +// This test intentionally crashes, so we don't need to run it under +// AddressSanitizer. +#if defined(ADDRESS_SANITIZER) +#define MAYBE_GetTerminationStatusCrash DISABLED_GetTerminationStatusCrash +#else +#define MAYBE_GetTerminationStatusCrash GetTerminationStatusCrash +#endif +TEST_F(ProcessUtilTest, MAYBE_GetTerminationStatusCrash) { + const std::string signal_file = + ProcessUtilTest::GetSignalFilePath(kSignalFileCrash); + remove(signal_file.c_str()); + base::ProcessHandle handle = this->SpawnChild("CrashingChildProcess", + false); + ASSERT_NE(base::kNullProcessHandle, handle); + + int exit_code = 42; + EXPECT_EQ(base::TERMINATION_STATUS_STILL_RUNNING, + base::GetTerminationStatus(handle, &exit_code)); + EXPECT_EQ(kExpectedStillRunningExitCode, exit_code); + + SignalChildren(signal_file.c_str()); + exit_code = 42; + base::TerminationStatus status = + WaitForChildTermination(handle, &exit_code); + EXPECT_EQ(base::TERMINATION_STATUS_PROCESS_CRASHED, status); + +#if defined(OS_WIN) + EXPECT_EQ(0xc0000005, exit_code); +#elif defined(OS_POSIX) + int signaled = WIFSIGNALED(exit_code); + EXPECT_NE(0, signaled); + int signal = WTERMSIG(exit_code); + EXPECT_EQ(SIGSEGV, signal); +#endif + base::CloseProcessHandle(handle); + + // Reset signal handlers back to "normal". + base::debug::EnableInProcessStackDumping(); + remove(signal_file.c_str()); +} +#endif // !defined(OS_MACOSX) + +MULTIPROCESS_TEST_MAIN(KilledChildProcess) { + WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileKill).c_str()); +#if defined(OS_WIN) + // Kill ourselves. + HANDLE handle = ::OpenProcess(PROCESS_ALL_ACCESS, 0, ::GetCurrentProcessId()); + ::TerminateProcess(handle, kExpectedKilledExitCode); +#elif defined(OS_POSIX) + // Send a SIGKILL to this process, just like the OOM killer would. + ::kill(getpid(), SIGKILL); +#endif + return 1; +} + +TEST_F(ProcessUtilTest, GetTerminationStatusKill) { + const std::string signal_file = + ProcessUtilTest::GetSignalFilePath(kSignalFileKill); + remove(signal_file.c_str()); + base::ProcessHandle handle = this->SpawnChild("KilledChildProcess", + false); + ASSERT_NE(base::kNullProcessHandle, handle); + + int exit_code = 42; + EXPECT_EQ(base::TERMINATION_STATUS_STILL_RUNNING, + base::GetTerminationStatus(handle, &exit_code)); + EXPECT_EQ(kExpectedStillRunningExitCode, exit_code); + + SignalChildren(signal_file.c_str()); + exit_code = 42; + base::TerminationStatus status = + WaitForChildTermination(handle, &exit_code); + EXPECT_EQ(base::TERMINATION_STATUS_PROCESS_WAS_KILLED, status); +#if defined(OS_WIN) + EXPECT_EQ(kExpectedKilledExitCode, exit_code); +#elif defined(OS_POSIX) + int signaled = WIFSIGNALED(exit_code); + EXPECT_NE(0, signaled); + int signal = WTERMSIG(exit_code); + EXPECT_EQ(SIGKILL, signal); +#endif + base::CloseProcessHandle(handle); + remove(signal_file.c_str()); +} + +// Ensure that the priority of a process is restored correctly after +// backgrounding and restoring. +// Note: a platform may not be willing or able to lower the priority of +// a process. The calls to SetProcessBackground should be noops then. +TEST_F(ProcessUtilTest, SetProcessBackgrounded) { + base::ProcessHandle handle = this->SpawnChild("SimpleChildProcess", false); + base::Process process(handle); + int old_priority = process.GetPriority(); +#if defined(OS_WIN) + EXPECT_TRUE(process.SetProcessBackgrounded(true)); + EXPECT_TRUE(process.IsProcessBackgrounded()); + EXPECT_TRUE(process.SetProcessBackgrounded(false)); + EXPECT_FALSE(process.IsProcessBackgrounded()); +#else + process.SetProcessBackgrounded(true); + process.SetProcessBackgrounded(false); +#endif + int new_priority = process.GetPriority(); + EXPECT_EQ(old_priority, new_priority); +} + +// Same as SetProcessBackgrounded but to this very process. It uses +// a different code path at least for Windows. +TEST_F(ProcessUtilTest, SetProcessBackgroundedSelf) { + base::Process process(base::Process::Current().handle()); + int old_priority = process.GetPriority(); +#if defined(OS_WIN) + EXPECT_TRUE(process.SetProcessBackgrounded(true)); + EXPECT_TRUE(process.IsProcessBackgrounded()); + EXPECT_TRUE(process.SetProcessBackgrounded(false)); + EXPECT_FALSE(process.IsProcessBackgrounded()); +#else + process.SetProcessBackgrounded(true); + process.SetProcessBackgrounded(false); +#endif + int new_priority = process.GetPriority(); + EXPECT_EQ(old_priority, new_priority); +} + +// TODO(estade): if possible, port these 2 tests. +#if defined(OS_WIN) +TEST_F(ProcessUtilTest, EnableLFH) { + ASSERT_TRUE(base::EnableLowFragmentationHeap()); + if (IsDebuggerPresent()) { + // Under these conditions, LFH can't be enabled. There's no point to test + // anything. + const char* no_debug_env = getenv("_NO_DEBUG_HEAP"); + if (!no_debug_env || strcmp(no_debug_env, "1")) + return; + } + HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); + ASSERT_TRUE(kernel32 != NULL); + HeapQueryFn heap_query = reinterpret_cast<HeapQueryFn>(GetProcAddress( + kernel32, + "HeapQueryInformation")); + + // On Windows 2000, the function is not exported. This is not a reason to + // fail but we won't be able to retrieves information about the heap, so we + // should stop here. + if (heap_query == NULL) + return; + + HANDLE heaps[1024] = { 0 }; + unsigned number_heaps = GetProcessHeaps(1024, heaps); + EXPECT_GT(number_heaps, 0u); + for (unsigned i = 0; i < number_heaps; ++i) { + ULONG flag = 0; + SIZE_T length; + ASSERT_NE(0, heap_query(heaps[i], + HeapCompatibilityInformation, + &flag, + sizeof(flag), + &length)); + // If flag is 0, the heap is a standard heap that does not support + // look-asides. If flag is 1, the heap supports look-asides. If flag is 2, + // the heap is a low-fragmentation heap (LFH). Note that look-asides are not + // supported on the LFH. + + // We don't have any documented way of querying the HEAP_NO_SERIALIZE flag. + EXPECT_LE(flag, 2u); + EXPECT_NE(flag, 1u); + } +} + +TEST_F(ProcessUtilTest, CalcFreeMemory) { + scoped_ptr<base::ProcessMetrics> metrics( + base::ProcessMetrics::CreateProcessMetrics(::GetCurrentProcess())); + ASSERT_TRUE(NULL != metrics.get()); + + // 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_array<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); + EXPECT_GE(expected_total, free_mem2.total); + EXPECT_GE(expected_largest, free_mem2.largest); + EXPECT_TRUE(NULL != free_mem2.largest_ptr); +} + +TEST_F(ProcessUtilTest, GetAppOutput) { + // Let's create a decently long message. + std::string message; + for (int i = 0; i < 1025; i++) { // 1025 so it does not end on a kilo-byte + // boundary. + message += "Hello!"; + } + // cmd.exe's echo always adds a \r\n to its output. + std::string expected(message); + expected += "\r\n"; + + FilePath cmd(L"cmd.exe"); + CommandLine cmd_line(cmd); + cmd_line.AppendArg("/c"); + cmd_line.AppendArg("echo " + message + ""); + std::string output; + ASSERT_TRUE(base::GetAppOutput(cmd_line, &output)); + EXPECT_EQ(expected, output); + + // Let's make sure stderr is ignored. + CommandLine other_cmd_line(cmd); + other_cmd_line.AppendArg("/c"); + // http://msdn.microsoft.com/library/cc772622.aspx + cmd_line.AppendArg("echo " + message + " >&2"); + output.clear(); + ASSERT_TRUE(base::GetAppOutput(other_cmd_line, &output)); + EXPECT_EQ("", output); +} + +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)); +} + +#endif // defined(OS_WIN) + +#if defined(OS_MACOSX) + +// For the following Mac tests: +// Note that base::EnableTerminationOnHeapCorruption() is called as part of +// test suite setup and does not need to be done again, else mach_override +// will fail. + +#if !defined(ADDRESS_SANITIZER) +// The following code tests the system implementation of malloc() thus no need +// to test it under AddressSanitizer. +TEST_F(ProcessUtilTest, MacMallocFailureDoesNotTerminate) { + // Install the OOM killer. + base::EnableTerminationOnOutOfMemory(); + + // Test that ENOMEM doesn't crash via CrMallocErrorBreak two ways: the exit + // code and lack of the error string. The number of bytes is one less than + // MALLOC_ABSOLUTE_MAX_SIZE, more than which the system early-returns NULL and + // does not call through malloc_error_break(). See the comment at + // EnableTerminationOnOutOfMemory() for more information. + void* buf = NULL; + ASSERT_EXIT( + buf = malloc(std::numeric_limits<size_t>::max() - (2 * PAGE_SIZE) - 1), + testing::KilledBySignal(SIGTRAP), + "\\*\\*\\* error: can't allocate region.*" + "(Terminating process due to a potential for future heap " + "corruption){0}"); + + base::debug::Alias(buf); +} +#endif // !defined(ADDRESS_SANITIZER) + +TEST_F(ProcessUtilTest, MacTerminateOnHeapCorruption) { + // Assert that freeing an unallocated pointer will crash the process. + char buf[3]; + asm("" : "=r" (buf)); // Prevent clang from being too smart. +#if !defined(ADDRESS_SANITIZER) + ASSERT_DEATH(free(buf), "being freed.*" + "\\*\\*\\* set a breakpoint in malloc_error_break to debug.*" + "Terminating process due to a potential for future heap corruption"); +#else + // AddressSanitizer replaces malloc() and prints a different error message on + // heap corruption. + ASSERT_DEATH(free(buf), "attempting free on address which " + "was not malloc\\(\\)-ed"); +#endif // !defined(ADDRESS_SANITIZER) +} + +#endif // defined(OS_MACOSX) + +#if defined(OS_POSIX) + +namespace { + +// Returns the maximum number of files that a process can have open. +// Returns 0 on error. +int GetMaxFilesOpenInProcess() { + struct rlimit rlim; + if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) { + return 0; + } + + // rlim_t is a uint64 - clip to maxint. We do this since FD #s are ints + // which are all 32 bits on the supported platforms. + rlim_t max_int = static_cast<rlim_t>(std::numeric_limits<int32>::max()); + if (rlim.rlim_cur > max_int) { + return max_int; + } + + return rlim.rlim_cur; +} + +const int kChildPipe = 20; // FD # for write end of pipe in child process. + +} // namespace + +MULTIPROCESS_TEST_MAIN(ProcessUtilsLeakFDChildProcess) { + // This child process counts the number of open FDs, it then writes that + // number out to a pipe connected to the parent. + int num_open_files = 0; + int write_pipe = kChildPipe; + int max_files = GetMaxFilesOpenInProcess(); + for (int i = STDERR_FILENO + 1; i < max_files; i++) { + if (i != kChildPipe) { + int fd; + if ((fd = HANDLE_EINTR(dup(i))) != -1) { + close(fd); + num_open_files += 1; + } + } + } + + 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)); + DPCHECK(ret == 0); + + return 0; +} + +int ProcessUtilTest::CountOpenFDsInChild() { + int fds[2]; + if (pipe(fds) < 0) + NOTREACHED(); + + base::FileHandleMappingVector fd_mapping_vec; + fd_mapping_vec.push_back(std::pair<int, int>(fds[1], kChildPipe)); + base::ProcessHandle handle = this->SpawnChild( + "ProcessUtilsLeakFDChildProcess", fd_mapping_vec, false); + CHECK(handle); + int ret = HANDLE_EINTR(close(fds[1])); + DPCHECK(ret == 0); + + // Read number of open files in client process from pipe; + int num_open_files = -1; + ssize_t bytes_read = + HANDLE_EINTR(read(fds[0], &num_open_files, sizeof(num_open_files))); + CHECK_EQ(bytes_read, static_cast<ssize_t>(sizeof(num_open_files))); + +#if defined(THREAD_SANITIZER) + // Compiler-based ThreadSanitizer makes this test slow. + CHECK(base::WaitForSingleProcess(handle, base::TimeDelta::FromSeconds(3))); +#else + CHECK(base::WaitForSingleProcess(handle, base::TimeDelta::FromSeconds(1))); +#endif + base::CloseProcessHandle(handle); + ret = HANDLE_EINTR(close(fds[0])); + DPCHECK(ret == 0); + + return num_open_files; +} + +TEST_F(ProcessUtilTest, FDRemapping) { + int fds_before = CountOpenFDsInChild(); + + // open some dummy fds to make sure they don't propagate over to the + // child process. + int dev_null = open("/dev/null", O_RDONLY); + int sockets[2]; + socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); + + int fds_after = CountOpenFDsInChild(); + + ASSERT_EQ(fds_after, fds_before); + + int ret; + ret = HANDLE_EINTR(close(sockets[0])); + DPCHECK(ret == 0); + ret = HANDLE_EINTR(close(sockets[1])); + DPCHECK(ret == 0); + ret = HANDLE_EINTR(close(dev_null)); + DPCHECK(ret == 0); +} + +namespace { + +std::string TestLaunchProcess(const base::EnvironmentVector& env_changes, + const int clone_flags) { + std::vector<std::string> args; + base::FileHandleMappingVector fds_to_remap; + + args.push_back(kPosixShell); + args.push_back("-c"); + args.push_back("echo $BASE_TEST"); + + int fds[2]; + PCHECK(pipe(fds) == 0); + + fds_to_remap.push_back(std::make_pair(fds[1], 1)); + base::LaunchOptions options; + options.wait = true; + options.environ = &env_changes; + options.fds_to_remap = &fds_to_remap; +#if defined(OS_LINUX) + options.clone_flags = clone_flags; +#else + CHECK_EQ(0, clone_flags); +#endif // OS_LINUX + EXPECT_TRUE(base::LaunchProcess(args, options, NULL)); + PCHECK(HANDLE_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); + + return std::string(buf, n); +} + +const char kLargeString[] = + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789012345678901234567890123456789"; + +} // namespace + +TEST_F(ProcessUtilTest, LaunchProcess) { + base::EnvironmentVector env_changes; + const int no_clone_flags = 0; + + env_changes.push_back(std::make_pair(std::string("BASE_TEST"), + std::string("bar"))); + EXPECT_EQ("bar\n", TestLaunchProcess(env_changes, no_clone_flags)); + env_changes.clear(); + + EXPECT_EQ(0, setenv("BASE_TEST", "testing", 1 /* override */)); + EXPECT_EQ("testing\n", TestLaunchProcess(env_changes, no_clone_flags)); + + env_changes.push_back(std::make_pair(std::string("BASE_TEST"), + std::string(""))); + EXPECT_EQ("\n", TestLaunchProcess(env_changes, no_clone_flags)); + + env_changes[0].second = "foo"; + EXPECT_EQ("foo\n", TestLaunchProcess(env_changes, no_clone_flags)); + + env_changes.clear(); + EXPECT_EQ(0, setenv("BASE_TEST", kLargeString, 1 /* override */)); + EXPECT_EQ(std::string(kLargeString) + "\n", + TestLaunchProcess(env_changes, no_clone_flags)); + + env_changes.push_back(std::make_pair(std::string("BASE_TEST"), + std::string("wibble"))); + EXPECT_EQ("wibble\n", TestLaunchProcess(env_changes, no_clone_flags)); + +#if defined(OS_LINUX) + // Test a non-trival value for clone_flags. + // Don't test on Valgrind as it has limited support for clone(). + if (!RunningOnValgrind()) { + EXPECT_EQ("wibble\n", TestLaunchProcess(env_changes, CLONE_FS | SIGCHLD)); + } +#endif +} + +TEST_F(ProcessUtilTest, AlterEnvironment) { + const char* const empty[] = { NULL }; + const char* const a2[] = { "A=2", NULL }; + base::EnvironmentVector changes; + char** e; + + e = base::AlterEnvironment(changes, empty); + EXPECT_TRUE(e[0] == NULL); + delete[] e; + + changes.push_back(std::make_pair(std::string("A"), std::string("1"))); + e = base::AlterEnvironment(changes, empty); + EXPECT_EQ(std::string("A=1"), e[0]); + EXPECT_TRUE(e[1] == NULL); + delete[] e; + + changes.clear(); + changes.push_back(std::make_pair(std::string("A"), std::string(""))); + e = base::AlterEnvironment(changes, empty); + EXPECT_TRUE(e[0] == NULL); + delete[] e; + + changes.clear(); + e = base::AlterEnvironment(changes, a2); + EXPECT_EQ(std::string("A=2"), e[0]); + EXPECT_TRUE(e[1] == NULL); + delete[] e; + + changes.clear(); + changes.push_back(std::make_pair(std::string("A"), std::string("1"))); + e = base::AlterEnvironment(changes, a2); + EXPECT_EQ(std::string("A=1"), e[0]); + EXPECT_TRUE(e[1] == NULL); + delete[] e; + + changes.clear(); + changes.push_back(std::make_pair(std::string("A"), std::string(""))); + e = base::AlterEnvironment(changes, a2); + EXPECT_TRUE(e[0] == NULL); + delete[] e; +} + +TEST_F(ProcessUtilTest, GetAppOutput) { + std::string output; + +#if defined(OS_ANDROID) + std::vector<std::string> argv; + argv.push_back("sh"); // Instead of /bin/sh, force path search to find it. + argv.push_back("-c"); + + argv.push_back("exit 0"); + EXPECT_TRUE(base::GetAppOutput(CommandLine(argv), &output)); + EXPECT_STREQ("", output.c_str()); + + argv[2] = "exit 1"; + EXPECT_FALSE(base::GetAppOutput(CommandLine(argv), &output)); + EXPECT_STREQ("", output.c_str()); + + argv[2] = "echo foobar42"; + EXPECT_TRUE(base::GetAppOutput(CommandLine(argv), &output)); + EXPECT_STREQ("foobar42\n", output.c_str()); +#else + EXPECT_TRUE(base::GetAppOutput(CommandLine(FilePath("true")), &output)); + EXPECT_STREQ("", output.c_str()); + + EXPECT_FALSE(base::GetAppOutput(CommandLine(FilePath("false")), &output)); + + std::vector<std::string> argv; + argv.push_back("/bin/echo"); + argv.push_back("-n"); + argv.push_back("foobar42"); + EXPECT_TRUE(base::GetAppOutput(CommandLine(argv), &output)); + EXPECT_STREQ("foobar42", output.c_str()); +#endif // defined(OS_ANDROID) +} + +TEST_F(ProcessUtilTest, GetAppOutputRestricted) { + // Unfortunately, since we can't rely on the path, we need to know where + // everything is. So let's use /bin/sh, which is on every POSIX system, and + // its built-ins. + std::vector<std::string> argv; + argv.push_back(std::string(kShellPath)); // argv[0] + argv.push_back("-c"); // argv[1] + + // On success, should set |output|. We use |/bin/sh -c 'exit 0'| instead of + // |true| since the location of the latter may be |/bin| or |/usr/bin| (and we + // need absolute paths). + argv.push_back("exit 0"); // argv[2]; equivalent to "true" + std::string output = "abc"; + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 100)); + EXPECT_STREQ("", output.c_str()); + + argv[2] = "exit 1"; // equivalent to "false" + output = "before"; + EXPECT_FALSE(base::GetAppOutputRestricted(CommandLine(argv), + &output, 100)); + EXPECT_STREQ("", output.c_str()); + + // Amount of output exactly equal to space allowed. + argv[2] = "echo 123456789"; // (the sh built-in doesn't take "-n") + output.clear(); + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 10)); + EXPECT_STREQ("123456789\n", output.c_str()); + + // Amount of output greater than space allowed. + output.clear(); + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 5)); + EXPECT_STREQ("12345", output.c_str()); + + // Amount of output less than space allowed. + output.clear(); + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 15)); + EXPECT_STREQ("123456789\n", output.c_str()); + + // Zero space allowed. + output = "abc"; + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 0)); + EXPECT_STREQ("", output.c_str()); +} + +#if !defined(OS_MACOSX) && !defined(OS_OPENBSD) +// TODO(benwells): GetAppOutputRestricted should terminate applications +// with SIGPIPE when we have enough output. http://crbug.com/88502 +TEST_F(ProcessUtilTest, GetAppOutputRestrictedSIGPIPE) { + std::vector<std::string> argv; + std::string output; + + argv.push_back(std::string(kShellPath)); // argv[0] + argv.push_back("-c"); +#if defined(OS_ANDROID) + argv.push_back("while echo 12345678901234567890; do :; done"); + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 10)); + EXPECT_STREQ("1234567890", output.c_str()); +#else + argv.push_back("yes"); + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 10)); + EXPECT_STREQ("y\ny\ny\ny\ny\n", output.c_str()); +#endif +} +#endif + +TEST_F(ProcessUtilTest, GetAppOutputRestrictedNoZombies) { + std::vector<std::string> argv; + + argv.push_back(std::string(kShellPath)); // argv[0] + argv.push_back("-c"); // argv[1] + argv.push_back("echo 123456789012345678901234567890"); // argv[2] + + // Run |GetAppOutputRestricted()| 300 (> default per-user processes on Mac OS + // 10.5) times with an output buffer big enough to capture all output. + for (int i = 0; i < 300; i++) { + std::string output; + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 100)); + EXPECT_STREQ("123456789012345678901234567890\n", output.c_str()); + } + + // Ditto, but with an output buffer too small to capture all output. + for (int i = 0; i < 300; i++) { + std::string output; + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 10)); + EXPECT_STREQ("1234567890", output.c_str()); + } +} + +TEST_F(ProcessUtilTest, GetAppOutputWithExitCode) { + // Test getting output from a successful application. + std::vector<std::string> argv; + std::string output; + int exit_code; + argv.push_back(std::string(kShellPath)); // argv[0] + argv.push_back("-c"); // argv[1] + argv.push_back("echo foo"); // argv[2]; + EXPECT_TRUE(base::GetAppOutputWithExitCode(CommandLine(argv), &output, + &exit_code)); + EXPECT_STREQ("foo\n", output.c_str()); + EXPECT_EQ(exit_code, 0); + + // Test getting output from an application which fails with a specific exit + // code. + output.clear(); + argv[2] = "echo foo; exit 2"; + EXPECT_TRUE(base::GetAppOutputWithExitCode(CommandLine(argv), &output, + &exit_code)); + EXPECT_STREQ("foo\n", output.c_str()); + EXPECT_EQ(exit_code, 2); +} + +TEST_F(ProcessUtilTest, GetParentProcessId) { + base::ProcessId ppid = base::GetParentProcessId(base::GetCurrentProcId()); + 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)); +} +#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 + // want to test for. The good thing is that if it can't find the process + // we'll get a nice value for errno which we can test for. + const pid_t result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG)); + return result == -1 && errno == ECHILD; +} + +TEST_F(ProcessUtilTest, DelayedTermination) { + base::ProcessHandle child_process = + SpawnChild("process_util_test_never_die", false); + ASSERT_TRUE(child_process); + base::EnsureProcessTerminated(child_process); + base::WaitForSingleProcess(child_process, base::TimeDelta::FromSeconds(5)); + + // Check that process was really killed. + EXPECT_TRUE(IsProcessDead(child_process)); + base::CloseProcessHandle(child_process); +} + +MULTIPROCESS_TEST_MAIN(process_util_test_never_die) { + while (1) { + sleep(500); + } + return 0; +} + +TEST_F(ProcessUtilTest, ImmediateTermination) { + base::ProcessHandle child_process = + SpawnChild("process_util_test_die_immediately", false); + ASSERT_TRUE(child_process); + // Give it time to die. + sleep(2); + base::EnsureProcessTerminated(child_process); + + // Check that process was really killed. + EXPECT_TRUE(IsProcessDead(child_process)); + base::CloseProcessHandle(child_process); +} + +MULTIPROCESS_TEST_MAIN(process_util_test_die_immediately) { + return 0; +} + +#endif // defined(OS_POSIX) + +// Android doesn't implement set_new_handler, so we can't use the +// OutOfMemoryTest cases. +// OpenBSD does not support these tests either. +// AddressSanitizer defines the malloc()/free()/etc. functions so that they +// don't crash if the program is out of memory, so the OOM tests aren't supposed +// to work. +// TODO(vandebo) make this work on Windows too. +#if !defined(OS_ANDROID) && !defined(OS_OPENBSD) && \ + !defined(OS_WIN) && !defined(ADDRESS_SANITIZER) + +#if defined(USE_TCMALLOC) +extern "C" { +int tc_set_new_mode(int mode); +} +#endif // defined(USE_TCMALLOC) + +class OutOfMemoryDeathTest : public testing::Test { + public: + OutOfMemoryDeathTest() + : value_(NULL), + // Make test size as large as possible minus a few pages so + // that alignment or other rounding doesn't make it wrap. + test_size_(std::numeric_limits<std::size_t>::max() - 12 * 1024), + signed_test_size_(std::numeric_limits<ssize_t>::max()) { + } + +#if defined(USE_TCMALLOC) + virtual void SetUp() OVERRIDE { + tc_set_new_mode(1); + } + + virtual void TearDown() OVERRIDE { + tc_set_new_mode(0); + } +#endif // defined(USE_TCMALLOC) + + void SetUpInDeathAssert() { + // Must call EnableTerminationOnOutOfMemory() because that is called from + // chrome's main function and therefore hasn't been called yet. + // Since this call may result in another thread being created and death + // tests shouldn't be started in a multithread environment, this call + // should be done inside of the ASSERT_DEATH. + base::EnableTerminationOnOutOfMemory(); + } + + void* value_; + size_t test_size_; + ssize_t signed_test_size_; +}; + +TEST_F(OutOfMemoryDeathTest, New) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = operator new(test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, NewArray) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = new char[test_size_]; + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Malloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc(test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Realloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = realloc(NULL, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Calloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = calloc(1024, test_size_ / 1024L); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Valloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = valloc(test_size_); + }, ""); +} + +#if defined(OS_LINUX) +TEST_F(OutOfMemoryDeathTest, Pvalloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = pvalloc(test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Memalign) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = memalign(4, test_size_); + }, ""); +} + +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. + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = g_try_malloc(test_size_); + }, ""); +} +#endif // OS_LINUX + +// Android doesn't implement posix_memalign(). +#if defined(OS_POSIX) && !defined(OS_ANDROID) +TEST_F(OutOfMemoryDeathTest, Posix_memalign) { + // Grab the return value of posix_memalign to silence a compiler warning + // about unused return values. We don't actually care about the return + // value, since we're asserting death. + ASSERT_DEATH({ + SetUpInDeathAssert(); + EXPECT_EQ(ENOMEM, posix_memalign(&value_, 8, test_size_)); + }, ""); +} +#endif // defined(OS_POSIX) && !defined(OS_ANDROID) + +#if defined(OS_MACOSX) + +// Purgeable zone tests + +TEST_F(OutOfMemoryDeathTest, MallocPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_malloc(zone, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, ReallocPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_realloc(zone, NULL, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, CallocPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_calloc(zone, 1024, test_size_ / 1024L); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, VallocPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_valloc(zone, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, PosixMemalignPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_memalign(zone, 8, test_size_); + }, ""); +} + +// Since these allocation functions take a signed size, it's possible that +// calling them just once won't be enough to exhaust memory. In the 32-bit +// environment, it's likely that these allocation attempts will fail because +// not enough contiguous address space is available. In the 64-bit environment, +// it's likely that they'll fail because they would require a preposterous +// amount of (virtual) memory. + +TEST_F(OutOfMemoryDeathTest, CFAllocatorSystemDefault) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + while ((value_ = + base::AllocateViaCFAllocatorSystemDefault(signed_test_size_))) {} + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, CFAllocatorMalloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + while ((value_ = + base::AllocateViaCFAllocatorMalloc(signed_test_size_))) {} + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, CFAllocatorMallocZone) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + while ((value_ = + base::AllocateViaCFAllocatorMallocZone(signed_test_size_))) {} + }, ""); +} + +#if !defined(ARCH_CPU_64_BITS) + +// See process_util_unittest_mac.mm for an explanation of why this test isn't +// run in the 64-bit environment. + +TEST_F(OutOfMemoryDeathTest, PsychoticallyBigObjCObject) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + while ((value_ = base::AllocatePsychoticallyBigObjCObject())) {} + }, ""); +} + +#endif // !ARCH_CPU_64_BITS +#endif // OS_MACOSX + +#endif // !defined(OS_ANDROID) && !defined(OS_OPENBSD) && + // !defined(OS_WIN) && !defined(ADDRESS_SANITIZER)
diff --git a/src/base/process_util_unittest_ios.cc b/src/base/process_util_unittest_ios.cc new file mode 100644 index 0000000..c82535d --- /dev/null +++ b/src/base/process_util_unittest_ios.cc
@@ -0,0 +1,15 @@ +// 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/memory/scoped_ptr.h" +#include "base/process_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(ProcessUtilTestIos, Memory) { + scoped_ptr<base::ProcessMetrics> process_metrics( + base::ProcessMetrics::CreateProcessMetrics( + base::GetCurrentProcessHandle())); + + ASSERT_NE(0u, process_metrics->GetWorkingSetSize()); +}
diff --git a/src/base/process_util_unittest_mac.h b/src/base/process_util_unittest_mac.h new file mode 100644 index 0000000..7b4fe1c --- /dev/null +++ b/src/base/process_util_unittest_mac.h
@@ -0,0 +1,32 @@ +// Copyright (c) 2010 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 contains helpers for the process_util_unittest to allow it to fully +// test the Mac code. + +#ifndef BASE_PROCESS_UTIL_UNITTEST_MAC_H_ +#define BASE_PROCESS_UTIL_UNITTEST_MAC_H_ + +#include "base/basictypes.h" + +namespace base { + +// Allocates memory via system allocators. Alas, they take a _signed_ size for +// allocation. +void* AllocateViaCFAllocatorSystemDefault(ssize_t size); +void* AllocateViaCFAllocatorMalloc(ssize_t size); +void* AllocateViaCFAllocatorMallocZone(ssize_t size); + +#if !defined(ARCH_CPU_64_BITS) +// See process_util_unittest_mac.mm for an explanation of why this function +// isn't implemented for the 64-bit environment. + +// Allocates a huge Objective C object. +void* AllocatePsychoticallyBigObjCObject(); + +#endif // !ARCH_CPU_64_BITS + +} // namespace base + +#endif // BASE_PROCESS_UTIL_UNITTEST_MAC_H_
diff --git a/src/base/process_util_unittest_mac.mm b/src/base/process_util_unittest_mac.mm new file mode 100644 index 0000000..2ef1868 --- /dev/null +++ b/src/base/process_util_unittest_mac.mm
@@ -0,0 +1,59 @@ +// Copyright (c) 2010 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_util_unittest_mac.h" + +#import <Foundation/Foundation.h> +#include <CoreFoundation/CoreFoundation.h> + +#if !defined(ARCH_CPU_64_BITS) + +// In the 64-bit environment, the Objective-C 2.0 Runtime Reference states +// that sizeof(anInstance) is constrained to 32 bits. That's not necessarily +// "psychotically big" and in fact a 64-bit program is expected to be able to +// successfully allocate an object that large, likely reserving a good deal of +// swap space. The only way to test the behavior of memory exhaustion for +// Objective-C allocation in this environment would be to loop over allocation +// of these large objects, but that would slowly consume all available memory +// and cause swap file proliferation. That's bad, so this behavior isn't +// tested in the 64-bit environment. + +@interface PsychoticallyBigObjCObject : NSObject +{ + // In the 32-bit environment, the compiler limits Objective-C objects to + // < 2GB in size. + int justUnder2Gigs_[(2U * 1024 * 1024 * 1024 - 1) / sizeof(int)]; +} + +@end + +@implementation PsychoticallyBigObjCObject + +@end + +namespace base { + +void* AllocatePsychoticallyBigObjCObject() { + return [[PsychoticallyBigObjCObject alloc] init]; +} + +} // namespace base + +#endif // ARCH_CPU_64_BITS + +namespace base { + +void* AllocateViaCFAllocatorSystemDefault(ssize_t size) { + return CFAllocatorAllocate(kCFAllocatorSystemDefault, size, 0); +} + +void* AllocateViaCFAllocatorMalloc(ssize_t size) { + return CFAllocatorAllocate(kCFAllocatorMalloc, size, 0); +} + +void* AllocateViaCFAllocatorMallocZone(ssize_t size) { + return CFAllocatorAllocate(kCFAllocatorMallocZone, size, 0); +} + +} // namespace base
diff --git a/src/base/process_util_win.cc b/src/base/process_util_win.cc new file mode 100644 index 0000000..a1a55ea --- /dev/null +++ b/src/base/process_util_win.cc
@@ -0,0 +1,982 @@ +// 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/process_util.h" + +#include <fcntl.h> +#include <io.h> +#include <windows.h> +#include <userenv.h> +#include <psapi.h> + +#include <ios> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/debug/stack_trace.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/sys_info.h" +#include "base/win/object_watcher.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "base/win/windows_version.h" + +// userenv.dll is required for CreateEnvironmentBlock(). +#pragma comment(lib, "userenv.lib") + +namespace base { + +namespace { + +// System pagesize. This value remains constant on x86/64 architectures. +const int PAGESIZE_KB = 4; + +// Exit codes with special meanings on Windows. +const DWORD kNormalTerminationExitCode = 0; +const DWORD kDebuggerInactiveExitCode = 0xC0000354; +const DWORD kKeyboardInterruptExitCode = 0xC000013A; +const DWORD kDebuggerTerminatedExitCode = 0x40010004; + +// Maximum amount of time (in milliseconds) to wait for the process to exit. +static const int kWaitInterval = 2000; + +// This exit code is used by the Windows task manager when it kills a +// process. It's value is obviously not that unique, and it's +// surprising to me that the task manager uses this value, but it +// seems to be common practice on Windows to test for it as an +// indication that the task manager has killed something if the +// process goes away. +const DWORD kProcessKilledExitCode = 1; + +// HeapSetInformation function pointer. +typedef BOOL (WINAPI* HeapSetFn)(HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T); + +void OnNoMemory() { + // Kill the process. This is important for security, since WebKit doesn't + // NULL-check many memory allocations. If a malloc fails, returns NULL, and + // the buffer is then used, it provides a handy mapping of memory starting at + // address 0 for an attacker to utilize. + __debugbreak(); + _exit(1); +} + +class TimerExpiredTask : public win::ObjectWatcher::Delegate { + public: + explicit TimerExpiredTask(ProcessHandle process); + ~TimerExpiredTask(); + + void TimedOut(); + + // MessageLoop::Watcher ----------------------------------------------------- + virtual void OnObjectSignaled(HANDLE object); + + private: + void KillProcess(); + + // The process that we are watching. + ProcessHandle process_; + + win::ObjectWatcher watcher_; + + DISALLOW_COPY_AND_ASSIGN(TimerExpiredTask); +}; + +TimerExpiredTask::TimerExpiredTask(ProcessHandle process) : process_(process) { + watcher_.StartWatching(process_, this); +} + +TimerExpiredTask::~TimerExpiredTask() { + TimedOut(); + DCHECK(!process_) << "Make sure to close the handle."; +} + +void TimerExpiredTask::TimedOut() { + if (process_) + KillProcess(); +} + +void TimerExpiredTask::OnObjectSignaled(HANDLE object) { + CloseHandle(process_); + process_ = NULL; +} + +void TimerExpiredTask::KillProcess() { + // Stop watching the process handle since we're killing it. + watcher_.StopWatching(); + + // OK, time to get frisky. We don't actually care when the process + // terminates. We just care that it eventually terminates, and that's what + // TerminateProcess should do for us. Don't check for the result code since + // it fails quite often. This should be investigated eventually. + base::KillProcess(process_, kProcessKilledExitCode, false); + + // Now, just cleanup as if the process exited normally. + OnObjectSignaled(process_); +} + +} // namespace + +void RouteStdioToConsole() { + if (!AttachConsole(ATTACH_PARENT_PROCESS)) { + unsigned int result = GetLastError(); + // Was probably already attached. + if (result == ERROR_ACCESS_DENIED) + return; + // Don't bother creating a new console for each child process if the + // parent process is invalid (eg: crashed). + if (result == ERROR_GEN_FAILURE) + return; + // Make a new console if attaching to parent fails with any other error. + // It should be ERROR_INVALID_HANDLE at this point, which means the browser + // was likely not started from a console. + AllocConsole(); + } + + // Arbitrary byte count to use when buffering output lines. More + // means potential waste, less means more risk of interleaved + // log-lines in output. + enum { kOutputBufferSize = 64 * 1024 }; + + if (freopen("CONOUT$", "w", stdout)) + setvbuf(stdout, NULL, _IOLBF, kOutputBufferSize); + if (freopen("CONOUT$", "w", stderr)) + setvbuf(stderr, NULL, _IOLBF, kOutputBufferSize); + + // Fix all cout, wcout, cin, wcin, cerr, wcerr, clog and wclog. + std::ios::sync_with_stdio(); +} + +ProcessId GetCurrentProcId() { + return ::GetCurrentProcessId(); +} + +ProcessHandle GetCurrentProcessHandle() { + return ::GetCurrentProcess(); +} + +HMODULE GetModuleFromAddress(void* address) { + HMODULE instance = NULL; + if (!::GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + static_cast<char*>(address), + &instance)) { + NOTREACHED(); + } + return instance; +} + +bool OpenProcessHandle(ProcessId pid, ProcessHandle* handle) { + // We try to limit privileges granted to the handle. If you need this + // for test code, consider using OpenPrivilegedProcessHandle instead of + // adding more privileges here. + ProcessHandle result = OpenProcess(PROCESS_DUP_HANDLE | PROCESS_TERMINATE, + FALSE, pid); + + if (result == NULL) + return false; + + *handle = result; + return true; +} + +bool OpenPrivilegedProcessHandle(ProcessId pid, ProcessHandle* handle) { + ProcessHandle result = OpenProcess(PROCESS_DUP_HANDLE | + PROCESS_TERMINATE | + PROCESS_QUERY_INFORMATION | + PROCESS_VM_READ | + SYNCHRONIZE, + FALSE, pid); + + if (result == NULL) + return false; + + *handle = result; + return true; +} + +bool OpenProcessHandleWithAccess(ProcessId pid, + uint32 access_flags, + ProcessHandle* handle) { + ProcessHandle result = OpenProcess(access_flags, FALSE, pid); + + if (result == NULL) + return false; + + *handle = result; + return true; +} + +void CloseProcessHandle(ProcessHandle process) { + CloseHandle(process); +} + +ProcessId GetProcId(ProcessHandle process) { + // Get a handle to |process| that has PROCESS_QUERY_INFORMATION rights. + HANDLE current_process = GetCurrentProcess(); + HANDLE process_with_query_rights; + if (DuplicateHandle(current_process, process, current_process, + &process_with_query_rights, PROCESS_QUERY_INFORMATION, + false, 0)) { + DWORD id = GetProcessId(process_with_query_rights); + CloseHandle(process_with_query_rights); + return id; + } + + // We're screwed. + NOTREACHED(); + return 0; +} + +bool GetProcessIntegrityLevel(ProcessHandle process, IntegrityLevel *level) { + if (!level) + return false; + + if (win::GetVersion() < base::win::VERSION_VISTA) + return false; + + HANDLE process_token; + if (!OpenProcessToken(process, TOKEN_QUERY | TOKEN_QUERY_SOURCE, + &process_token)) + return false; + + win::ScopedHandle scoped_process_token(process_token); + + DWORD token_info_length = 0; + if (GetTokenInformation(process_token, TokenIntegrityLevel, NULL, 0, + &token_info_length) || + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return false; + + scoped_array<char> token_label_bytes(new char[token_info_length]); + if (!token_label_bytes.get()) + return false; + + TOKEN_MANDATORY_LABEL* token_label = + reinterpret_cast<TOKEN_MANDATORY_LABEL*>(token_label_bytes.get()); + if (!token_label) + return false; + + if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_label, + token_info_length, &token_info_length)) + return false; + + DWORD integrity_level = *GetSidSubAuthority(token_label->Label.Sid, + (DWORD)(UCHAR)(*GetSidSubAuthorityCount(token_label->Label.Sid)-1)); + + if (integrity_level < SECURITY_MANDATORY_MEDIUM_RID) { + *level = LOW_INTEGRITY; + } else if (integrity_level >= SECURITY_MANDATORY_MEDIUM_RID && + integrity_level < SECURITY_MANDATORY_HIGH_RID) { + *level = MEDIUM_INTEGRITY; + } else if (integrity_level >= SECURITY_MANDATORY_HIGH_RID) { + *level = HIGH_INTEGRITY; + } else { + NOTREACHED(); + return false; + } + + return true; +} + +bool LaunchProcess(const string16& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle) { + STARTUPINFO startup_info = {}; + startup_info.cb = sizeof(startup_info); + if (options.empty_desktop_name) + startup_info.lpDesktop = L""; + startup_info.dwFlags = STARTF_USESHOWWINDOW; + startup_info.wShowWindow = options.start_hidden ? SW_HIDE : SW_SHOW; + + DWORD flags = 0; + + if (options.job_handle) { + flags |= CREATE_SUSPENDED; + + // If this code is run under a debugger, the launched process is + // automatically associated with a job object created by the debugger. + // The CREATE_BREAKAWAY_FROM_JOB flag is used to prevent this. + flags |= CREATE_BREAKAWAY_FROM_JOB; + } + + if (options.force_breakaway_from_job_) + flags |= CREATE_BREAKAWAY_FROM_JOB; + + base::win::ScopedProcessInformation process_info; + + if (options.as_user) { + flags |= CREATE_UNICODE_ENVIRONMENT; + void* enviroment_block = NULL; + + if (!CreateEnvironmentBlock(&enviroment_block, options.as_user, FALSE)) + return false; + + 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()); + DestroyEnvironmentBlock(enviroment_block); + if (!launched) + return false; + } else { + if (!CreateProcess(NULL, + const_cast<wchar_t*>(cmdline.c_str()), NULL, NULL, + options.inherit_handles, flags, NULL, NULL, + &startup_info, process_info.Receive())) { + return false; + } + } + + if (options.job_handle) { + if (0 == AssignProcessToJobObject(options.job_handle, + process_info.process_handle())) { + DLOG(ERROR) << "Could not AssignProcessToObject."; + KillProcess(process_info.process_handle(), kProcessKilledExitCode, true); + return false; + } + + ResumeThread(process_info.thread_handle()); + } + + if (options.wait) + WaitForSingleObject(process_info.process_handle(), INFINITE); + + // If the caller wants the process handle, we won't close it. + if (process_handle) + *process_handle = process_info.TakeProcessHandle(); + + return true; +} + +bool LaunchProcess(const CommandLine& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle) { + return LaunchProcess(cmdline.GetCommandLineString(), options, process_handle); +} + +bool SetJobObjectAsKillOnJobClose(HANDLE job_object) { + JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info = {0}; + limit_info.BasicLimitInformation.LimitFlags = + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + return 0 != SetInformationJobObject( + job_object, + JobObjectExtendedLimitInformation, + &limit_info, + sizeof(limit_info)); +} + +// Attempts to kill the process identified by the given process +// entry structure, giving it the specified exit code. +// Returns true if this is successful, false otherwise. +bool KillProcessById(ProcessId process_id, int exit_code, bool wait) { + HANDLE process = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, + FALSE, // Don't inherit handle + process_id); + if (!process) { + DLOG(ERROR) << "Unable to open process " << process_id << " : " + << GetLastError(); + return false; + } + bool ret = KillProcess(process, exit_code, wait); + CloseHandle(process); + return ret; +} + +bool GetAppOutput(const CommandLine& cl, std::string* output) { + HANDLE out_read = NULL; + HANDLE out_write = NULL; + + SECURITY_ATTRIBUTES sa_attr; + // Set the bInheritHandle flag so pipe handles are inherited. + sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + sa_attr.bInheritHandle = TRUE; + sa_attr.lpSecurityDescriptor = NULL; + + // Create the pipe for the child process's STDOUT. + if (!CreatePipe(&out_read, &out_write, &sa_attr, 0)) { + NOTREACHED() << "Failed to create pipe"; + return false; + } + + // Ensure we don't leak the handles. + win::ScopedHandle scoped_out_read(out_read); + win::ScopedHandle scoped_out_write(out_write); + + // Ensure the read handle to the pipe for STDOUT is not inherited. + if (!SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)) { + NOTREACHED() << "Failed to disabled pipe inheritance"; + return false; + } + + FilePath::StringType writable_command_line_string(cl.GetCommandLineString()); + + base::win::ScopedProcessInformation proc_info; + STARTUPINFO start_info = { 0 }; + + start_info.cb = sizeof(STARTUPINFO); + start_info.hStdOutput = out_write; + // Keep the normal stdin and stderr. + start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); + start_info.dwFlags |= STARTF_USESTDHANDLES; + + // Create the child process. + if (!CreateProcess(NULL, + &writable_command_line_string[0], + NULL, NULL, + TRUE, // Handles are inherited. + 0, NULL, NULL, &start_info, proc_info.Receive())) { + NOTREACHED() << "Failed to start process"; + return false; + } + + // Close our writing end of pipe now. Otherwise later read would not be able + // to detect end of child's output. + scoped_out_write.Close(); + + // Read output from the child process's pipe for STDOUT + const int kBufferSize = 1024; + char buffer[kBufferSize]; + + for (;;) { + DWORD bytes_read = 0; + BOOL success = ReadFile(out_read, buffer, kBufferSize, &bytes_read, NULL); + if (!success || bytes_read == 0) + break; + output->append(buffer, bytes_read); + } + + // Let's wait for the process to finish. + WaitForSingleObject(proc_info.process_handle(), INFINITE); + + return true; +} + +bool KillProcess(ProcessHandle process, int exit_code, bool wait) { + bool result = (TerminateProcess(process, exit_code) != FALSE); + if (result && wait) { + // The process may not end immediately due to pending I/O + if (WAIT_OBJECT_0 != WaitForSingleObject(process, 60 * 1000)) + DLOG(ERROR) << "Error waiting for process exit: " << GetLastError(); + } else if (!result) { + DLOG(ERROR) << "Unable to terminate process: " << GetLastError(); + } + return result; +} + +TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) { + DWORD tmp_exit_code = 0; + + if (!::GetExitCodeProcess(handle, &tmp_exit_code)) { + NOTREACHED(); + if (exit_code) { + // This really is a random number. We haven't received any + // information about the exit code, presumably because this + // process doesn't have permission to get the exit code, or + // because of some other cause for GetExitCodeProcess to fail + // (MSDN docs don't give the possible failure error codes for + // this function, so it could be anything). But we don't want + // to leave exit_code uninitialized, since that could cause + // random interpretations of the exit code. So we assume it + // terminated "normally" in this case. + *exit_code = kNormalTerminationExitCode; + } + // Assume the child has exited normally if we can't get the exit + // code. + return TERMINATION_STATUS_NORMAL_TERMINATION; + } + if (tmp_exit_code == STILL_ACTIVE) { + DWORD wait_result = WaitForSingleObject(handle, 0); + if (wait_result == WAIT_TIMEOUT) { + if (exit_code) + *exit_code = wait_result; + return TERMINATION_STATUS_STILL_RUNNING; + } + + DCHECK_EQ(WAIT_OBJECT_0, wait_result); + + // Strange, the process used 0x103 (STILL_ACTIVE) as exit code. + NOTREACHED(); + + return TERMINATION_STATUS_ABNORMAL_TERMINATION; + } + + if (exit_code) + *exit_code = tmp_exit_code; + + switch (tmp_exit_code) { + case kNormalTerminationExitCode: + return TERMINATION_STATUS_NORMAL_TERMINATION; + case kDebuggerInactiveExitCode: // STATUS_DEBUGGER_INACTIVE. + case kKeyboardInterruptExitCode: // Control-C/end session. + case kDebuggerTerminatedExitCode: // Debugger terminated process. + case kProcessKilledExitCode: // Task manager kill. + return TERMINATION_STATUS_PROCESS_WAS_KILLED; + default: + // All other exit codes indicate crashes. + return TERMINATION_STATUS_PROCESS_CRASHED; + } +} + +bool WaitForExitCode(ProcessHandle handle, int* exit_code) { + bool success = WaitForExitCodeWithTimeout( + handle, exit_code, base::TimeDelta::FromMilliseconds(INFINITE)); + CloseProcessHandle(handle); + return success; +} + +bool WaitForExitCodeWithTimeout(ProcessHandle handle, int* exit_code, + base::TimeDelta timeout) { + if (::WaitForSingleObject(handle, timeout.InMilliseconds()) != WAIT_OBJECT_0) + return false; + DWORD temp_code; // Don't clobber out-parameters in case of failure. + if (!::GetExitCodeProcess(handle, &temp_code)) + return false; + + *exit_code = temp_code; + return true; +} + +ProcessIterator::ProcessIterator(const ProcessFilter* filter) + : started_iteration_(false), + filter_(filter) { + snapshot_ = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); +} + +ProcessIterator::~ProcessIterator() { + CloseHandle(snapshot_); +} + +bool ProcessIterator::CheckForNextProcess() { + InitProcessEntry(&entry_); + + if (!started_iteration_) { + started_iteration_ = true; + return !!Process32First(snapshot_, &entry_); + } + + return !!Process32Next(snapshot_, &entry_); +} + +void ProcessIterator::InitProcessEntry(ProcessEntry* entry) { + memset(entry, 0, sizeof(*entry)); + entry->dwSize = sizeof(*entry); +} + +bool NamedProcessIterator::IncludeEntry() { + // Case insensitive. + return _wcsicmp(executable_name_.c_str(), entry().exe_file()) == 0 && + ProcessIterator::IncludeEntry(); +} + +bool WaitForProcessesToExit(const FilePath::StringType& executable_name, + base::TimeDelta wait, + const ProcessFilter* filter) { + const ProcessEntry* entry; + bool result = true; + DWORD start_time = GetTickCount(); + + NamedProcessIterator iter(executable_name, filter); + while ((entry = iter.NextProcessEntry())) { + DWORD remaining_wait = std::max<int64>( + 0, wait.InMilliseconds() - (GetTickCount() - start_time)); + HANDLE process = OpenProcess(SYNCHRONIZE, + FALSE, + entry->th32ProcessID); + DWORD wait_result = WaitForSingleObject(process, remaining_wait); + CloseHandle(process); + result = result && (wait_result == WAIT_OBJECT_0); + } + + return result; +} + +bool WaitForSingleProcess(ProcessHandle handle, base::TimeDelta wait) { + int exit_code; + if (!WaitForExitCodeWithTimeout(handle, &exit_code, wait)) + return false; + return exit_code == 0; +} + +bool CleanupProcesses(const FilePath::StringType& executable_name, + base::TimeDelta wait, + int exit_code, + const ProcessFilter* filter) { + bool exited_cleanly = WaitForProcessesToExit(executable_name, wait, filter); + if (!exited_cleanly) + KillProcesses(executable_name, exit_code, filter); + return exited_cleanly; +} + +void EnsureProcessTerminated(ProcessHandle process) { + DCHECK(process != GetCurrentProcess()); + + // If already signaled, then we are done! + if (WaitForSingleObject(process, 0) == WAIT_OBJECT_0) { + CloseHandle(process); + return; + } + + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&TimerExpiredTask::TimedOut, + base::Owned(new TimerExpiredTask(process))), + base::TimeDelta::FromMilliseconds(kWaitInterval)); +} + +/////////////////////////////////////////////////////////////////////////////// +// ProcesMetrics + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + processor_count_(base::SysInfo::NumberOfProcessors()), + last_time_(0), + last_system_time_(0) { +} + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +ProcessMetrics::~ProcessMetrics() { } + +size_t ProcessMetrics::GetPagefileUsage() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.PagefileUsage; + } + return 0; +} + +// Returns the peak space allocated for the pagefile, in bytes. +size_t ProcessMetrics::GetPeakPagefileUsage() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.PeakPagefileUsage; + } + return 0; +} + +// Returns the current working set size, in bytes. +size_t ProcessMetrics::GetWorkingSetSize() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.WorkingSetSize; + } + return 0; +} + +// Returns the peak working set size, in bytes. +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.PeakWorkingSetSize; + } + return 0; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + // PROCESS_MEMORY_COUNTERS_EX is not supported until XP SP2. + // GetProcessMemoryInfo() will simply fail on prior OS. So the requested + // information is simply not available. Hence, we will return 0 on unsupported + // OSes. Unlike most Win32 API, we don't need to initialize the "cb" member. + PROCESS_MEMORY_COUNTERS_EX pmcx; + if (private_bytes && + GetProcessMemoryInfo(process_, + reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&pmcx), + sizeof(pmcx))) { + *private_bytes = pmcx.PrivateUsage; + } + + if (shared_bytes) { + WorkingSetKBytes ws_usage; + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + *shared_bytes = ws_usage.shared * 1024; + } + + return true; +} + +void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) const { + MEMORY_BASIC_INFORMATION mbi = {0}; + size_t committed_private = 0; + size_t committed_mapped = 0; + size_t committed_image = 0; + void* base_address = NULL; + while (VirtualQueryEx(process_, base_address, &mbi, sizeof(mbi)) == + sizeof(mbi)) { + if (mbi.State == MEM_COMMIT) { + if (mbi.Type == MEM_PRIVATE) { + committed_private += mbi.RegionSize; + } else if (mbi.Type == MEM_MAPPED) { + committed_mapped += mbi.RegionSize; + } else if (mbi.Type == MEM_IMAGE) { + committed_image += mbi.RegionSize; + } else { + NOTREACHED(); + } + } + void* new_base = (static_cast<BYTE*>(mbi.BaseAddress)) + mbi.RegionSize; + // Avoid infinite loop by weird MEMORY_BASIC_INFORMATION. + // If we query 64bit processes in a 32bit process, VirtualQueryEx() + // returns such data. + if (new_base <= base_address) { + usage->image = 0; + usage->mapped = 0; + usage->priv = 0; + return; + } + base_address = new_base; + } + usage->image = committed_image / 1024; + usage->mapped = committed_mapped / 1024; + usage->priv = committed_private / 1024; +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { + size_t ws_private = 0; + size_t ws_shareable = 0; + size_t ws_shared = 0; + + DCHECK(ws_usage); + memset(ws_usage, 0, sizeof(*ws_usage)); + + DWORD number_of_entries = 4096; // Just a guess. + PSAPI_WORKING_SET_INFORMATION* buffer = NULL; + int retries = 5; + for (;;) { + DWORD buffer_size = sizeof(PSAPI_WORKING_SET_INFORMATION) + + (number_of_entries * sizeof(PSAPI_WORKING_SET_BLOCK)); + + // if we can't expand the buffer, don't leak the previous + // contents or pass a NULL pointer to QueryWorkingSet + PSAPI_WORKING_SET_INFORMATION* new_buffer = + reinterpret_cast<PSAPI_WORKING_SET_INFORMATION*>( + realloc(buffer, buffer_size)); + if (!new_buffer) { + free(buffer); + return false; + } + buffer = new_buffer; + + // Call the function once to get number of items + if (QueryWorkingSet(process_, buffer, buffer_size)) + break; // Success + + if (GetLastError() != ERROR_BAD_LENGTH) { + free(buffer); + return false; + } + + number_of_entries = static_cast<DWORD>(buffer->NumberOfEntries); + + // Maybe some entries are being added right now. Increase the buffer to + // take that into account. + number_of_entries = static_cast<DWORD>(number_of_entries * 1.25); + + if (--retries == 0) { + free(buffer); // If we're looping, eventually fail. + return false; + } + } + + // On windows 2000 the function returns 1 even when the buffer is too small. + // The number of entries that we are going to parse is the minimum between the + // size we allocated and the real number of entries. + number_of_entries = + std::min(number_of_entries, static_cast<DWORD>(buffer->NumberOfEntries)); + for (unsigned int i = 0; i < number_of_entries; i++) { + if (buffer->WorkingSetInfo[i].Shared) { + ws_shareable++; + if (buffer->WorkingSetInfo[i].ShareCount > 1) + ws_shared++; + } else { + ws_private++; + } + } + + ws_usage->priv = ws_private * PAGESIZE_KB; + ws_usage->shareable = ws_shareable * PAGESIZE_KB; + ws_usage->shared = ws_shared * PAGESIZE_KB; + free(buffer); + return true; +} + +static uint64 FileTimeToUTC(const FILETIME& ftime) { + LARGE_INTEGER li; + li.LowPart = ftime.dwLowDateTime; + li.HighPart = ftime.dwHighDateTime; + return li.QuadPart; +} + +double ProcessMetrics::GetCPUUsage() { + FILETIME now; + FILETIME creation_time; + FILETIME exit_time; + FILETIME kernel_time; + FILETIME user_time; + + GetSystemTimeAsFileTime(&now); + + if (!GetProcessTimes(process_, &creation_time, &exit_time, + &kernel_time, &user_time)) { + // We don't assert here because in some cases (such as in the Task Manager) + // we may call this function on a process that has just exited but we have + // not yet received the notification. + return 0; + } + int64 system_time = (FileTimeToUTC(kernel_time) + FileTimeToUTC(user_time)) / + processor_count_; + int64 time = FileTimeToUTC(now); + + if ((last_system_time_ == 0) || (last_time_ == 0)) { + // First call, just set the last values. + last_system_time_ = system_time; + last_time_ = time; + return 0; + } + + int64 system_time_delta = system_time - last_system_time_; + int64 time_delta = time - last_time_; + DCHECK_NE(0U, time_delta); + if (time_delta == 0) + return 0; + + // We add time_delta / 2 so the result is rounded. + int cpu = static_cast<int>((system_time_delta * 100 + time_delta / 2) / + time_delta); + + last_system_time_ = system_time; + last_time_ = time; + + return cpu; +} + +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + return GetProcessIoCounters(process_, io_counters) != FALSE; +} + +bool ProcessMetrics::CalculateFreeMemory(FreeMBytes* free) const { + const SIZE_T kTopAddress = 0x7F000000; + const SIZE_T kMegabyte = 1024 * 1024; + SIZE_T accumulated = 0; + + MEMORY_BASIC_INFORMATION largest = {0}; + UINT_PTR scan = 0; + while (scan < kTopAddress) { + MEMORY_BASIC_INFORMATION info; + if (!::VirtualQueryEx(process_, reinterpret_cast<void*>(scan), + &info, sizeof(info))) + return false; + if (info.State == MEM_FREE) { + accumulated += info.RegionSize; + if (info.RegionSize > largest.RegionSize) + largest = info; + } + scan += info.RegionSize; + } + free->largest = largest.RegionSize / kMegabyte; + free->largest_ptr = largest.BaseAddress; + free->total = accumulated / kMegabyte; + return true; +} + +bool EnableLowFragmentationHeap() { + HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); + HeapSetFn heap_set = reinterpret_cast<HeapSetFn>(GetProcAddress( + kernel32, + "HeapSetInformation")); + + // On Windows 2000, the function is not exported. This is not a reason to + // fail. + if (!heap_set) + return true; + + unsigned number_heaps = GetProcessHeaps(0, NULL); + if (!number_heaps) + return false; + + // Gives us some extra space in the array in case a thread is creating heaps + // at the same time we're querying them. + static const int MARGIN = 8; + scoped_array<HANDLE> heaps(new HANDLE[number_heaps + MARGIN]); + number_heaps = GetProcessHeaps(number_heaps + MARGIN, heaps.get()); + if (!number_heaps) + return false; + + for (unsigned i = 0; i < number_heaps; ++i) { + ULONG lfh_flag = 2; + // Don't bother with the result code. It may fails on heaps that have the + // HEAP_NO_SERIALIZE flag. This is expected and not a problem at all. + heap_set(heaps[i], + HeapCompatibilityInformation, + &lfh_flag, + sizeof(lfh_flag)); + } + return true; +} + +void EnableTerminationOnHeapCorruption() { + // Ignore the result code. Supported on XP SP3 and Vista. + HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0); +} + +void EnableTerminationOnOutOfMemory() { + std::set_new_handler(&OnNoMemory); +} + +void RaiseProcessToHighPriority() { + SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); +} + +// GetPerformanceInfo is not available on WIN2K. So we'll +// load it on-the-fly. +const wchar_t kPsapiDllName[] = L"psapi.dll"; +typedef BOOL (WINAPI *GetPerformanceInfoFunction) ( + PPERFORMANCE_INFORMATION pPerformanceInformation, + DWORD cb); + +// Beware of races if called concurrently from multiple threads. +static BOOL InternalGetPerformanceInfo( + PPERFORMANCE_INFORMATION pPerformanceInformation, DWORD cb) { + static GetPerformanceInfoFunction GetPerformanceInfo_func = NULL; + if (!GetPerformanceInfo_func) { + HMODULE psapi_dll = ::GetModuleHandle(kPsapiDllName); + if (psapi_dll) + GetPerformanceInfo_func = reinterpret_cast<GetPerformanceInfoFunction>( + GetProcAddress(psapi_dll, "GetPerformanceInfo")); + + if (!GetPerformanceInfo_func) { + // The function could be loaded! + memset(pPerformanceInformation, 0, cb); + return FALSE; + } + } + return GetPerformanceInfo_func(pPerformanceInformation, cb); +} + +size_t GetSystemCommitCharge() { + // Get the System Page Size. + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + + PERFORMANCE_INFORMATION info; + if (!InternalGetPerformanceInfo(&info, sizeof(info))) { + DLOG(ERROR) << "Failed to fetch internal performance info."; + return 0; + } + return (info.CommitTotal * system_info.dwPageSize) / 1024; +} + +} // namespace base
diff --git a/src/base/process_win.cc b/src/base/process_win.cc new file mode 100644 index 0000000..3f683ab --- /dev/null +++ b/src/base/process_win.cc
@@ -0,0 +1,92 @@ +// 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/process.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/process_util.h" +#include "base/win/windows_version.h" + +namespace base { + +void Process::Close() { + if (!process_) + return; + + // Don't call CloseHandle on a pseudo-handle. + if (process_ != ::GetCurrentProcess()) + ::CloseHandle(process_); + + process_ = NULL; +} + +void Process::Terminate(int result_code) { + if (!process_) + return; + + // Call NtTerminateProcess directly, without going through the import table, + // which might have been hooked with a buggy replacement by third party + // software. http://crbug.com/81449. + HMODULE module = GetModuleHandle(L"ntdll.dll"); + typedef UINT (WINAPI *TerminateProcessPtr)(HANDLE handle, UINT code); + TerminateProcessPtr terminate_process = reinterpret_cast<TerminateProcessPtr>( + GetProcAddress(module, "NtTerminateProcess")); + terminate_process(process_, result_code); +} + +bool Process::IsProcessBackgrounded() const { + if (!process_) + return false; // Failure case. + DWORD priority = GetPriority(); + if (priority == 0) + return false; // Failure case. + return ((priority == BELOW_NORMAL_PRIORITY_CLASS) || + (priority == IDLE_PRIORITY_CLASS)); +} + +bool Process::SetProcessBackgrounded(bool value) { + if (!process_) + return false; + // Vista and above introduce a real background mode, which not only + // sets the priority class on the threads but also on the IO generated + // by it. Unfortunately it can only be set for the calling process. + DWORD priority; + if ((base::win::GetVersion() >= base::win::VERSION_VISTA) && + (process_ == ::GetCurrentProcess())) { + priority = value ? PROCESS_MODE_BACKGROUND_BEGIN : + PROCESS_MODE_BACKGROUND_END; + } else { + priority = value ? BELOW_NORMAL_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS; + } + + return (::SetPriorityClass(process_, priority) != 0); +} + +ProcessId Process::pid() const { + if (process_ == 0) + return 0; + + return GetProcId(process_); +} + +bool Process::is_current() const { + return process_ == GetCurrentProcess(); +} + +// static +Process Process::Current() { + return Process(::GetCurrentProcess()); +} + +// static +bool Process::CanBackgroundProcesses() { + return true; +} + +int Process::GetPriority() const { + DCHECK(process_); + return ::GetPriorityClass(process_); +} + +} // namespace base
diff --git a/src/base/profiler/alternate_timer.cc b/src/base/profiler/alternate_timer.cc new file mode 100644 index 0000000..4eba89c --- /dev/null +++ b/src/base/profiler/alternate_timer.cc
@@ -0,0 +1,37 @@ +// 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/profiler/alternate_timer.h" + +#include "base/logging.h" + +namespace { + +tracked_objects::NowFunction* g_time_function = NULL; +tracked_objects::TimeSourceType g_time_source_type = + tracked_objects::TIME_SOURCE_TYPE_WALL_TIME; + +} // anonymous namespace + +namespace tracked_objects { + +const char kAlternateProfilerTime[] = "CHROME_PROFILER_TIME"; + +// Set an alternate timer function to replace the OS time function when +// profiling. +void SetAlternateTimeSource(NowFunction* now_function, TimeSourceType type) { + DCHECK_EQ(reinterpret_cast<NowFunction*>(NULL), g_time_function); + g_time_function = now_function; + g_time_source_type = type; +} + +NowFunction* GetAlternateTimeSource() { + return g_time_function; +} + +TimeSourceType GetTimeSourceType() { + return g_time_source_type; +} + +} // namespace tracked_objects
diff --git a/src/base/profiler/alternate_timer.h b/src/base/profiler/alternate_timer.h new file mode 100644 index 0000000..fdc75dc --- /dev/null +++ b/src/base/profiler/alternate_timer.h
@@ -0,0 +1,44 @@ +// 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. + +// This is a glue file, which allows third party code to call into our profiler +// without having to include most any functions from base. + +#ifndef BASE_PROFILER_ALTERNATE_TIMER_H_ +#define BASE_PROFILER_ALTERNATE_TIMER_H_ + +#include "base/base_export.h" + +namespace tracked_objects { + +enum TimeSourceType { + TIME_SOURCE_TYPE_WALL_TIME, + TIME_SOURCE_TYPE_TCMALLOC +}; + +// Provide type for an alternate timer function. +typedef unsigned int NowFunction(); + +// Environment variable name that is used to activate alternate timer profiling +// (such as using TCMalloc allocations to provide a pseudo-timer) for tasks +// instead of wall clock profiling. +BASE_EXPORT extern const char kAlternateProfilerTime[]; + +// Set an alternate timer function to replace the OS time function when +// profiling. Typically this is called by an allocator that is providing a +// function that indicates how much memory has been allocated on any given +// thread. +BASE_EXPORT void SetAlternateTimeSource(NowFunction* now_function, + TimeSourceType type); + +// Gets the pointer to a function that was set via SetAlternateTimeSource(). +// Returns NULL if no set was done prior to calling GetAlternateTimeSource. +NowFunction* GetAlternateTimeSource(); + +// Returns the type of the currently set time source. +BASE_EXPORT TimeSourceType GetTimeSourceType(); + +} // namespace tracked_objects + +#endif // BASE_PROFILER_ALTERNATE_TIMER_H_
diff --git a/src/base/profiler/scoped_profile.cc b/src/base/profiler/scoped_profile.cc new file mode 100644 index 0000000..93c86e9 --- /dev/null +++ b/src/base/profiler/scoped_profile.cc
@@ -0,0 +1,31 @@ +// 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/profiler/scoped_profile.h" + +#include "base/location.h" +#include "base/tracked_objects.h" + + +namespace tracked_objects { + + +ScopedProfile::ScopedProfile(const Location& location) + : birth_(ThreadData::TallyABirthIfActive(location)), + start_of_run_(ThreadData::NowForStartOfRun(birth_)) { +} + +ScopedProfile::~ScopedProfile() { + StopClockAndTally(); +} + +void ScopedProfile::StopClockAndTally() { + if (!birth_) + return; + ThreadData::TallyRunInAScopedRegionIfTracking(birth_, start_of_run_, + ThreadData::NowForEndOfRun()); + birth_ = NULL; +} + +} // namespace tracked_objects
diff --git a/src/base/profiler/scoped_profile.h b/src/base/profiler/scoped_profile.h new file mode 100644 index 0000000..8b77d6d --- /dev/null +++ b/src/base/profiler/scoped_profile.h
@@ -0,0 +1,67 @@ +// 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. + + +#ifndef BASE_PROFILER_SCOPED_PROFILE_H_ +#define BASE_PROFILER_SCOPED_PROFILE_H_ + +//------------------------------------------------------------------------------ +// ScopedProfile provides basic helper functions for profiling a short +// region of code within a scope. It is separate from the related ThreadData +// class so that it can be included without much other cruft, and provide the +// macros listed below. + +#include "base/base_export.h" +#include "base/location.h" +#include "base/profiler/tracked_time.h" + +#if defined(GOOGLE_CHROME_BUILD) + +// We don't ship these profiled regions. This is for developer builds only. +// It allows developers to do some profiling of their code, and see results on +// their about:profiler page. +#define TRACK_RUN_IN_THIS_SCOPED_REGION_FOR_DEVELOPER_BUILDS(scope_name) \ + ((void)0) + +#else + +#define TRACK_RUN_IN_THIS_SCOPED_REGION_FOR_DEVELOPER_BUILDS(scope_name) \ + ::tracked_objects::ScopedProfile LINE_BASED_VARIABLE_NAME_FOR_PROFILING( \ + FROM_HERE_WITH_EXPLICIT_FUNCTION(#scope_name)) + +#endif + + + +#define PASTE_LINE_NUMBER_ON_NAME(name, line) name##line + +#define LINE_BASED_VARIABLE_NAME_FOR_PROFILING \ + PASTE_LINE_NUMBER_ON_NAME(some_profiler_variable_, __LINE__) + +#define TRACK_RUN_IN_IPC_HANDLER(dispatch_function_name) \ + ::tracked_objects::ScopedProfile some_tracking_variable_name( \ + FROM_HERE_WITH_EXPLICIT_FUNCTION(#dispatch_function_name)) + + +namespace tracked_objects { +class Births; + +class BASE_EXPORT ScopedProfile { + public: + explicit ScopedProfile(const Location& location); + ~ScopedProfile(); + + // Stop tracing prior to the end destruction of the instance. + void StopClockAndTally(); + + private: + Births* birth_; // Place in code where tracking started. + const TrackedTime start_of_run_; + + DISALLOW_COPY_AND_ASSIGN(ScopedProfile); +}; + +} // namespace tracked_objects + +#endif // BASE_PROFILER_SCOPED_PROFILE_H_
diff --git a/src/base/profiler/tracked_time.cc b/src/base/profiler/tracked_time.cc new file mode 100644 index 0000000..f1edd6d --- /dev/null +++ b/src/base/profiler/tracked_time.cc
@@ -0,0 +1,76 @@ +// 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/profiler/tracked_time.h" + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <mmsystem.h> // Declare timeGetTime()... after including build_config. +#endif + +namespace tracked_objects { + +Duration::Duration() : ms_(0) {} +Duration::Duration(int32 duration) : ms_(duration) {} + +Duration& Duration::operator+=(const Duration& other) { + ms_ += other.ms_; + return *this; +} + +Duration Duration::operator+(const Duration& other) const { + return Duration(ms_ + other.ms_); +} + +bool Duration::operator==(const Duration& other) const { + return ms_ == other.ms_; +} + +bool Duration::operator!=(const Duration& other) const { + return ms_ != other.ms_; +} + +bool Duration::operator>(const Duration& other) const { + return ms_ > other.ms_; +} + +// static +Duration Duration::FromMilliseconds(int ms) { return Duration(ms); } + +int32 Duration::InMilliseconds() const { return ms_; } + +//------------------------------------------------------------------------------ + +TrackedTime::TrackedTime() : ms_(0) {} +TrackedTime::TrackedTime(int32 ms) : ms_(ms) {} +TrackedTime::TrackedTime(const base::TimeTicks& time) + : ms_((time - base::TimeTicks()).InMilliseconds()) { +} + +// static +TrackedTime TrackedTime::Now() { +#if defined(OS_WIN) + // Use lock-free accessor to 32 bit time. + // Note that TimeTicks::Now() is built on this, so we have "compatible" + // times when we down-convert a TimeTicks sample. + // TODO(jar): Surface this interface via something in base/time.h. + return TrackedTime(static_cast<int32>(timeGetTime())); +#else + // Posix has nice cheap 64 bit times, so we just down-convert it. + return TrackedTime(base::TimeTicks::Now()); +#endif // OS_WIN +} + +Duration TrackedTime::operator-(const TrackedTime& other) const { + return Duration(ms_ - other.ms_); +} + +TrackedTime TrackedTime::operator+(const Duration& other) const { + return TrackedTime(ms_ + other.ms_); +} + +bool TrackedTime::is_null() const { return ms_ == 0; } + +} // namespace tracked_objects
diff --git a/src/base/profiler/tracked_time.h b/src/base/profiler/tracked_time.h new file mode 100644 index 0000000..372c753 --- /dev/null +++ b/src/base/profiler/tracked_time.h
@@ -0,0 +1,71 @@ +// 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_PROFILER_TRACKED_TIME_H_ +#define BASE_PROFILER_TRACKED_TIME_H_ + + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/time.h" + +namespace tracked_objects { + +//------------------------------------------------------------------------------ + +// TimeTicks maintains a wasteful 64 bits of data (we need less than 32), and on +// windows, a 64 bit timer is expensive to even obtain. We use a simple +// millisecond counter for most of our time values, as well as millisecond units +// of duration between those values. This means we can only handle durations +// up to 49 days (range), or 24 days (non-negative time durations). +// We only define enough methods to service the needs of the tracking classes, +// and our interfaces are modeled after what TimeTicks and TimeDelta use (so we +// can swap them into place if we want to use the "real" classes). + +class BASE_EXPORT Duration { // Similar to base::TimeDelta. + public: + Duration(); + + Duration& operator+=(const Duration& other); + Duration operator+(const Duration& other) const; + + bool operator==(const Duration& other) const; + bool operator!=(const Duration& other) const; + bool operator>(const Duration& other) const; + + static Duration FromMilliseconds(int ms); + + int32 InMilliseconds() const; + + private: + friend class TrackedTime; + explicit Duration(int32 duration); + + // Internal time is stored directly in milliseconds. + int32 ms_; +}; + +class BASE_EXPORT TrackedTime { // Similar to base::TimeTicks. + public: + TrackedTime(); + explicit TrackedTime(const base::TimeTicks& time); + + static TrackedTime Now(); + Duration operator-(const TrackedTime& other) const; + TrackedTime operator+(const Duration& other) const; + bool is_null() const; + + static TrackedTime FromMilliseconds(int32 ms) { return TrackedTime(ms); } + + private: + friend class Duration; + explicit TrackedTime(int32 ms); + + // Internal duration is stored directly in milliseconds. + uint32 ms_; +}; + +} // namespace tracked_objects + +#endif // BASE_PROFILER_TRACKED_TIME_H_
diff --git a/src/base/profiler/tracked_time_unittest.cc b/src/base/profiler/tracked_time_unittest.cc new file mode 100644 index 0000000..a36a7ed --- /dev/null +++ b/src/base/profiler/tracked_time_unittest.cc
@@ -0,0 +1,125 @@ +// 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. + +// Test of classes in tracked_time.cc + +#include "base/profiler/tracked_time.h" +#include "base/time.h" +#include "base/tracked_objects.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace tracked_objects { + +class TrackedTimeTest : public testing::Test { + protected: + TrackedTimeTest() { + // On entry, leak any database structures in case they are still in use by + // prior threads. + ThreadData::ShutdownSingleThreadedCleanup(true); + } + + virtual ~TrackedTimeTest() { + // We should not need to leak any structures we create, since we are + // single threaded, and carefully accounting for items. + ThreadData::ShutdownSingleThreadedCleanup(false); + } +}; + +TEST_F(TrackedTimeTest, TrackedTimerMilliseconds) { + // First make sure we basicallly transfer simple milliseconds values as + // expected. Most critically, things should not become null. + int32 kSomeMilliseconds = 243; // Some example times. + int64 kReallyBigMilliseconds = (1LL << 35) + kSomeMilliseconds; + + TrackedTime some = TrackedTime() + + Duration::FromMilliseconds(kSomeMilliseconds); + EXPECT_EQ(kSomeMilliseconds, (some - TrackedTime()).InMilliseconds()); + EXPECT_FALSE(some.is_null()); + + // Now create a big time, to check that it is wrapped modulo 2^32. + base::TimeTicks big = base::TimeTicks() + + base::TimeDelta::FromMilliseconds(kReallyBigMilliseconds); + EXPECT_EQ(kReallyBigMilliseconds, (big - base::TimeTicks()).InMilliseconds()); + + TrackedTime wrapped_big(big); + // Expect wrapping at 32 bits. + EXPECT_EQ(kSomeMilliseconds, (wrapped_big - TrackedTime()).InMilliseconds()); +} + +TEST_F(TrackedTimeTest, TrackedTimerDuration) { + int kFirstMilliseconds = 793; + int kSecondMilliseconds = 14889; + + Duration first = Duration::FromMilliseconds(kFirstMilliseconds); + Duration second = Duration::FromMilliseconds(kSecondMilliseconds); + + EXPECT_EQ(kFirstMilliseconds, first.InMilliseconds()); + EXPECT_EQ(kSecondMilliseconds, second.InMilliseconds()); + + Duration sum = first + second; + EXPECT_EQ(kFirstMilliseconds + kSecondMilliseconds, sum.InMilliseconds()); +} + +TEST_F(TrackedTimeTest, TrackedTimerVsTimeTicks) { + // Make sure that our 32 bit timer is aligned with the TimeTicks() timer. + + // First get a 64 bit timer (which should not be null). + base::TimeTicks ticks_before = base::TimeTicks::Now(); + EXPECT_FALSE(ticks_before.is_null()); + + // Then get a 32 bit timer that can be be null when it wraps. + TrackedTime now = TrackedTime::Now(); + + // Then get a bracketing time. + base::TimeTicks ticks_after = base::TimeTicks::Now(); + EXPECT_FALSE(ticks_after.is_null()); + + // Now make sure that we bracketed our tracked time nicely. + Duration before = now - TrackedTime(ticks_before); + EXPECT_LE(0, before.InMilliseconds()); + Duration after = now - TrackedTime(ticks_after); + EXPECT_GE(0, after.InMilliseconds()); +} + +TEST_F(TrackedTimeTest, TrackedTimerDisabled) { + // Check to be sure disabling the collection of data induces a null time + // (which we know will return much faster). + if (!ThreadData::InitializeAndSetTrackingStatus(ThreadData::DEACTIVATED)) + return; + // Since we disabled tracking, we should get a null response. + TrackedTime track_now = ThreadData::Now(); + EXPECT_TRUE(track_now.is_null()); + track_now = ThreadData::NowForStartOfRun(NULL); + EXPECT_TRUE(track_now.is_null()); + track_now = ThreadData::NowForEndOfRun(); + EXPECT_TRUE(track_now.is_null()); +} + +TEST_F(TrackedTimeTest, TrackedTimerEnabled) { + if (!ThreadData::InitializeAndSetTrackingStatus( + ThreadData::PROFILING_CHILDREN_ACTIVE)) + return; + // Make sure that when we enable tracking, we get a real timer result. + + // First get a 64 bit timer (which should not be null). + base::TimeTicks ticks_before = base::TimeTicks::Now(); + EXPECT_FALSE(ticks_before.is_null()); + + // Then get a 32 bit timer that can be null when it wraps. + // Crtical difference from the TrackedTimerVsTimeTicks test, is that we use + // ThreadData::Now(). It can sometimes return the null time. + TrackedTime now = ThreadData::Now(); + + // Then get a bracketing time. + base::TimeTicks ticks_after = base::TimeTicks::Now(); + EXPECT_FALSE(ticks_after.is_null()); + + // Now make sure that we bracketed our tracked time nicely. + Duration before = now - TrackedTime(ticks_before); + EXPECT_LE(0, before.InMilliseconds()); + Duration after = now - TrackedTime(ticks_after); + EXPECT_GE(0, after.InMilliseconds()); +} + +} // namespace tracked_objects
diff --git a/src/base/rand_util.cc b/src/base/rand_util.cc new file mode 100644 index 0000000..a9bc961 --- /dev/null +++ b/src/base/rand_util.cc
@@ -0,0 +1,80 @@ +// 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/rand_util.h" + +#include <math.h> + +#include <limits> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/string_util.h" + +namespace base { + +int RandInt(int min, int max) { + DCHECK_LE(min, max); + + uint64 range = static_cast<uint64>(max) - min + 1; + int result = min + static_cast<int>(base::RandGenerator(range)); + DCHECK_GE(result, min); + DCHECK_LE(result, max); + return result; +} + +double RandDouble() { + return BitsToOpenEndedUnitInterval(base::RandUint64()); +} + +double BitsToOpenEndedUnitInterval(uint64 bits) { + // We try to get maximum precision by masking out as many bits as will fit + // in the target type's mantissa, and raising it to an appropriate power to + // produce output in the range [0, 1). For IEEE 754 doubles, the mantissa + // is expected to accommodate 53 bits. + + COMPILE_ASSERT(std::numeric_limits<double>::radix == 2, otherwise_use_scalbn); + static const int kBits = std::numeric_limits<double>::digits; + uint64 random_bits = bits & ((GG_UINT64_C(1) << kBits) - 1); + double result = ldexp(static_cast<double>(random_bits), -1 * kBits); + DCHECK_GE(result, 0.0); + DCHECK_LT(result, 1.0); + return result; +} + +uint64 RandGenerator(uint64 range) { + DCHECK_GT(range, 0u); + // We must discard random results above this number, as they would + // make the random generator non-uniform (consider e.g. if + // MAX_UINT64 was 7 and |range| was 5, then a result of 1 would be twice + // as likely as a result of 3 or 4). + uint64 max_acceptable_value = + (std::numeric_limits<uint64>::max() / range) * range - 1; + + uint64 value; + do { + value = base::RandUint64(); + } while (value > max_acceptable_value); + + return value % range; +} + +void RandBytes(void* output, size_t output_length) { + uint64 random_int; + size_t random_int_size = sizeof(random_int); + for (size_t i = 0; i < output_length; i += random_int_size) { + random_int = base::RandUint64(); + size_t copy_count = std::min(output_length - i, random_int_size); + memcpy(((uint8*)output) + i, &random_int, copy_count); + } +} + +std::string RandBytesAsString(size_t length) { + DCHECK_GT(length, 0u); + std::string result; + RandBytes(WriteInto(&result, length + 1), length); + return result; +} + +} // namespace base
diff --git a/src/base/rand_util.h b/src/base/rand_util.h new file mode 100644 index 0000000..4f4765b --- /dev/null +++ b/src/base/rand_util.h
@@ -0,0 +1,58 @@ +// 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_RAND_UTIL_H_ +#define BASE_RAND_UTIL_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { + +// Returns a random number in range [0, kuint64max]. Thread-safe. +BASE_EXPORT uint64 RandUint64(); + +// Returns a random number between min and max (inclusive). Thread-safe. +BASE_EXPORT int RandInt(int min, int max); + +// Returns a random number in range [0, range). Thread-safe. +// +// Note that this can be used as an adapter for std::random_shuffle(): +// Given a pre-populated |std::vector<int> myvector|, shuffle it as +// std::random_shuffle(myvector.begin(), myvector.end(), base::RandGenerator); +BASE_EXPORT uint64 RandGenerator(uint64 range); + +// Returns a random double in range [0, 1). Thread-safe. +BASE_EXPORT double RandDouble(); + +// Given input |bits|, convert with maximum precision to a double in +// the range [0, 1). Thread-safe. +BASE_EXPORT double BitsToOpenEndedUnitInterval(uint64 bits); + +// Fills |output_length| bytes of |output| with random data. +// +// WARNING: +// Do not use for security-sensitive purposes. +// See crypto/ for cryptographically secure random number generation APIs. +BASE_EXPORT void RandBytes(void* output, size_t output_length); + +// Fills a string of length |length| with with random data and returns it. +// |length| should be nonzero. +// +// Note that this is a variation of |RandBytes| with a different return type. +// +// WARNING: +// Do not use for security-sensitive purposes. +// See crypto/ for cryptographically secure random number generation APIs. +BASE_EXPORT std::string RandBytesAsString(size_t length); + +#ifdef OS_POSIX +BASE_EXPORT int GetUrandomFD(); +#endif + +} // namespace base + +#endif // BASE_RAND_UTIL_H_
diff --git a/src/base/rand_util_nacl.cc b/src/base/rand_util_nacl.cc new file mode 100644 index 0000000..47450aa --- /dev/null +++ b/src/base/rand_util_nacl.cc
@@ -0,0 +1,53 @@ +// 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/rand_util.h" + +#include "base/basictypes.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "native_client/src/untrusted/irt/irt.h" + +namespace { + +class NaclRandom { + public: + NaclRandom() { + size_t result = nacl_interface_query(NACL_IRT_RANDOM_v0_1, + &random_, sizeof(random_)); + CHECK_EQ(result, sizeof(random_)); + } + + ~NaclRandom() { + } + + void GetRandomBytes(char* buffer, uint32_t num_bytes) { + while (num_bytes > 0) { + size_t nread; + int error = random_.get_random_bytes(buffer, num_bytes, &nread); + CHECK_EQ(error, 0); + CHECK_LE(nread, num_bytes); + buffer += nread; + num_bytes -= nread; + } + } + + private: + nacl_irt_random random_; +}; + +base::LazyInstance<NaclRandom>::Leaky g_nacl_random = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace base { + +uint64 RandUint64() { + uint64 result; + g_nacl_random.Pointer()->GetRandomBytes( + reinterpret_cast<char*>(&result), sizeof(result)); + return result; +} + +} // namespace base
diff --git a/src/base/rand_util_posix.cc b/src/base/rand_util_posix.cc new file mode 100644 index 0000000..d65ddae --- /dev/null +++ b/src/base/rand_util_posix.cc
@@ -0,0 +1,61 @@ +// 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/rand_util.h" + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" + +namespace { + +// We keep the file descriptor for /dev/urandom around so we don't need to +// reopen it (which is expensive), and since we may not even be able to reopen +// it if we are later put in a sandbox. This class wraps the file descriptor so +// we can use LazyInstance to handle opening it on the first access. +class URandomFd { + public: + URandomFd() { + fd_ = open("/dev/urandom", O_RDONLY); + DCHECK_GE(fd_, 0) << "Cannot open /dev/urandom: " << errno; + } + + ~URandomFd() { + close(fd_); + } + + int fd() const { return fd_; } + + private: + int fd_; +}; + +base::LazyInstance<URandomFd> g_urandom_fd = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace base { + +// NOTE: This function must be cryptographically secure. http://crbug.com/140076 +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)); + CHECK(success); + + return number; +} + +int GetUrandomFD(void) { + return g_urandom_fd.Pointer()->fd(); +} + +} // namespace base
diff --git a/src/base/rand_util_starboard.cc b/src/base/rand_util_starboard.cc new file mode 100644 index 0000000..1c1c586 --- /dev/null +++ b/src/base/rand_util_starboard.cc
@@ -0,0 +1,26 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/rand_util.h" + +#include "starboard/system.h" + +namespace base { + +// NOTE: This function must be cryptographically secure. http://crbug.com/140076 +uint64 RandUint64() { + return SbSystemGetRandomUInt64(); +} + +} // namespace base
diff --git a/src/base/rand_util_unittest.cc b/src/base/rand_util_unittest.cc new file mode 100644 index 0000000..e0e85ec --- /dev/null +++ b/src/base/rand_util_unittest.cc
@@ -0,0 +1,122 @@ +// 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/rand_util.h" + +#include <algorithm> +#include <limits> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const int kIntMin = std::numeric_limits<int>::min(); +const int kIntMax = std::numeric_limits<int>::max(); + +} // namespace + +TEST(RandUtilTest, SameMinAndMax) { + EXPECT_EQ(base::RandInt(0, 0), 0); + EXPECT_EQ(base::RandInt(kIntMin, kIntMin), kIntMin); + EXPECT_EQ(base::RandInt(kIntMax, kIntMax), kIntMax); +} + +TEST(RandUtilTest, RandDouble) { + // Force 64-bit precision, making sure we're not in a 80-bit FPU register. + volatile double number = base::RandDouble(); + EXPECT_GT(1.0, number); + EXPECT_LE(0.0, number); +} + +TEST(RandUtilTest, RandBytes) { + const size_t buffer_size = 50; + char buffer[buffer_size]; + memset(buffer, 0, buffer_size); + base::RandBytes(buffer, buffer_size); + std::sort(buffer, buffer + buffer_size); + // Probability of occurrence of less than 25 unique bytes in 50 random bytes + // is below 10^-25. + EXPECT_GT(std::unique(buffer, buffer + buffer_size) - buffer, 25); +} + +TEST(RandUtilTest, RandBytesAsString) { + std::string random_string = base::RandBytesAsString(1); + EXPECT_EQ(1U, random_string.size()); + random_string = base::RandBytesAsString(145); + EXPECT_EQ(145U, random_string.size()); + char accumulator = 0; + for (size_t i = 0; i < random_string.size(); ++i) + accumulator |= random_string[i]; + // In theory this test can fail, but it won't before the universe dies of + // heat death. + EXPECT_NE(0, accumulator); +} + +// Make sure that it is still appropriate to use RandGenerator in conjunction +// with std::random_shuffle(). +TEST(RandUtilTest, RandGeneratorForRandomShuffle) { + EXPECT_EQ(base::RandGenerator(1), 0U); + EXPECT_LE(std::numeric_limits<ptrdiff_t>::max(), + std::numeric_limits<int64>::max()); +} + +TEST(RandUtilTest, RandGeneratorIsUniform) { + // Verify that RandGenerator has a uniform distribution. This is a + // regression test that consistently failed when RandGenerator was + // implemented this way: + // + // return base::RandUint64() % max; + // + // A degenerate case for such an implementation is e.g. a top of + // range that is 2/3rds of the way to MAX_UINT64, in which case the + // bottom half of the range would be twice as likely to occur as the + // top half. A bit of calculus care of jar@ shows that the largest + // measurable delta is when the top of the range is 3/4ths of the + // way, so that's what we use in the test. + const uint64 kTopOfRange = (std::numeric_limits<uint64>::max() / 4ULL) * 3ULL; + const uint64 kExpectedAverage = kTopOfRange / 2ULL; + const uint64 kAllowedVariance = kExpectedAverage / 50ULL; // +/- 2% + const int kMinAttempts = 1000; + const int kMaxAttempts = 1000000; + + double cumulative_average = 0.0; + int count = 0; + while (count < kMaxAttempts) { + uint64 value = base::RandGenerator(kTopOfRange); + cumulative_average = (count * cumulative_average + value) / (count + 1); + + // Don't quit too quickly for things to start converging, or we may have + // a false positive. + if (count > kMinAttempts && + kExpectedAverage - kAllowedVariance < cumulative_average && + cumulative_average < kExpectedAverage + kAllowedVariance) { + break; + } + + ++count; + } + + ASSERT_LT(count, kMaxAttempts) << "Expected average was " << + kExpectedAverage << ", average ended at " << cumulative_average; +} + +TEST(RandUtilTest, RandUint64ProducesBothValuesOfAllBits) { + // This tests to see that our underlying random generator is good + // enough, for some value of good enough. + uint64 kAllZeros = 0ULL; + uint64 kAllOnes = ~kAllZeros; + uint64 found_ones = kAllZeros; + uint64 found_zeros = kAllOnes; + + for (size_t i = 0; i < 1000; ++i) { + uint64 value = base::RandUint64(); + found_ones |= value; + found_zeros &= value; + + if (found_zeros == kAllZeros && found_ones == kAllOnes) + return; + } + + FAIL() << "Didn't achieve all bit values in maximum number of tries."; +}
diff --git a/src/base/rand_util_win.cc b/src/base/rand_util_win.cc new file mode 100644 index 0000000..391fe5b --- /dev/null +++ b/src/base/rand_util_win.cc
@@ -0,0 +1,31 @@ +// 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/rand_util.h" + +#include <stdlib.h> + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace { + +uint32 RandUint32() { + uint32 number; + CHECK_EQ(rand_s(&number), 0); + return number; +} + +} // namespace + +namespace base { + +// NOTE: This function must be cryptographically secure. http://crbug.com/140076 +uint64 RandUint64() { + uint32 first_half = RandUint32(); + uint32 second_half = RandUint32(); + return (static_cast<uint64>(first_half) << 32) + second_half; +} + +} // namespace base
diff --git a/src/base/reuse_memory.h b/src/base/reuse_memory.h new file mode 100644 index 0000000..9ef0268 --- /dev/null +++ b/src/base/reuse_memory.h
@@ -0,0 +1,87 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This class uses a static free list to reuse memory block. Note, this +// class is not multi thread safe. + +#ifndef BASE_REUSE_MEMORY_H_ +#define BASE_REUSE_MEMORY_H_ + +namespace base { + +// For base class if there is one. +#define DECLARE_REUSE_MEMORY_BASE_FREE_FUNCTION() \ + virtual bool AddToFreeList() { return false; } + +// Declear free function +#define DECLARE_REUSE_MEMORY_FREE_FUNCTION() \ + virtual bool AddToFreeList() { AddObjectToFreeList(this); return true; } + +// Utility macros +#define TryRecycleAdoptPtrAndReturn(type, ...) \ + void* data = AllocateFromFreeList(); \ + if (data) \ + return adoptPtr(new (data) type(__VA_ARGS__)); + +// Free a vector<OwnPtr>. Delete the objects which are not handled by +// AddToFreeList() +#define FreeOwnPtrVector(type, vector_var) \ + for (int _free_i = vector_var.size() - 1; _free_i >= 0; --_free_i) { \ + type* free_data = vector_var[_free_i].leakPtr(); \ + if (!free_data->AddToFreeList()) delete free_data; \ + } + +// For reusable class. Note: this class should be the last one in base +// class list. +template<typename T> +class ReuseMemory { + public: + ReuseMemory() : next_(NULL) {} + virtual ~ReuseMemory() {} + + protected: + // Allocate object from free list + static void* AllocateFromFreeList() { + if (free_list_) { // List is valid and not empty + T* data = free_list_; + free_list_ = free_list_->next_; + return data; + } + return NULL; + } + // Add object to free list + static void AddObjectToFreeList(T* obj) { + obj->next_ = free_list_; + free_list_ = obj; + } + + // Release all objects in free list + static void FreeAll() { + while (free_list_) { + T* data = free_list_; + free_list_ = free_list_->next_; + delete data; + } + } + + private: + T* next_; + static T* free_list_; +}; + +} // namespace base + +#endif // BASE_REUSE_MEMORY_H_
diff --git a/src/base/run_loop.cc b/src/base/run_loop.cc new file mode 100644 index 0000000..ebf0c27 --- /dev/null +++ b/src/base/run_loop.cc
@@ -0,0 +1,96 @@ +// 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/run_loop.h" + +#include "base/bind.h" + +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) { +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) && !defined(__LB_ANDROID__) && \ + !defined(OS_STARBOARD) + dispatcher_ = NULL; +#endif +} + +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) && !defined(__LB_ANDROID__) && \ + !defined(OS_STARBOARD) +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) { +} +#endif + +RunLoop::~RunLoop() { +} + +void RunLoop::Run() { + if (!BeforeRun()) + return; + loop_->RunHandler(); + AfterRun(); +} + +void RunLoop::RunUntilIdle() { + quit_when_idle_received_ = true; + Run(); +} + +void RunLoop::Quit() { + quit_called_ = true; + if (running_ && loop_->run_loop_ == this) { + // This is the inner-most RunLoop, so quit now. + loop_->QuitNow(); + } +} + +base::Closure RunLoop::QuitClosure() { + return base::Bind(&RunLoop::Quit, weak_factory_.GetWeakPtr()); +} + +bool RunLoop::BeforeRun() { + DCHECK(!run_called_); + run_called_ = true; + + // Allow Quit to be called before Run. + if (quit_called_) + return false; + + // Push RunLoop stack: + previous_run_loop_ = loop_->run_loop_; + run_depth_ = previous_run_loop_? previous_run_loop_->run_depth_ + 1 : 1; + loop_->run_loop_ = this; + + running_ = true; + return true; +} + +void RunLoop::AfterRun() { + running_ = false; + + // Pop RunLoop stack: + loop_->run_loop_ = previous_run_loop_; + + // Execute deferred QuitNow, if any: + if (previous_run_loop_ && previous_run_loop_->quit_called_) + loop_->QuitNow(); +} + +} // namespace base
diff --git a/src/base/run_loop.h b/src/base/run_loop.h new file mode 100644 index 0000000..59f5a41 --- /dev/null +++ b/src/base/run_loop.h
@@ -0,0 +1,135 @@ +// 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_RUN_LOOP_H_ +#define BASE_RUN_LOOP_H_ + +#include "base/base_export.h" +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop.h" + +namespace base { +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) +class MessagePumpForUI; +#endif + +#if defined(OS_STARBOARD) +class MessagePumpUIStarboard; +#endif + +#if defined(OS_IOS) +class MessagePumpUIApplication; +#endif + +// Helper class to Run a nested MessageLoop. Please do not use nested +// MessageLoops in production code! If you must, use this class instead of +// calling MessageLoop::Run/Quit directly. RunLoop::Run can only be called once +// per RunLoop lifetime. Create a RunLoop on the stack and call Run/Quit to run +// a nested MessageLoop. +class BASE_EXPORT RunLoop { + public: + RunLoop(); +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) && !defined(__LB_ANDROID__) && \ + !defined(OS_STARBOARD) + explicit RunLoop(MessageLoop::Dispatcher* dispatcher); +#endif + ~RunLoop(); + +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) && !defined(__LB_ANDROID__) && \ + !defined(OS_STARBOARD) + void set_dispatcher(MessageLoop::Dispatcher* dispatcher) { + dispatcher_ = dispatcher; + } +#endif + + // Run the current MessageLoop. This blocks until Quit is called. Before + // calling Run, be sure to grab an AsWeakPtr or the QuitClosure in order to + // stop the MessageLoop asynchronously. MessageLoop::Quit and QuitNow will + // also trigger a return from Run, but those are deprecated. + void Run(); + + // Run the current MessageLoop until it doesn't find any tasks or messages in + // the queue (it goes idle). WARNING: This may never return! Only use this + // when repeating tasks such as animated web pages have been shut down. + void RunUntilIdle(); + + bool running() const { return running_; } + + // Quit an earlier call to Run(). There can be other nested RunLoops servicing + // the same task queue (MessageLoop); Quitting one RunLoop has no bearing on + // the others. Quit can be called before, during or after Run. If called + // before Run, Run will return immediately when called. Calling Quit after the + // RunLoop has already finished running has no effect. + // + // WARNING: You must NEVER assume that a call to Quit will terminate the + // targetted message loop. If a nested message loop continues running, the + // target may NEVER terminate. It is very easy to livelock (run forever) in + // such a case. + void Quit(); + + // Convenience method to get a closure that safely calls Quit (has no effect + // if the RunLoop instance is gone). + // + // Example: + // RunLoop run_loop; + // PostTask(run_loop.QuitClosure()); + // run_loop.Run(); + base::Closure QuitClosure(); + + private: + friend class ::MessageLoop; +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) + // Android doesn't support the blocking MessageLoop::Run, so it calls + // BeforeRun and AfterRun directly. + friend class base::MessagePumpForUI; +#endif + +#if defined(OS_STARBOARD) + // Similar to Android (because it may be Android), Starboard also doesn't + // support the blocking MessageLoop::Run, so it calls BeforeRun and AfterRun + // directly. + friend class base::MessagePumpUIStarboard; +#endif + +#if defined(OS_IOS) + // iOS doesn't support the blocking MessageLoop::Run, so it calls + // BeforeRun directly. + friend class base::MessagePumpUIApplication; +#endif + + // Return false to abort the Run. + bool BeforeRun(); + void AfterRun(); + + 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_; + +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) && !defined(__LB_ANDROID__) && \ + !defined(OS_STARBOARD) + MessageLoop::Dispatcher* dispatcher_; +#endif + + // Used to count how many nested Run() invocations are on the stack. + int run_depth_; + + bool run_called_; + bool quit_called_; + bool running_; + + // Used to record that QuitWhenIdle() was called on the MessageLoop, meaning + // that we should quit Run once it becomes idle. + bool quit_when_idle_received_; + + DISALLOW_COPY_AND_ASSIGN(RunLoop); +}; + +} // namespace base + +#endif // BASE_RUN_LOOP_H_
diff --git a/src/base/safe_strerror_posix.cc b/src/base/safe_strerror_posix.cc new file mode 100644 index 0000000..012e7be --- /dev/null +++ b/src/base/safe_strerror_posix.cc
@@ -0,0 +1,119 @@ +// Copyright (c) 2006-2009 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 "build/build_config.h" +#include "base/safe_strerror_posix.h" + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#if (defined(__GLIBC__) || defined(OS_NACL)) +#define USE_HISTORICAL_STRERRO_R 1 +#else +#define USE_HISTORICAL_STRERRO_R 0 +#endif + +#if USE_HISTORICAL_STRERRO_R && defined(__GNUC__) +// GCC will complain about the unused second wrap function unless we tell it +// that we meant for them to be potentially unused, which is exactly what this +// attribute is for. +#define POSSIBLY_UNUSED __attribute__((unused)) +#else +#define POSSIBLY_UNUSED +#endif + +#if USE_HISTORICAL_STRERRO_R +// glibc has two strerror_r functions: a historical GNU-specific one that +// returns type char *, and a POSIX.1-2001 compliant one available since 2.3.4 +// that returns int. This wraps the GNU-specific one. +static void POSSIBLY_UNUSED wrap_posix_strerror_r( + char *(*strerror_r_ptr)(int, char *, size_t), + int err, + char *buf, + size_t len) { + // GNU version. + char *rc = (*strerror_r_ptr)(err, buf, len); + if (rc != buf) { + // glibc did not use buf and returned a static string instead. Copy it + // into buf. + buf[0] = '\0'; + strncat(buf, rc, len - 1); + } + // The GNU version never fails. Unknown errors get an "unknown error" message. + // The result is always null terminated. +} +#endif // USE_HISTORICAL_STRERRO_R + +// Wrapper for strerror_r functions that implement the POSIX interface. POSIX +// does not define the behaviour for some of the edge cases, so we wrap it to +// guarantee that they are handled. This is compiled on all POSIX platforms, but +// it will only be used on Linux if the POSIX strerror_r implementation is +// being used (see below). +static void POSSIBLY_UNUSED wrap_posix_strerror_r( + int (*strerror_r_ptr)(int, char *, size_t), + int err, + char *buf, + size_t len) { + int old_errno = errno; + // Have to cast since otherwise we get an error if this is the GNU version + // (but in such a scenario this function is never called). Sadly we can't use + // C++-style casts because the appropriate one is reinterpret_cast but it's + // considered illegal to reinterpret_cast a type to itself, so we get an + // error in the opposite case. + int result = (*strerror_r_ptr)(err, buf, len); + if (result == 0) { + // POSIX is vague about whether the string will be terminated, although + // it indirectly implies that typically ERANGE will be returned, instead + // of truncating the string. We play it safe by always terminating the + // string explicitly. + buf[len - 1] = '\0'; + } else { + // Error. POSIX is vague about whether the return value is itself a system + // error code or something else. On Linux currently it is -1 and errno is + // set. On BSD-derived systems it is a system error and errno is unchanged. + // We try and detect which case it is so as to put as much useful info as + // we can into our message. + int strerror_error; // The error encountered in strerror + int new_errno = errno; + if (new_errno != old_errno) { + // errno was changed, so probably the return value is just -1 or something + // else that doesn't provide any info, and errno is the error. + strerror_error = new_errno; + } else { + // Either the error from strerror_r was the same as the previous value, or + // errno wasn't used. Assume the latter. + strerror_error = result; + } + // snprintf truncates and always null-terminates. + snprintf(buf, + len, + "Error %d while retrieving error %d", + strerror_error, + err); + } + errno = old_errno; +} + +void safe_strerror_r(int err, char *buf, size_t len) { + if (buf == NULL || len <= 0) { + return; + } + // If using glibc (i.e., Linux), the compiler will automatically select the + // appropriate overloaded function based on the function type of strerror_r. + // The other one will be elided from the translation unit since both are + // static. +#if defined(__LB_XB1__) || defined(__LB_XB360__) || defined(__STDC_LIB_EXT1__) + strerror_s(buf, len, err); +#else + wrap_posix_strerror_r(&strerror_r, err, buf, len); +#endif +} + +std::string safe_strerror(int err) { + const int buffer_size = 256; + char buf[buffer_size]; + safe_strerror_r(err, buf, sizeof(buf)); + return std::string(buf); +}
diff --git a/src/base/safe_strerror_posix.h b/src/base/safe_strerror_posix.h new file mode 100644 index 0000000..2f77d84 --- /dev/null +++ b/src/base/safe_strerror_posix.h
@@ -0,0 +1,38 @@ +// 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. + +#ifndef BASE_SAFE_STRERROR_POSIX_H_ +#define BASE_SAFE_STRERROR_POSIX_H_ + +#include <string> + +#include "base/base_export.h" + +// BEFORE using anything from this file, first look at PLOG and friends in +// logging.h and use them instead if applicable. +// +// This file declares safe, portable alternatives to the POSIX strerror() +// function. strerror() is inherently unsafe in multi-threaded apps and should +// never be used. Doing so can cause crashes. Additionally, the thread-safe +// alternative strerror_r varies in semantics across platforms. Use these +// functions instead. + +// Thread-safe strerror function with dependable semantics that never fails. +// It will write the string form of error "err" to buffer buf of length len. +// If there is an error calling the OS's strerror_r() function then a message to +// that effect will be printed into buf, truncating if necessary. The final +// result is always null-terminated. The value of errno is never changed. +// +// Use this instead of strerror_r(). +BASE_EXPORT void safe_strerror_r(int err, char *buf, size_t len); + +// Calls safe_strerror_r with a buffer of suitable size and returns the result +// in a C++ string. +// +// Use this instead of strerror(). Note though that safe_strerror_r will be +// more robust in the case of heap corruption errors, since it doesn't need to +// allocate a string. +BASE_EXPORT std::string safe_strerror(int err); + +#endif // BASE_SAFE_STRERROR_POSIX_H_
diff --git a/src/base/safe_strerror_shell.cc b/src/base/safe_strerror_shell.cc new file mode 100644 index 0000000..1f1a8ea --- /dev/null +++ b/src/base/safe_strerror_shell.cc
@@ -0,0 +1,18 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "safe_strerror_posix.cc" +
diff --git a/src/base/scoped_native_library.cc b/src/base/scoped_native_library.cc new file mode 100644 index 0000000..7290d29 --- /dev/null +++ b/src/base/scoped_native_library.cc
@@ -0,0 +1,44 @@ +// 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/scoped_native_library.h" + +namespace base { + +ScopedNativeLibrary::ScopedNativeLibrary() : library_(NULL) { +} + +ScopedNativeLibrary::ScopedNativeLibrary(NativeLibrary library) + : library_(library) { +} + +ScopedNativeLibrary::ScopedNativeLibrary(const FilePath& library_path) { + library_ = base::LoadNativeLibrary(library_path, NULL); +} + +ScopedNativeLibrary::~ScopedNativeLibrary() { + if (library_) + base::UnloadNativeLibrary(library_); +} + +void* ScopedNativeLibrary::GetFunctionPointer( + const char* function_name) const { + if (!library_) + return NULL; + return base::GetFunctionPointerFromNativeLibrary(library_, function_name); +} + +void ScopedNativeLibrary::Reset(NativeLibrary library) { + if (library_) + base::UnloadNativeLibrary(library_); + library_ = library; +} + +NativeLibrary ScopedNativeLibrary::Release() { + NativeLibrary result = library_; + library_ = NULL; + return result; +} + +} // namespace base
diff --git a/src/base/scoped_native_library.h b/src/base/scoped_native_library.h new file mode 100644 index 0000000..94380ba --- /dev/null +++ b/src/base/scoped_native_library.h
@@ -0,0 +1,52 @@ +// 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. + +#ifndef BASE_SCOPED_NATIVE_LIBRARY_H_ +#define BASE_SCOPED_NATIVE_LIBRARY_H_ + +#include "base/base_export.h" +#include "base/native_library.h" + +class FilePath; + +namespace base { + +// A class which encapsulates a base::NativeLibrary object available only in a +// scope. +// This class automatically unloads the loaded library in its destructor. +class BASE_EXPORT ScopedNativeLibrary { + public: + // Initializes with a NULL library. + ScopedNativeLibrary(); + + // Takes ownership of the given library handle. + explicit ScopedNativeLibrary(NativeLibrary library); + + // Opens the given library and manages its lifetime. + explicit ScopedNativeLibrary(const FilePath& library_path); + + ~ScopedNativeLibrary(); + + // Returns true if there's a valid library loaded. + bool is_valid() const { return !!library_; } + + void* GetFunctionPointer(const char* function_name) const; + + // Takes ownership of the given library handle. Any existing handle will + // be freed. + void Reset(NativeLibrary library); + + // Returns the native library handle and removes it from this object. The + // caller must manage the lifetime of the handle. + NativeLibrary Release(); + + private: + NativeLibrary library_; + + DISALLOW_COPY_AND_ASSIGN(ScopedNativeLibrary); +}; + +} // namespace base + +#endif // BASE_MEMORY_NATIVE_LIBRARY_H_
diff --git a/src/base/scoped_native_library_unittest.cc b/src/base/scoped_native_library_unittest.cc new file mode 100644 index 0000000..7efffdd --- /dev/null +++ b/src/base/scoped_native_library_unittest.cc
@@ -0,0 +1,33 @@ +// 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/scoped_native_library.h" +#if defined(OS_WIN) +#include "base/file_path.h" +#endif + +#include "testing/gtest/include/gtest/gtest.h" + +// Tests whether or not a function pointer retrieved via ScopedNativeLibrary +// is available only in a scope. +TEST(ScopedNativeLibrary, Basic) { +#if defined(OS_WIN) + // 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 + // 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; + { + FilePath path(base::GetNativeLibraryName(L"ddraw")); + base::ScopedNativeLibrary library(path); + test_function = reinterpret_cast<FARPROC>( + library.GetFunctionPointer("DirectDrawCreate")); + EXPECT_EQ(0, IsBadCodePtr(test_function)); + } + EXPECT_NE(0, IsBadCodePtr(test_function)); +#endif +}
diff --git a/src/base/scoped_observer.h b/src/base/scoped_observer.h new file mode 100644 index 0000000..eae6367 --- /dev/null +++ b/src/base/scoped_observer.h
@@ -0,0 +1,54 @@ +// 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_SCOPED_OBSERVER_H_ +#define BASE_SCOPED_OBSERVER_H_ + +#include <algorithm> +#include <vector> + +#include "base/basictypes.h" + +// ScopedObserver is used to keep track of the set of sources an object has +// attached itself to as an observer. When ScopedObserver is destroyed it +// removes the object as an observer from all sources it has been added to. +template <class Source, class Observer> +class ScopedObserver { + public: + explicit ScopedObserver(Observer* observer) : observer_(observer) {} + + ~ScopedObserver() { + for (size_t i = 0; i < sources_.size(); ++i) + sources_[i]->RemoveObserver(observer_); + } + + // Adds the object passed to the constructor as an observer on |source|. + void Add(Source* source) { + sources_.push_back(source); + source->AddObserver(observer_); + } + + // Remove the object passed to the constructor as an observer from |source|. + void Remove(Source* source) { + sources_.erase(std::find(sources_.begin(), sources_.end(), source)); + source->RemoveObserver(observer_); + } + + bool IsObserving(Source* source) const { + for (size_t i = 0; i < sources_.size(); ++i) { + if (sources_[i] == source) + return true; + } + return false; + } + + private: + Observer* observer_; + + std::vector<Source*> sources_; + + DISALLOW_COPY_AND_ASSIGN(ScopedObserver); +}; + +#endif // BASE_SCOPED_OBSERVER_H_
diff --git a/src/base/sequenced_task_runner.cc b/src/base/sequenced_task_runner.cc new file mode 100644 index 0000000..00d4048 --- /dev/null +++ b/src/base/sequenced_task_runner.cc
@@ -0,0 +1,31 @@ +// 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/sequenced_task_runner.h" + +#include "base/bind.h" + +namespace base { + +bool SequencedTaskRunner::PostNonNestableTask( + const tracked_objects::Location& from_here, + const Closure& task) { + return PostNonNestableDelayedTask(from_here, task, base::TimeDelta()); +} + +bool SequencedTaskRunner::DeleteSoonInternal( + const tracked_objects::Location& from_here, + void(*deleter)(const void*), + const void* object) { + return PostNonNestableTask(from_here, Bind(deleter, object)); +} + +bool SequencedTaskRunner::ReleaseSoonInternal( + const tracked_objects::Location& from_here, + void(*releaser)(const void*), + const void* object) { + return PostNonNestableTask(from_here, Bind(releaser, object)); +} + +} // namespace base
diff --git a/src/base/sequenced_task_runner.h b/src/base/sequenced_task_runner.h new file mode 100644 index 0000000..f6ca646 --- /dev/null +++ b/src/base/sequenced_task_runner.h
@@ -0,0 +1,159 @@ +// 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_SEQUENCED_TASKRUNNER_H_ +#define BASE_SEQUENCED_TASKRUNNER_H_ + +#include "base/base_export.h" +#include "base/sequenced_task_runner_helpers.h" +#include "base/task_runner.h" + +namespace base { + +// A SequencedTaskRunner is a subclass of TaskRunner that provides +// additional guarantees on the order that tasks are started, as well +// as guarantees on when tasks are in sequence, i.e. one task finishes +// before the other one starts. +// +// Summary +// ------- +// Non-nested tasks with the same delay will run one by one in FIFO +// order. +// +// Detailed guarantees +// ------------------- +// +// SequencedTaskRunner also adds additional methods for posting +// non-nestable tasks. In general, an implementation of TaskRunner +// may expose task-running methods which are themselves callable from +// within tasks. A non-nestable task is one that is guaranteed to not +// be run from within an already-running task. Conversely, a nestable +// task (the default) is a task that can be run from within an +// already-running task. +// +// The guarantees of SequencedTaskRunner are as follows: +// +// - Given two tasks T2 and T1, T2 will start after T1 starts if: +// +// * T2 is posted after T1; and +// * T2 has equal or higher delay than T1; and +// * T2 is non-nestable or T1 is nestable. +// +// - If T2 will start after T1 starts by the above guarantee, then +// T2 will start after T1 finishes and is destroyed if: +// +// * T2 is non-nestable, or +// * T1 doesn't call any task-running methods. +// +// - If T2 will start after T1 finishes by the above guarantee, then +// all memory changes in T1 and T1's destruction will be visible +// to T2. +// +// - If T2 runs nested within T1 via a call to the task-running +// method M, then all memory changes in T1 up to the call to M +// will be visible to T2, and all memory changes in T2 will be +// visible to T1 from the return from M. +// +// Note that SequencedTaskRunner does not guarantee that tasks are run +// on a single dedicated thread, although the above guarantees provide +// most (but not all) of the same guarantees. If you do need to +// guarantee that tasks are run on a single dedicated thread, see +// SingleThreadTaskRunner (in single_thread_task_runner.h). +// +// Some corollaries to the above guarantees, assuming the tasks in +// question don't call any task-running methods: +// +// - Tasks posted via PostTask are run in FIFO order. +// +// - Tasks posted via PostNonNestableTask are run in FIFO order. +// +// - Tasks posted with the same delay and the same nestable state +// are run in FIFO order. +// +// - A list of tasks with the same nestable state posted in order of +// non-decreasing delay is run in FIFO order. +// +// - A list of tasks posted in order of non-decreasing delay with at +// most a single change in nestable state from nestable to +// non-nestable is run in FIFO order. (This is equivalent to the +// statement of the first guarantee above.) +// +// Some theoretical implementations of SequencedTaskRunner: +// +// - A SequencedTaskRunner that wraps a regular TaskRunner but makes +// sure that only one task at a time is posted to the TaskRunner, +// with appropriate memory barriers in between tasks. +// +// - A SequencedTaskRunner that, for each task, spawns a joinable +// thread to run that task and immediately quit, and then +// immediately joins that thread. +// +// - A SequencedTaskRunner that stores the list of posted tasks and +// has a method Run() that runs each runnable task in FIFO order +// that can be called from any thread, but only if another +// (non-nested) Run() call isn't already happening. +class BASE_EXPORT SequencedTaskRunner : public TaskRunner { + public: + // The two PostNonNestable*Task methods below are like their + // nestable equivalents in TaskRunner, but they guarantee that the + // posted task will not run nested within an already-running task. + // + // A simple corollary is that posting a task as non-nestable can + // only delay when the task gets run. That is, posting a task as + // non-nestable may not affect when the task gets run, or it could + // make it run later than it normally would, but it won't make it + // run earlier than it normally would. + + // TODO(akalin): Get rid of the boolean return value for the methods + // below. + + bool PostNonNestableTask(const tracked_objects::Location& from_here, + const Closure& task); + + virtual bool PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + base::TimeDelta delay) = 0; + + // Submits a non-nestable task to delete the given object. Returns + // true if the object may be deleted at some point in the future, + // and false if the object definitely will not be deleted. + template <class T> + bool DeleteSoon(const tracked_objects::Location& from_here, + const T* object) { + return + subtle::DeleteHelperInternal<T, bool>::DeleteViaSequencedTaskRunner( + this, from_here, object); + } + + // Submits a non-nestable task to release the given object. Returns + // true if the object may be released at some point in the future, + // and false if the object definitely will not be released. + template <class T> + bool ReleaseSoon(const tracked_objects::Location& from_here, + T* object) { + return + subtle::ReleaseHelperInternal<T, bool>::ReleaseViaSequencedTaskRunner( + this, from_here, object); + } + + protected: + virtual ~SequencedTaskRunner() {} + + private: + template <class T, class R> friend class subtle::DeleteHelperInternal; + template <class T, class R> friend class subtle::ReleaseHelperInternal; + + bool DeleteSoonInternal(const tracked_objects::Location& from_here, + void(*deleter)(const void*), + const void* object); + + bool ReleaseSoonInternal(const tracked_objects::Location& from_here, + void(*releaser)(const void*), + const void* object); +}; + +} // namespace base + +#endif // BASE_SEQUENCED_TASKRUNNER_H_
diff --git a/src/base/sequenced_task_runner_helpers.h b/src/base/sequenced_task_runner_helpers.h new file mode 100644 index 0000000..2d0d493 --- /dev/null +++ b/src/base/sequenced_task_runner_helpers.h
@@ -0,0 +1,112 @@ +// 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_SEQUENCED_TASK_RUNNER_HELPERS_H_ +#define BASE_SEQUENCED_TASK_RUNNER_HELPERS_H_ + +#include "base/basictypes.h" + +// TODO(akalin): Investigate whether it's possible to just have +// SequencedTaskRunner use these helpers (instead of MessageLoop). +// Then we can just move these to sequenced_task_runner.h. + +namespace tracked_objects { +class Location; +} + +namespace base { + +namespace subtle { +template <class T, class R> class DeleteHelperInternal; +template <class T, class R> class ReleaseHelperInternal; +} + +// Template helpers which use function indirection to erase T from the +// function signature while still remembering it so we can call the +// correct destructor/release function. +// +// We use this trick so we don't need to include bind.h in a header +// file like sequenced_task_runner.h. We also wrap the helpers in a +// templated class to make it easier for users of DeleteSoon to +// declare the helper as a friend. +template <class T> +class DeleteHelper { + private: + template <class T2, class R> friend class subtle::DeleteHelperInternal; + + static void DoDelete(const void* object) { + delete reinterpret_cast<const T*>(object); + } + + DISALLOW_COPY_AND_ASSIGN(DeleteHelper); +}; + +template <class T> +class ReleaseHelper { + private: + template <class T2, class R> friend class subtle::ReleaseHelperInternal; + + static void DoRelease(const void* object) { + reinterpret_cast<const T*>(object)->Release(); + } + + DISALLOW_COPY_AND_ASSIGN(ReleaseHelper); +}; + +namespace subtle { + +// An internal SequencedTaskRunner-like class helper for DeleteHelper +// and ReleaseHelper. We don't want to expose the Do*() functions +// directly directly since the void* argument makes it possible to +// pass/ an object of the wrong type to delete. Instead, we force +// callers to go through these internal helpers for type +// safety. SequencedTaskRunner-like classes which expose DeleteSoon or +// ReleaseSoon methods should friend the appropriate helper and +// implement a corresponding *Internal method with the following +// signature: +// +// bool(const tracked_objects::Location&, +// void(*function)(const void*), +// void* object) +// +// An implementation of this function should simply create a +// base::Closure from (function, object) and return the result of +// posting the task. +template <class T, class ReturnType> +class DeleteHelperInternal { + public: + template <class SequencedTaskRunnerType> + static ReturnType DeleteViaSequencedTaskRunner( + SequencedTaskRunnerType* sequenced_task_runner, + const tracked_objects::Location& from_here, + const T* object) { + return sequenced_task_runner->DeleteSoonInternal( + from_here, &DeleteHelper<T>::DoDelete, object); + } + + private: + DISALLOW_COPY_AND_ASSIGN(DeleteHelperInternal); +}; + +template <class T, class ReturnType> +class ReleaseHelperInternal { + public: + template <class SequencedTaskRunnerType> + static ReturnType ReleaseViaSequencedTaskRunner( + SequencedTaskRunnerType* sequenced_task_runner, + const tracked_objects::Location& from_here, + const T* object) { + return sequenced_task_runner->ReleaseSoonInternal( + from_here, &ReleaseHelper<T>::DoRelease, object); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ReleaseHelperInternal); +}; + +} // namespace subtle + +} // namespace base + +#endif // BASE_SEQUENCED_TASK_RUNNER_HELPERS_H_
diff --git a/src/base/sha1.h b/src/base/sha1.h new file mode 100644 index 0000000..998cccb --- /dev/null +++ b/src/base/sha1.h
@@ -0,0 +1,29 @@ +// 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. + +#ifndef BASE_SHA1_H_ +#define BASE_SHA1_H_ + +#include <string> + +#include "base/base_export.h" + +namespace base { + +// These functions perform SHA-1 operations. + +static const size_t kSHA1Length = 20; // Length in bytes of a SHA-1 hash. + +// Computes the SHA-1 hash of the input string |str| and returns the full +// hash. +BASE_EXPORT std::string SHA1HashString(const std::string& str); + +// Computes the SHA-1 hash of the |len| bytes in |data| and puts the hash +// in |hash|. |hash| must be kSHA1Length bytes long. +BASE_EXPORT void SHA1HashBytes(const unsigned char* data, size_t len, + unsigned char* hash); + +} // namespace base + +#endif // BASE_SHA1_H_
diff --git a/src/base/sha1_portable.cc b/src/base/sha1_portable.cc new file mode 100644 index 0000000..0e38f25 --- /dev/null +++ b/src/base/sha1_portable.cc
@@ -0,0 +1,217 @@ +// 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/sha1.h" + +#include <string.h> + +#include "base/basictypes.h" + +namespace base { + +// Implementation of SHA-1. Only handles data in byte-sized blocks, +// which simplifies the code a fair bit. + +// Identifier names follow notation in FIPS PUB 180-3, where you'll +// also find a description of the algorithm: +// http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf + +// Usage example: +// +// SecureHashAlgorithm sha; +// while(there is data to hash) +// sha.Update(moredata, size of data); +// sha.Final(); +// memcpy(somewhere, sha.Digest(), 20); +// +// to reuse the instance of sha, call sha.Init(); + +// TODO(jhawkins): Replace this implementation with a per-platform +// implementation using each platform's crypto library. See +// http://crbug.com/47218 + +class SecureHashAlgorithm { + public: + SecureHashAlgorithm() { Init(); } + + static const int kDigestSizeBytes; + + void Init(); + void Update(const void* data, size_t nbytes); + void Final(); + + // 20 bytes of message digest. + const unsigned char* Digest() const { + return reinterpret_cast<const unsigned char*>(H); + } + + private: + void Pad(); + void Process(); + + uint32 A, B, C, D, E; + + uint32 H[5]; + + union { + uint32 W[80]; + uint8 M[64]; + }; + + uint32 cursor; + uint32 l; +}; + +static inline uint32 f(uint32 t, uint32 B, uint32 C, uint32 D) { + if (t < 20) { + return (B & C) | ((~B) & D); + } else if (t < 40) { + return B ^ C ^ D; + } else if (t < 60) { + return (B & C) | (B & D) | (C & D); + } else { + return B ^ C ^ D; + } +} + +static inline uint32 S(uint32 n, uint32 X) { + return (X << n) | (X >> (32-n)); +} + +static inline uint32 K(uint32 t) { + if (t < 20) { + return 0x5a827999; + } else if (t < 40) { + return 0x6ed9eba1; + } else if (t < 60) { + return 0x8f1bbcdc; + } else { + return 0xca62c1d6; + } +} + +static inline void swapends(uint32* t) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + *t = ((*t & 0xff000000) >> 24) | + ((*t & 0xff0000) >> 8) | + ((*t & 0xff00) << 8) | + ((*t & 0xff) << 24); +#endif +} + +const int SecureHashAlgorithm::kDigestSizeBytes = 20; + +void SecureHashAlgorithm::Init() { + A = 0; + B = 0; + C = 0; + D = 0; + E = 0; + cursor = 0; + l = 0; + H[0] = 0x67452301; + H[1] = 0xefcdab89; + H[2] = 0x98badcfe; + H[3] = 0x10325476; + H[4] = 0xc3d2e1f0; +} + +void SecureHashAlgorithm::Final() { + Pad(); + Process(); + + for (int t = 0; t < 5; ++t) + swapends(&H[t]); +} + +void SecureHashAlgorithm::Update(const void* data, size_t nbytes) { + const uint8* d = reinterpret_cast<const uint8*>(data); + while (nbytes--) { + M[cursor++] = *d++; + if (cursor >= 64) + Process(); + l += 8; + } +} + +void SecureHashAlgorithm::Pad() { + M[cursor++] = 0x80; + + if (cursor > 64-8) { + // pad out to next block + while (cursor < 64) + M[cursor++] = 0; + + Process(); + } + + while (cursor < 64-4) + M[cursor++] = 0; + + M[64-4] = (l & 0xff000000) >> 24; + M[64-3] = (l & 0xff0000) >> 16; + M[64-2] = (l & 0xff00) >> 8; + M[64-1] = (l & 0xff); +} + +void SecureHashAlgorithm::Process() { + uint32 t; + + // Each a...e corresponds to a section in the FIPS 180-3 algorithm. + + // a. + // + // W and M are in a union, so no need to memcpy. + // memcpy(W, M, sizeof(M)); + for (t = 0; t < 16; ++t) + swapends(&W[t]); + + // b. + for (t = 16; t < 80; ++t) + W[t] = S(1, W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]); + + // c. + A = H[0]; + B = H[1]; + C = H[2]; + D = H[3]; + E = H[4]; + + // d. + for (t = 0; t < 80; ++t) { + uint32 TEMP = S(5, A) + f(t, B, C, D) + E + W[t] + K(t); + E = D; + D = C; + C = S(30, B); + B = A; + A = TEMP; + } + + // e. + H[0] += A; + H[1] += B; + H[2] += C; + H[3] += D; + H[4] += E; + + cursor = 0; +} + +std::string SHA1HashString(const std::string& str) { + char hash[SecureHashAlgorithm::kDigestSizeBytes]; + SHA1HashBytes(reinterpret_cast<const unsigned char*>(str.c_str()), + str.length(), reinterpret_cast<unsigned char*>(hash)); + return std::string(hash, SecureHashAlgorithm::kDigestSizeBytes); +} + +void SHA1HashBytes(const unsigned char* data, size_t len, + unsigned char* hash) { + SecureHashAlgorithm sha; + sha.Update(data, len); + sha.Final(); + + memcpy(hash, sha.Digest(), SecureHashAlgorithm::kDigestSizeBytes); +} + +} // namespace base
diff --git a/src/base/sha1_unittest.cc b/src/base/sha1_unittest.cc new file mode 100644 index 0000000..b29fe46 --- /dev/null +++ b/src/base/sha1_unittest.cc
@@ -0,0 +1,108 @@ +// 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/sha1.h" + +#include <string> + +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(SHA1Test, Test1) { + // Example A.1 from FIPS 180-2: one-block message. + std::string input = "abc"; + + int expected[] = { 0xa9, 0x99, 0x3e, 0x36, + 0x47, 0x06, 0x81, 0x6a, + 0xba, 0x3e, 0x25, 0x71, + 0x78, 0x50, 0xc2, 0x6c, + 0x9c, 0xd0, 0xd8, 0x9d }; + + std::string output = base::SHA1HashString(input); + for (size_t i = 0; i < base::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i] & 0xFF); +} + +TEST(SHA1Test, Test2) { + // Example A.2 from FIPS 180-2: multi-block message. + std::string input = + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + + int expected[] = { 0x84, 0x98, 0x3e, 0x44, + 0x1c, 0x3b, 0xd2, 0x6e, + 0xba, 0xae, 0x4a, 0xa1, + 0xf9, 0x51, 0x29, 0xe5, + 0xe5, 0x46, 0x70, 0xf1 }; + + std::string output = base::SHA1HashString(input); + for (size_t i = 0; i < base::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i] & 0xFF); +} + +TEST(SHA1Test, Test3) { + // Example A.3 from FIPS 180-2: long message. + std::string input(1000000, 'a'); + + int expected[] = { 0x34, 0xaa, 0x97, 0x3c, + 0xd4, 0xc4, 0xda, 0xa4, + 0xf6, 0x1e, 0xeb, 0x2b, + 0xdb, 0xad, 0x27, 0x31, + 0x65, 0x34, 0x01, 0x6f }; + + std::string output = base::SHA1HashString(input); + for (size_t i = 0; i < base::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i] & 0xFF); +} + +TEST(SHA1Test, Test1Bytes) { + // Example A.1 from FIPS 180-2: one-block message. + std::string input = "abc"; + unsigned char output[base::kSHA1Length]; + + unsigned char expected[] = { 0xa9, 0x99, 0x3e, 0x36, + 0x47, 0x06, 0x81, 0x6a, + 0xba, 0x3e, 0x25, 0x71, + 0x78, 0x50, 0xc2, 0x6c, + 0x9c, 0xd0, 0xd8, 0x9d }; + + base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(input.c_str()), + input.length(), output); + for (size_t i = 0; i < base::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i]); +} + +TEST(SHA1Test, Test2Bytes) { + // Example A.2 from FIPS 180-2: multi-block message. + std::string input = + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + unsigned char output[base::kSHA1Length]; + + unsigned char expected[] = { 0x84, 0x98, 0x3e, 0x44, + 0x1c, 0x3b, 0xd2, 0x6e, + 0xba, 0xae, 0x4a, 0xa1, + 0xf9, 0x51, 0x29, 0xe5, + 0xe5, 0x46, 0x70, 0xf1 }; + + base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(input.c_str()), + input.length(), output); + for (size_t i = 0; i < base::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i]); +} + +TEST(SHA1Test, Test3Bytes) { + // Example A.3 from FIPS 180-2: long message. + std::string input(1000000, 'a'); + unsigned char output[base::kSHA1Length]; + + unsigned char expected[] = { 0x34, 0xaa, 0x97, 0x3c, + 0xd4, 0xc4, 0xda, 0xa4, + 0xf6, 0x1e, 0xeb, 0x2b, + 0xdb, 0xad, 0x27, 0x31, + 0x65, 0x34, 0x01, 0x6f }; + + base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(input.c_str()), + input.length(), output); + for (size_t i = 0; i < base::kSHA1Length; i++) + EXPECT_EQ(expected[i], output[i]); +}
diff --git a/src/base/sha1_win.cc b/src/base/sha1_win.cc new file mode 100644 index 0000000..626f41b --- /dev/null +++ b/src/base/sha1_win.cc
@@ -0,0 +1,69 @@ +// 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/sha1.h" + +#include <windows.h> +#include <wincrypt.h> + +// This file is not being compiled at the moment (see bug 47218). If we keep +// sha1 inside base, we cannot depend on src/crypto. +// #include "crypto/scoped_capi_types.h" +#include "base/logging.h" + +namespace base { + +std::string SHA1HashString(const std::string& str) { + ScopedHCRYPTPROV provider; + if (!CryptAcquireContext(provider.receive(), NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { + DLOG(ERROR) << "CryptAcquireContext failed: " << GetLastError(); + return std::string(kSHA1Length, '\0'); + } + + { + ScopedHCRYPTHASH hash; + if (!CryptCreateHash(provider, CALG_SHA1, 0, 0, hash.receive())) { + DLOG(ERROR) << "CryptCreateHash failed: " << GetLastError(); + return std::string(kSHA1Length, '\0'); + } + + if (!CryptHashData(hash, reinterpret_cast<CONST BYTE*>(str.data()), + static_cast<DWORD>(str.length()), 0)) { + DLOG(ERROR) << "CryptHashData failed: " << GetLastError(); + return std::string(kSHA1Length, '\0'); + } + + DWORD hash_len = 0; + DWORD buffer_size = sizeof hash_len; + if (!CryptGetHashParam(hash, HP_HASHSIZE, + reinterpret_cast<unsigned char*>(&hash_len), + &buffer_size, 0)) { + DLOG(ERROR) << "CryptGetHashParam(HP_HASHSIZE) failed: " + << GetLastError(); + return std::string(kSHA1Length, '\0'); + } + + std::string result; + if (!CryptGetHashParam(hash, HP_HASHVAL, + // We need the + 1 here not because the call will write a trailing \0, + // but so that result.length() is correctly set to |hash_len|. + reinterpret_cast<BYTE*>(WriteInto(&result, hash_len + 1)), &hash_len, + 0))) { + DLOG(ERROR) << "CryptGetHashParam(HP_HASHVAL) failed: " + << GetLastError(); + return std::string(kSHA1Length, '\0'); + } + + if (hash_len != kSHA1Length) { + DLOG(ERROR) << "Returned hash value is wrong length: " << hash_len + << " should be " << kSHA1Length; + return std::string(kSHA1Length, '\0'); + } + + return result; + } +} + +} // namespace base
diff --git a/src/base/shared_memory.h b/src/base/shared_memory.h new file mode 100644 index 0000000..bf77788 --- /dev/null +++ b/src/base/shared_memory.h
@@ -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. + +#ifndef BASE_SHARED_MEMORY_H_ +#define BASE_SHARED_MEMORY_H_ + +#include "build/build_config.h" + +#include <string> + +#if defined(OS_POSIX) && !defined(__LB_SHELL__) +#include <stdio.h> +#include <sys/types.h> +#include <semaphore.h> +#endif + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/process.h" + +#if defined(__LB_SHELL__) +#include <stdlib.h> +#elif defined(OS_STARBOARD) +#include "starboard/memory.h" +#endif + +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#endif + +#if defined(OS_POSIX) && !defined(__LB_SHELL__) +#include "base/file_descriptor_posix.h" +#endif + +class FilePath; + +namespace base { + +// SharedMemoryHandle is a platform specific type which represents +// the underlying OS handle to a shared memory segment. +#if defined(OS_WIN) +typedef HANDLE SharedMemoryHandle; +typedef HANDLE SharedMemoryLock; +#elif defined(OS_POSIX) && !defined(__LB_SHELL__) +// A SharedMemoryId is sufficient to identify a given shared memory segment on a +// system, but insufficient to map it. +typedef FileDescriptor SharedMemoryHandle; +typedef ino_t SharedMemoryId; +// On POSIX, the lock is implemented as a lockf() on the mapped file, +// so no additional member (or definition of SharedMemoryLock) is +// needed. +#elif defined(__LB_SHELL__) || defined(OS_STARBOARD) + +// For LB_SHELL, and Starboard, we will only ever have one process, and so we +// use standard heap memory for this. +class RefCountedMem : public base::RefCountedThreadSafe<RefCountedMem> { + public: + RefCountedMem(size_t size) { + size_ = size; +#if defined(OS_STARBOARD) + memory_ = SbMemoryAllocate(size); +#else + memory_ = malloc(size); +#endif + } + ~RefCountedMem() { +#if defined(OS_STARBOARD) + SbMemoryFree(memory_); +#else + free(memory_); +#endif + } + + void* GetMemory() const { return memory_; } + size_t GetSize() const { return size_; } + Lock& lock() { return lock_; } +private: + size_t size_; + void* memory_; + Lock lock_; +}; +typedef scoped_refptr<RefCountedMem> SharedMemoryHandle; +#endif + +// Options for creating a shared memory object. +struct SharedMemoryCreateOptions { + SharedMemoryCreateOptions() : name(NULL), size(0), open_existing(false), + executable(false) {} + + // If NULL, the object is anonymous. This pointer is owned by the caller + // and must live through the call to Create(). + const std::string* name; + + // Size of the shared memory object to be created. + // When opening an existing object, this has no effect. + size_t size; + + // If true, and the shared memory already exists, Create() will open the + // existing shared memory and ignore the size parameter. If false, + // shared memory must not exist. This flag is meaningless unless name is + // non-NULL. + bool open_existing; + + // If true, mappings might need to be made executable later. + bool executable; +}; + +// Platform abstraction for shared memory. Provides a C++ wrapper +// around the OS primitive for a memory mapped file. +class BASE_EXPORT SharedMemory { + public: + SharedMemory(); + +#if defined(OS_WIN) + // Similar to the default constructor, except that this allows for + // calling Lock() to acquire the named mutex before either Create or Open + // are called on Windows. + explicit SharedMemory(const std::wstring& name); +#endif + + // Create a new SharedMemory object from an existing, open + // shared memory file. + SharedMemory(SharedMemoryHandle handle, bool read_only); + + // Create a new SharedMemory object from an existing, open + // shared memory file that was created by a remote process and not shared + // to the current process. + SharedMemory(SharedMemoryHandle handle, bool read_only, + ProcessHandle process); + + // Closes any open files. + ~SharedMemory(); + + // Return true iff the given handle is valid (i.e. not the distingished + // invalid value; NULL for a HANDLE and -1 for a file descriptor) + static bool IsHandleValid(const SharedMemoryHandle& handle); + + // Returns invalid handle (see comment above for exact definition). + static SharedMemoryHandle NULLHandle(); + + // Closes a shared memory handle. + static void CloseHandle(const SharedMemoryHandle& handle); + + // Creates a shared memory object as described by the options struct. + // Returns true on success and false on failure. + bool Create(const SharedMemoryCreateOptions& options); + + // Creates and maps an anonymous shared memory segment of size size. + // Returns true on success and false on failure. + bool CreateAndMapAnonymous(size_t size); + + // Creates an anonymous shared memory segment of size size. + // Returns true on success and false on failure. + bool CreateAnonymous(size_t size) { + SharedMemoryCreateOptions options; + options.size = size; + return Create(options); + } + + // Creates or opens a shared memory segment based on a name. + // If open_existing is true, and the shared memory already exists, + // opens the existing shared memory and ignores the size parameter. + // If open_existing is false, shared memory must not exist. + // size is the size of the block to be created. + // Returns true on success, false on failure. + bool CreateNamed(const std::string& name, bool open_existing, size_t size) { + SharedMemoryCreateOptions options; + options.name = &name; + options.open_existing = open_existing; + options.size = size; + return Create(options); + } + + // Deletes resources associated with a shared memory segment based on name. + // Not all platforms require this call. + bool Delete(const std::string& name); + + // Opens a shared memory segment based on a name. + // If read_only is true, opens for read-only access. + // Returns true on success, false on failure. + bool Open(const std::string& name, bool read_only); + + // Maps the shared memory into the caller's address space. + // Returns true on success, false otherwise. The memory address + // is accessed via the memory() accessor. The mapped address is guaranteed to + // have an alignment of at least MAP_MINIMUM_ALIGNMENT. + bool Map(size_t bytes); + enum { MAP_MINIMUM_ALIGNMENT = 32 }; + + // Unmaps the shared memory from the caller's address space. + // Returns true if successful; returns false on error or if the + // memory is not mapped. + bool Unmap(); + + // Get the size of the shared memory backing file. + // Note: This size is only available to the creator of the + // shared memory, and not to those that opened shared memory + // created externally. + // Returns 0 if not created or unknown. + // Deprecated method, please keep track of the size yourself if you created + // it. + // http://crbug.com/60821 + size_t created_size() const { return created_size_; } + + // Gets a pointer to the opened memory space if it has been + // Mapped via Map(). Returns NULL if it is not mapped. + void *memory() const { return memory_; } + + // Returns the underlying OS handle for this segment. + // Use of this handle for anything other than an opaque + // identifier is not portable. + SharedMemoryHandle handle() const; + +#if defined(OS_POSIX) && !defined(OS_NACL) && !defined(__LB_SHELL__) + // Returns a unique identifier for this shared memory segment. Inode numbers + // are technically only unique to a single filesystem. However, we always + // allocate shared memory backing files from the same directory, so will end + // up on the same filesystem. + SharedMemoryId id() const { return inode_; } +#endif + + // Closes the open shared memory segment. + // 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 access the shared memory + // file. new_handle is an ouput 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); + } + + // Logically equivalent to: + // bool ok = ShareToProcess(process, new_handle); + // Close(); + // return ok; + // Note that the memory is unmapped by calling this method, regardless of the + // return value. + bool GiveToProcess(ProcessHandle process, + SharedMemoryHandle* new_handle) { + return ShareToProcessCommon(process, new_handle, true); + } + + // Locks the shared memory. + // + // WARNING: on POSIX the memory locking primitive only works across + // processes, not across threads. The Lock method is not currently + // used in inner loops, so we protect against multiple threads in a + // critical section using a class global lock. + void Lock(); + +#if defined(OS_WIN) + // A Lock() implementation with a timeout that also allows setting + // security attributes on the mutex. sec_attr may be NULL. + // Returns true if the Lock() has been acquired, false if the timeout was + // reached. + bool Lock(uint32 timeout_ms, SECURITY_ATTRIBUTES* sec_attr); +#endif + + // Releases the shared memory lock. + void Unlock(); + + private: +#if defined(OS_POSIX) && !defined(OS_NACL) && !defined(__LB_SHELL__) + bool PrepareMapFile(FILE *fp); + bool FilePathForMemoryName(const std::string& mem_name, FilePath* path); + void LockOrUnlockCommon(int function); +#endif + bool ShareToProcessCommon(ProcessHandle process, + SharedMemoryHandle* new_handle, + bool close_self); + +#if defined(OS_WIN) + std::wstring name_; + HANDLE mapped_file_; +#elif defined(OS_POSIX) && !defined(__LB_SHELL__) + int mapped_file_; + size_t mapped_size_; + ino_t inode_; +#elif defined(__LB_SHELL__) + scoped_refptr<RefCountedMem> handle_; +#endif + void* memory_; + bool read_only_; + size_t created_size_; +#if !defined(OS_POSIX) && !defined(OS_STARBOARD) + SharedMemoryLock lock_; +#endif + + DISALLOW_COPY_AND_ASSIGN(SharedMemory); +}; + +// A helper class that acquires the shared memory lock while +// the SharedMemoryAutoLock is in scope. +class SharedMemoryAutoLock { + public: + explicit SharedMemoryAutoLock(SharedMemory* shared_memory) + : shared_memory_(shared_memory) { + shared_memory_->Lock(); + } + + ~SharedMemoryAutoLock() { + shared_memory_->Unlock(); + } + + private: + SharedMemory* shared_memory_; + DISALLOW_COPY_AND_ASSIGN(SharedMemoryAutoLock); +}; + +} // namespace base + +#endif // BASE_SHARED_MEMORY_H_
diff --git a/src/base/shared_memory_android.cc b/src/base/shared_memory_android.cc new file mode 100644 index 0000000..e2c683c --- /dev/null +++ b/src/base/shared_memory_android.cc
@@ -0,0 +1,57 @@ +// 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/shared_memory.h" + +#include <sys/mman.h> + +#include "base/logging.h" +#include "third_party/ashmem/ashmem.h" + +namespace base { + +// For Android, we use ashmem to implement SharedMemory. ashmem_create_region +// will automatically pin the region. We never explicitly call pin/unpin. When +// all the file descriptors from different processes associated with the region +// are closed, the memory buffer will go away. + +bool SharedMemory::Create(const SharedMemoryCreateOptions& options) { + DCHECK_EQ(-1, mapped_file_ ); + + if (options.size > static_cast<size_t>(std::numeric_limits<int>::max())) + return false; + + // "name" is just a label in ashmem. It is visible in /proc/pid/maps. + mapped_file_ = ashmem_create_region( + options.name == NULL ? "" : options.name->c_str(), + options.size); + if (-1 == mapped_file_) { + DLOG(ERROR) << "Shared memory creation failed"; + return false; + } + + int err = ashmem_set_prot_region(mapped_file_, + PROT_READ | PROT_WRITE | PROT_EXEC); + if (err < 0) { + DLOG(ERROR) << "Error " << err << " when setting protection of ashmem"; + return false; + } + created_size_ = options.size; + + return true; +} + +bool SharedMemory::Delete(const std::string& name) { + // Like on Windows, this is intentionally returning true as ashmem will + // automatically releases the resource when all FDs on it are closed. + return true; +} + +bool SharedMemory::Open(const std::string& name, bool read_only) { + // ashmem doesn't support name mapping + NOTIMPLEMENTED(); + return false; +} + +} // namespace base
diff --git a/src/base/shared_memory_nacl.cc b/src/base/shared_memory_nacl.cc new file mode 100644 index 0000000..a86578b --- /dev/null +++ b/src/base/shared_memory_nacl.cc
@@ -0,0 +1,148 @@ +// 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/shared_memory.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <limits> + +#include "base/logging.h" + +namespace base { + +SharedMemory::SharedMemory() + : mapped_file_(-1), + mapped_size_(0), + inode_(0), + memory_(NULL), + read_only_(false), + created_size_(0) { +} + +SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) + : mapped_file_(handle.fd), + mapped_size_(0), + inode_(0), + memory_(NULL), + read_only_(read_only), + created_size_(0) { +} + +SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, + ProcessHandle process) + : mapped_file_(handle.fd), + mapped_size_(0), + inode_(0), + memory_(NULL), + read_only_(read_only), + created_size_(0) { + NOTREACHED(); +} + +SharedMemory::~SharedMemory() { + Close(); +} + +// static +bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) { + return handle.fd >= 0; +} + +// static +SharedMemoryHandle SharedMemory::NULLHandle() { + return SharedMemoryHandle(); +} + +// static +void SharedMemory::CloseHandle(const SharedMemoryHandle& handle) { + DCHECK_GE(handle.fd, 0); + if (close(handle.fd) < 0) + DPLOG(ERROR) << "close"; +} + +bool SharedMemory::CreateAndMapAnonymous(size_t size) { + // Untrusted code can't create descriptors or handles. + return false; +} + +bool SharedMemory::Create(const SharedMemoryCreateOptions& options) { + // Untrusted code can't create descriptors or handles. + return false; +} + +bool SharedMemory::Delete(const std::string& name) { + return false; +} + +bool SharedMemory::Open(const std::string& name, bool read_only) { + return false; +} + +bool SharedMemory::Map(size_t bytes) { + if (mapped_file_ == -1) + return false; + + if (bytes > static_cast<size_t>(std::numeric_limits<int>::max())) + return false; + + memory_ = mmap(NULL, bytes, PROT_READ | (read_only_ ? 0 : PROT_WRITE), + MAP_SHARED, mapped_file_, 0); + + bool mmap_succeeded = memory_ != MAP_FAILED && memory_ != NULL; + if (mmap_succeeded) { + mapped_size_ = bytes; + DCHECK_EQ(0U, reinterpret_cast<uintptr_t>(memory_) & + (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1)); + } else { + memory_ = NULL; + } + + return mmap_succeeded; +} + +bool SharedMemory::Unmap() { + if (memory_ == NULL) + return false; + + if (munmap(memory_, mapped_size_) < 0) + DPLOG(ERROR) << "munmap"; + memory_ = NULL; + mapped_size_ = 0; + return true; +} + +SharedMemoryHandle SharedMemory::handle() const { + return FileDescriptor(mapped_file_, false); +} + +void SharedMemory::Close() { + Unmap(); + + if (mapped_file_ > 0) { + if (close(mapped_file_) < 0) + DPLOG(ERROR) << "close"; + mapped_file_ = -1; + } +} + +void SharedMemory::Lock() { + NOTIMPLEMENTED(); +} + +void SharedMemory::Unlock() { + NOTIMPLEMENTED(); +} + +bool SharedMemory::ShareToProcessCommon(ProcessHandle process, + SharedMemoryHandle *new_handle, + bool close_self) { + return false; +} + +} // namespace base
diff --git a/src/base/shared_memory_posix.cc b/src/base/shared_memory_posix.cc new file mode 100644 index 0000000..7862f42 --- /dev/null +++ b/src/base/shared_memory_posix.cc
@@ -0,0 +1,389 @@ +// 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/shared_memory.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/platform_thread.h" +#include "base/safe_strerror_posix.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_restrictions.h" +#include "base/utf_string_conversions.h" + +#if defined(OS_MACOSX) +#include "base/mac/foundation_util.h" +#endif // OS_MACOSX + +#if defined(OS_ANDROID) +#include "base/os_compat_android.h" +#include "third_party/ashmem/ashmem.h" +#endif + +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), + mapped_size_(0), + inode_(0), + memory_(NULL), + read_only_(false), + created_size_(0) { +} + +SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) + : mapped_file_(handle.fd), + mapped_size_(0), + inode_(0), + memory_(NULL), + read_only_(read_only), + created_size_(0) { + struct stat st; + if (fstat(handle.fd, &st) == 0) { + // If fstat fails, then the file descriptor is invalid and we'll learn this + // fact when Map() fails. + inode_ = st.st_ino; + } +} + +SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, + ProcessHandle process) + : mapped_file_(handle.fd), + mapped_size_(0), + inode_(0), + memory_(NULL), + read_only_(read_only), + created_size_(0) { + // We don't handle this case yet (note the ignored parameter); let's die if + // someone comes calling. + NOTREACHED(); +} + +SharedMemory::~SharedMemory() { + Close(); +} + +// static +bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) { + return handle.fd >= 0; +} + +// static +SharedMemoryHandle SharedMemory::NULLHandle() { + return SharedMemoryHandle(); +} + +// static +void SharedMemory::CloseHandle(const SharedMemoryHandle& handle) { + DCHECK_GE(handle.fd, 0); + if (HANDLE_EINTR(close(handle.fd)) < 0) + DPLOG(ERROR) << "close"; +} + +bool SharedMemory::CreateAndMapAnonymous(size_t size) { + return CreateAnonymous(size) && Map(size); +} + +#if !defined(OS_ANDROID) +// Chromium mostly only uses the unique/private shmem as specified by +// "name == L"". The exception is in the StatsTable. +// TODO(jrg): there is no way to "clean up" all unused named shmem if +// we restart from a crash. (That isn't a new problem, but it is a problem.) +// In case we want to delete it later, it may be useful to save the value +// of mem_filename after FilePathForMemoryName(). +bool SharedMemory::Create(const SharedMemoryCreateOptions& options) { + DCHECK_EQ(-1, mapped_file_); + if (options.size == 0) return false; + + if (options.size > static_cast<size_t>(std::numeric_limits<int>::max())) + 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 *fp; + bool fix_size = true; + + FilePath path; + if (options.name == NULL || options.name->empty()) { + // It doesn't make sense to have a open-existing private piece of shmem + 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); + + // 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) + file_util::Delete(path, false); + + } else { + if (!FilePathForMemoryName(*options.name, &path)) + return false; + + fp = file_util::OpenFile(path, "w+x"); + if (fp == NULL && options.open_existing) { + // "w+" will truncate if it already exists. + fp = file_util::OpenFile(path, "a+"); + fix_size = false; + } + } + if (fp && fix_size) { + // Get current size. + struct stat stat; + if (fstat(fileno(fp), &stat) != 0) { + file_util::CloseFile(fp); + 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); + return false; + } + if (fseeko(fp, options.size, SEEK_SET) != 0) { + file_util::CloseFile(fp); + return false; + } + } + created_size_ = options.size; + } + if (fp == NULL) { +#if !defined(OS_MACOSX) + PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed"; + FilePath dir = path.DirName(); + if (access(dir.value().c_str(), W_OK | X_OK) < 0) { + PLOG(ERROR) << "Unable to access(W_OK|X_OK) " << dir.value(); + if (dir.value() == "/dev/shm") { + LOG(FATAL) << "This is frequently caused by incorrect permissions on " + << "/dev/shm. Try 'sudo chmod 1777 /dev/shm' to fix."; + } + } +#else + PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed"; +#endif + return false; + } + + return PrepareMapFile(fp); +} + +// Our current implementation of shmem is with mmap()ing of files. +// These files need to be deleted explicitly. +// In practice this call is only needed for unit tests. +bool SharedMemory::Delete(const std::string& name) { + FilePath path; + if (!FilePathForMemoryName(name, &path)) + return false; + + if (file_util::PathExists(path)) { + return file_util::Delete(path, false); + } + + // Doesn't exist, so success. + return true; +} + +bool SharedMemory::Open(const std::string& name, bool read_only) { + FilePath path; + if (!FilePathForMemoryName(name, &path)) + return false; + + read_only_ = read_only; + + const char *mode = read_only ? "r" : "r+"; + FILE *fp = file_util::OpenFile(path, mode); + return PrepareMapFile(fp); +} + +#endif // !defined(OS_ANDROID) + +bool SharedMemory::Map(size_t bytes) { + if (mapped_file_ == -1) + return false; + + if (bytes > static_cast<size_t>(std::numeric_limits<int>::max())) + return false; + +#if defined(OS_ANDROID) + if (bytes == 0) { + int ashmem_bytes = ashmem_get_size_region(mapped_file_); + if (ashmem_bytes < 0) + return false; + + DCHECK_GE(static_cast<uint32>(ashmem_bytes), bytes); + // The caller wants to determine the map region size from ashmem. + bytes = ashmem_bytes; + // TODO(port): we set the created size here so that it is available in + // transport_dib_android.cc. We should use ashmem_get_size_region() + // in transport_dib_android.cc. + created_size_ = bytes; + } +#endif + + memory_ = mmap(NULL, bytes, PROT_READ | (read_only_ ? 0 : PROT_WRITE), + MAP_SHARED, mapped_file_, 0); + + bool mmap_succeeded = memory_ != (void*)-1 && memory_ != NULL; + if (mmap_succeeded) { + mapped_size_ = bytes; + DCHECK_EQ(0U, reinterpret_cast<uintptr_t>(memory_) & + (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1)); + } else { + memory_ = NULL; + } + + return mmap_succeeded; +} + +bool SharedMemory::Unmap() { + if (memory_ == NULL) + return false; + + munmap(memory_, mapped_size_); + memory_ = NULL; + mapped_size_ = 0; + return true; +} + +SharedMemoryHandle SharedMemory::handle() const { + return FileDescriptor(mapped_file_, false); +} + +void SharedMemory::Close() { + Unmap(); + + if (mapped_file_ > 0) { + if (HANDLE_EINTR(close(mapped_file_)) < 0) + PLOG(ERROR) << "close"; + mapped_file_ = -1; + } +} + +void SharedMemory::Lock() { + g_thread_lock_.Get().Acquire(); + LockOrUnlockCommon(F_LOCK); +} + +void SharedMemory::Unlock() { + LockOrUnlockCommon(F_ULOCK); + g_thread_lock_.Get().Release(); +} + +#if !defined(OS_ANDROID) +bool SharedMemory::PrepareMapFile(FILE *fp) { + DCHECK_EQ(-1, mapped_file_); + if (fp == NULL) 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); + + mapped_file_ = dup(fileno(fp)); + if (mapped_file_ == -1) { + if (errno == EMFILE) { + LOG(WARNING) << "Shared memory creation failed; out of file descriptors"; + return false; + } else { + NOTREACHED() << "Call to dup failed, errno=" << errno; + } + } + + struct stat st; + if (fstat(mapped_file_, &st)) + NOTREACHED(); + inode_ = st.st_ino; + + return true; +} +#endif + +// For the given shmem named |mem_name|, return a filename to mmap() +// (and possibly create). Modifies |filename|. Return false on +// error, or true of we are happy. +bool SharedMemory::FilePathForMemoryName(const std::string& mem_name, + FilePath* path) { + // mem_name will be used for a filename; make sure it doesn't + // contain anything which will confuse us. + DCHECK_EQ(std::string::npos, mem_name.find('/')); + DCHECK_EQ(std::string::npos, mem_name.find('\0')); + + FilePath temp_dir; + if (!file_util::GetShmemTempDir(&temp_dir, false)) + return false; + +#if !defined(OS_MACOSX) +#if defined(GOOGLE_CHROME_BUILD) + std::string name_base = std::string("com.google.Chrome"); +#else + std::string name_base = std::string("org.chromium.Chromium"); +#endif +#else // OS_MACOSX + std::string name_base = std::string(base::mac::BaseBundleID()); +#endif // OS_MACOSX + *path = temp_dir.AppendASCII(name_base + ".shmem." + mem_name); + return true; +} + +void SharedMemory::LockOrUnlockCommon(int function) { + DCHECK_GE(mapped_file_, 0); + while (lockf(mapped_file_, function, 0) < 0) { + if (errno == EINTR) { + continue; + } else if (errno == ENOLCK) { + // temporary kernel resource exaustion + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(500)); + continue; + } else { + NOTREACHED() << "lockf() failed." + << " function:" << function + << " fd:" << mapped_file_ + << " errno:" << errno + << " msg:" << safe_strerror(errno); + } + } +} + +bool SharedMemory::ShareToProcessCommon(ProcessHandle process, + SharedMemoryHandle *new_handle, + bool close_self) { + const int new_fd = dup(mapped_file_); + if (new_fd < 0) { + DPLOG(ERROR) << "dup() failed."; + return false; + } + + new_handle->fd = new_fd; + new_handle->auto_close = true; + + if (close_self) + Close(); + + return true; +} + +} // namespace base
diff --git a/src/base/shared_memory_shell.cc b/src/base/shared_memory_shell.cc new file mode 100644 index 0000000..c5d5cae --- /dev/null +++ b/src/base/shared_memory_shell.cc
@@ -0,0 +1,107 @@ +// 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/shared_memory.h" + +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/platform_thread.h" +#include "base/safe_strerror_posix.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_restrictions.h" +#include "base/utf_string_conversions.h" + +namespace base { + +SharedMemory::SharedMemory() + : memory_(NULL), + read_only_(false), + created_size_(0) { +} + +SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) + : handle_(handle), + memory_(NULL), + read_only_(read_only), + created_size_(0) { +} + +SharedMemory::~SharedMemory() { + Close(); +} + +// static +bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) { + return handle.get() != NULL; +} + +// static +SharedMemoryHandle SharedMemory::NULLHandle() { + return SharedMemoryHandle(); +} + +// static +void SharedMemory::CloseHandle(const SharedMemoryHandle& handle) { + // Nothing to worry about here since shared memory are refcounted +} + +bool SharedMemory::CreateAndMapAnonymous(size_t size) { + return CreateAnonymous(size) && Map(size); +} + +bool SharedMemory::Create(const SharedMemoryCreateOptions& options) { + handle_ = new RefCountedMem(options.size); + + return true; +} + +bool SharedMemory::Delete(const std::string& name) { + NOTREACHED(); + return false; +} + +bool SharedMemory::Open(const std::string& name, bool read_only) { + NOTREACHED(); + return false; +} + +bool SharedMemory::Map(size_t bytes) { + DCHECK(bytes <= handle_->GetSize()); + memory_ = handle_->GetMemory(); + return true; +} + +bool SharedMemory::Unmap() { + memory_ = NULL; + return true; +} + +SharedMemoryHandle SharedMemory::handle() const { + return handle_.get(); +} + +void SharedMemory::Close() { + handle_ = NULL; +} + +void SharedMemory::Lock() { + handle_->lock().Acquire(); +} + +void SharedMemory::Unlock() { + handle_->lock().Release(); +} + +bool SharedMemory::ShareToProcessCommon(ProcessHandle process, + SharedMemoryHandle *new_handle, + bool close_self) { + // close_self doesn't matter because the shared memory instances are + // ref counted + *new_handle = handle_; + + return true; +} + +} // namespace base
diff --git a/src/base/shared_memory_unittest.cc b/src/base/shared_memory_unittest.cc new file mode 100644 index 0000000..e7f1d86 --- /dev/null +++ b/src/base/shared_memory_unittest.cc
@@ -0,0 +1,450 @@ +// 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/basictypes.h" +#if defined(OS_MACOSX) +#include "base/mac/scoped_nsautorelease_pool.h" +#endif +#include "base/memory/scoped_ptr.h" +#include "base/shared_memory.h" +#include "base/test/multiprocess_test.h" +#include "base/threading/platform_thread.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +#if defined(OS_MACOSX) +#include "base/mac/scoped_nsautorelease_pool.h" +#endif + +#if defined(OS_POSIX) +#include <sys/mman.h> +#endif + +static const int kNumThreads = 5; +static const int kNumTasks = 5; + +namespace base { + +namespace { + +// Each thread will open the shared memory. Each thread will take a different 4 +// byte int pointer, and keep changing it, with some small pauses in between. +// Verify that each thread's value in the shared memory is always correct. +class MultipleThreadMain : public PlatformThread::Delegate { + public: + explicit MultipleThreadMain(int16 id) : id_(id) {} + virtual ~MultipleThreadMain() {} + + static void CleanUp() { + SharedMemory memory; + memory.Delete(s_test_name_); + } + + // PlatformThread::Delegate interface. + virtual void ThreadMain() OVERRIDE { +#if defined(OS_MACOSX) + mac::ScopedNSAutoreleasePool pool; +#endif + const uint32 kDataSize = 1024; + SharedMemory memory; + bool rv = memory.CreateNamed(s_test_name_, true, kDataSize); + EXPECT_TRUE(rv); + rv = memory.Map(kDataSize); + EXPECT_TRUE(rv); + int *ptr = static_cast<int*>(memory.memory()) + id_; + EXPECT_EQ(0, *ptr); + + for (int idx = 0; idx < 100; idx++) { + *ptr = idx; + PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1)); + EXPECT_EQ(*ptr, idx); + } + // Reset back to 0 for the next test that uses the same name. + *ptr = 0; + + memory.Close(); + } + + private: + int16 id_; + + static const char* const s_test_name_; + + DISALLOW_COPY_AND_ASSIGN(MultipleThreadMain); +}; + +const char* const MultipleThreadMain::s_test_name_ = + "SharedMemoryOpenThreadTest"; + +// TODO(port): +// This test requires the ability to pass file descriptors between processes. +// We haven't done that yet in Chrome for POSIX. +#if defined(OS_WIN) +// Each thread will open the shared memory. Each thread will take the memory, +// and keep changing it while trying to lock it, with some small pauses in +// between. Verify that each thread's value in the shared memory is always +// correct. +class MultipleLockThread : public PlatformThread::Delegate { + public: + explicit MultipleLockThread(int id) : id_(id) {} + virtual ~MultipleLockThread() {} + + // PlatformThread::Delegate interface. + virtual void ThreadMain() OVERRIDE { + const uint32 kDataSize = sizeof(int); + SharedMemoryHandle handle = NULL; + { + SharedMemory memory1; + EXPECT_TRUE(memory1.CreateNamed("SharedMemoryMultipleLockThreadTest", + true, kDataSize)); + EXPECT_TRUE(memory1.ShareToProcess(GetCurrentProcess(), &handle)); + // TODO(paulg): Implement this once we have a posix version of + // SharedMemory::ShareToProcess. + EXPECT_TRUE(true); + } + + SharedMemory memory2(handle, false); + EXPECT_TRUE(memory2.Map(kDataSize)); + volatile int* const ptr = static_cast<int*>(memory2.memory()); + + for (int idx = 0; idx < 20; idx++) { + memory2.Lock(); + int i = (id_ << 16) + idx; + *ptr = i; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(1)); + EXPECT_EQ(*ptr, i); + memory2.Unlock(); + } + + memory2.Close(); + } + + private: + int id_; + + DISALLOW_COPY_AND_ASSIGN(MultipleLockThread); +}; +#endif + +} // namespace + +// Android doesn't support SharedMemory::Open/Delete/ +// CreateNamed(openExisting=true) +#if !defined(OS_ANDROID) +TEST(SharedMemoryTest, OpenClose) { + const uint32 kDataSize = 1024; + std::string test_name = "SharedMemoryOpenCloseTest"; + + // Open two handles to a memory segment, confirm that they are mapped + // separately yet point to the same space. + SharedMemory memory1; + bool rv = memory1.Delete(test_name); + EXPECT_TRUE(rv); + rv = memory1.Delete(test_name); + EXPECT_TRUE(rv); + rv = memory1.Open(test_name, false); + EXPECT_FALSE(rv); + rv = memory1.CreateNamed(test_name, false, kDataSize); + EXPECT_TRUE(rv); + rv = memory1.Map(kDataSize); + EXPECT_TRUE(rv); + SharedMemory memory2; + rv = memory2.Open(test_name, false); + EXPECT_TRUE(rv); + rv = memory2.Map(kDataSize); + EXPECT_TRUE(rv); + EXPECT_NE(memory1.memory(), memory2.memory()); // Compare the pointers. + + // Make sure we don't segfault. (it actually happened!) + ASSERT_NE(memory1.memory(), static_cast<void*>(NULL)); + ASSERT_NE(memory2.memory(), static_cast<void*>(NULL)); + + // Write data to the first memory segment, verify contents of second. + memset(memory1.memory(), '1', kDataSize); + EXPECT_EQ(memcmp(memory1.memory(), memory2.memory(), kDataSize), 0); + + // Close the first memory segment, and verify the second has the right data. + memory1.Close(); + char *start_ptr = static_cast<char *>(memory2.memory()); + char *end_ptr = start_ptr + kDataSize; + for (char* ptr = start_ptr; ptr < end_ptr; ptr++) + EXPECT_EQ(*ptr, '1'); + + // Close the second memory segment. + memory2.Close(); + + rv = memory1.Delete(test_name); + EXPECT_TRUE(rv); + rv = memory2.Delete(test_name); + EXPECT_TRUE(rv); +} + +TEST(SharedMemoryTest, OpenExclusive) { + const uint32 kDataSize = 1024; + const uint32 kDataSize2 = 2048; + std::ostringstream test_name_stream; + test_name_stream << "SharedMemoryOpenExclusiveTest." + << Time::Now().ToDoubleT(); + std::string test_name = test_name_stream.str(); + + // Open two handles to a memory segment and check that open_existing works + // as expected. + SharedMemory memory1; + bool rv = memory1.CreateNamed(test_name, false, kDataSize); + EXPECT_TRUE(rv); + + // Memory1 knows it's size because it created it. + EXPECT_EQ(memory1.created_size(), kDataSize); + + rv = memory1.Map(kDataSize); + EXPECT_TRUE(rv); + + memset(memory1.memory(), 'G', kDataSize); + + SharedMemory memory2; + // Should not be able to create if openExisting is false. + rv = memory2.CreateNamed(test_name, false, kDataSize2); + EXPECT_FALSE(rv); + + // Should be able to create with openExisting true. + rv = memory2.CreateNamed(test_name, true, kDataSize2); + EXPECT_TRUE(rv); + + // Memory2 shouldn't know the size because we didn't create it. + EXPECT_EQ(memory2.created_size(), 0U); + + // We should be able to map the original size. + rv = memory2.Map(kDataSize); + EXPECT_TRUE(rv); + + // Verify that opening memory2 didn't truncate or delete memory 1. + char *start_ptr = static_cast<char *>(memory2.memory()); + char *end_ptr = start_ptr + kDataSize; + for (char* ptr = start_ptr; ptr < end_ptr; ptr++) { + EXPECT_EQ(*ptr, 'G'); + } + + memory1.Close(); + memory2.Close(); + + rv = memory1.Delete(test_name); + EXPECT_TRUE(rv); +} +#endif + +// Create a set of N threads to each open a shared memory segment and write to +// it. Verify that they are always reading/writing consistent data. +TEST(SharedMemoryTest, MultipleThreads) { + MultipleThreadMain::CleanUp(); + // On POSIX we have a problem when 2 threads try to create the shmem + // (a file) at exactly the same time, since create both creates the + // file and zerofills it. We solve the problem for this unit test + // (make it not flaky) by starting with 1 thread, then + // intentionally don't clean up its shmem before running with + // kNumThreads. + + int threadcounts[] = { 1, kNumThreads }; + for (size_t i = 0; i < arraysize(threadcounts); i++) { + int numthreads = threadcounts[i]; + scoped_array<PlatformThreadHandle> thread_handles; + scoped_array<MultipleThreadMain*> thread_delegates; + + thread_handles.reset(new PlatformThreadHandle[numthreads]); + thread_delegates.reset(new MultipleThreadMain*[numthreads]); + + // Spawn the threads. + for (int16 index = 0; index < numthreads; index++) { + PlatformThreadHandle pth; + thread_delegates[index] = new MultipleThreadMain(index); + EXPECT_TRUE(PlatformThread::Create(0, thread_delegates[index], &pth)); + thread_handles[index] = pth; + } + + // Wait for the threads to finish. + for (int index = 0; index < numthreads; index++) { + PlatformThread::Join(thread_handles[index]); + delete thread_delegates[index]; + } + } + MultipleThreadMain::CleanUp(); +} + +// TODO(port): this test requires the MultipleLockThread class +// (defined above), which requires the ability to pass file +// descriptors between processes. We haven't done that yet in Chrome +// for POSIX. +#if defined(OS_WIN) +// Create a set of threads to each open a shared memory segment and write to it +// with the lock held. Verify that they are always reading/writing consistent +// data. +TEST(SharedMemoryTest, Lock) { + PlatformThreadHandle thread_handles[kNumThreads]; + MultipleLockThread* thread_delegates[kNumThreads]; + + // Spawn the threads. + for (int index = 0; index < kNumThreads; ++index) { + PlatformThreadHandle pth; + thread_delegates[index] = new MultipleLockThread(index); + EXPECT_TRUE(PlatformThread::Create(0, thread_delegates[index], &pth)); + thread_handles[index] = pth; + } + + // Wait for the threads to finish. + for (int index = 0; index < kNumThreads; ++index) { + PlatformThread::Join(thread_handles[index]); + delete thread_delegates[index]; + } +} +#endif + +// Allocate private (unique) shared memory with an empty string for a +// name. Make sure several of them don't point to the same thing as +// we might expect if the names are equal. +TEST(SharedMemoryTest, AnonymousPrivate) { + int i, j; + int count = 4; + bool rv; + const uint32 kDataSize = 8192; + + scoped_array<SharedMemory> memories(new SharedMemory[count]); + scoped_array<int*> pointers(new int*[count]); + ASSERT_TRUE(memories.get()); + ASSERT_TRUE(pointers.get()); + + for (i = 0; i < count; i++) { + rv = memories[i].CreateAndMapAnonymous(kDataSize); + EXPECT_TRUE(rv); + int *ptr = static_cast<int*>(memories[i].memory()); + EXPECT_TRUE(ptr); + pointers[i] = ptr; + } + + for (i = 0; i < count; i++) { + // zero out the first int in each except for i; for that one, make it 100. + for (j = 0; j < count; j++) { + if (i == j) + pointers[j][0] = 100; + else + pointers[j][0] = 0; + } + // make sure there is no bleeding of the 100 into the other pointers + for (j = 0; j < count; j++) { + if (i == j) + EXPECT_EQ(100, pointers[j][0]); + else + EXPECT_EQ(0, pointers[j][0]); + } + } + + for (int i = 0; i < count; i++) { + memories[i].Close(); + } +} + +#if defined(OS_POSIX) && !defined(__LB_XB1__) && !defined(__LB_XB360__) +// Create a shared memory object, mmap it, and mprotect it to PROT_EXEC. +TEST(SharedMemoryTest, AnonymousExecutable) { + const uint32 kTestSize = 1 << 16; + + SharedMemory shared_memory; + SharedMemoryCreateOptions options; + options.size = kTestSize; + options.executable = true; + + EXPECT_TRUE(shared_memory.Create(options)); + EXPECT_TRUE(shared_memory.Map(shared_memory.created_size())); + + EXPECT_EQ(0, mprotect(shared_memory.memory(), shared_memory.created_size(), + PROT_READ | PROT_EXEC)); +} +#endif + +// Map() will return addresses which are aligned to the platform page size, this +// varies from platform to platform though. Since we'd like to advertise a +// minimum alignment that callers can count on, test for it here. +TEST(SharedMemoryTest, MapMinimumAlignment) { + static const int kDataSize = 8192; + + SharedMemory shared_memory; + ASSERT_TRUE(shared_memory.CreateAndMapAnonymous(kDataSize)); + EXPECT_EQ(0U, reinterpret_cast<uintptr_t>( + shared_memory.memory()) & (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1)); + shared_memory.Close(); +} + +#if !defined(OS_IOS) // iOS does not allow multiple processes. + +// On POSIX it is especially important we test shmem across processes, +// not just across threads. But the test is enabled on all platforms. +class SharedMemoryProcessTest : public MultiProcessTest { + public: + + static void CleanUp() { + SharedMemory memory; + memory.Delete(s_test_name_); + } + + static int TaskTestMain() { + int errors = 0; +#if defined(OS_MACOSX) + mac::ScopedNSAutoreleasePool pool; +#endif + const uint32 kDataSize = 1024; + SharedMemory memory; + bool rv = memory.CreateNamed(s_test_name_, true, kDataSize); + EXPECT_TRUE(rv); + if (rv != true) + errors++; + rv = memory.Map(kDataSize); + EXPECT_TRUE(rv); + if (rv != true) + errors++; + int *ptr = static_cast<int*>(memory.memory()); + + for (int idx = 0; idx < 20; idx++) { + memory.Lock(); + int i = (1 << 16) + idx; + *ptr = i; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(10)); + if (*ptr != i) + errors++; + memory.Unlock(); + } + + memory.Close(); + return errors; + } + + private: + static const char* const s_test_name_; +}; + +const char* const SharedMemoryProcessTest::s_test_name_ = "MPMem"; + +TEST_F(SharedMemoryProcessTest, Tasks) { + SharedMemoryProcessTest::CleanUp(); + + ProcessHandle handles[kNumTasks]; + for (int index = 0; index < kNumTasks; ++index) { + handles[index] = SpawnChild("SharedMemoryTestMain", false); + ASSERT_TRUE(handles[index]); + } + + int exit_code = 0; + for (int index = 0; index < kNumTasks; ++index) { + EXPECT_TRUE(WaitForExitCode(handles[index], &exit_code)); + EXPECT_EQ(0, exit_code); + } + + SharedMemoryProcessTest::CleanUp(); +} + +MULTIPROCESS_TEST_MAIN(SharedMemoryTestMain) { + return SharedMemoryProcessTest::TaskTestMain(); +} + +#endif // !OS_IOS + +} // namespace base
diff --git a/src/base/shared_memory_win.cc b/src/base/shared_memory_win.cc new file mode 100644 index 0000000..877ccd7 --- /dev/null +++ b/src/base/shared_memory_win.cc
@@ -0,0 +1,234 @@ +// 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/shared_memory.h" + +#include "base/logging.h" +#include "base/utf_string_conversions.h" + +namespace base { + +SharedMemory::SharedMemory() + : mapped_file_(NULL), + memory_(NULL), + read_only_(false), + created_size_(0), + lock_(NULL) { +} + +SharedMemory::SharedMemory(const std::wstring& name) + : mapped_file_(NULL), + memory_(NULL), + read_only_(false), + created_size_(0), + lock_(NULL), + name_(name) { +} + +SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) + : mapped_file_(handle), + memory_(NULL), + read_only_(read_only), + created_size_(0), + lock_(NULL) { +} + +SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, + ProcessHandle process) + : mapped_file_(NULL), + memory_(NULL), + read_only_(read_only), + created_size_(0), + lock_(NULL) { + ::DuplicateHandle(process, handle, + GetCurrentProcess(), &mapped_file_, + STANDARD_RIGHTS_REQUIRED | + (read_only_ ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS), + FALSE, 0); +} + +SharedMemory::~SharedMemory() { + Close(); + if (lock_ != NULL) + CloseHandle(lock_); +} + +// static +bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) { + return handle != NULL; +} + +// static +SharedMemoryHandle SharedMemory::NULLHandle() { + return NULL; +} + +// static +void SharedMemory::CloseHandle(const SharedMemoryHandle& handle) { + DCHECK(handle != NULL); + ::CloseHandle(handle); +} + +bool SharedMemory::CreateAndMapAnonymous(size_t size) { + return CreateAnonymous(size) && Map(size); +} + +bool SharedMemory::Create(const SharedMemoryCreateOptions& options) { + DCHECK(!options.executable); + DCHECK(!mapped_file_); + if (options.size == 0) + return false; + + if (options.size > static_cast<size_t>(std::numeric_limits<int>::max())) + return false; + + // NaCl's memory allocator requires 0mod64K alignment and size for + // shared memory objects. To allow passing shared memory to NaCl, + // therefore we round the size actually created to the nearest 64K unit. + // To avoid client impact, we continue to retain the size as the + // actual requested size. + uint32 rounded_size = (options.size + 0xffff) & ~0xffff; + if (rounded_size < options.size) + return false; + name_ = ASCIIToWide(options.name == NULL ? "" : *options.name); + mapped_file_ = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, + PAGE_READWRITE, 0, static_cast<DWORD>(rounded_size), + name_.empty() ? NULL : name_.c_str()); + if (!mapped_file_) + return false; + + created_size_ = options.size; + + // Check if the shared memory pre-exists. + if (GetLastError() == ERROR_ALREADY_EXISTS) { + // If the file already existed, set created_size_ to 0 to show that + // we don't know the size. + created_size_ = 0; + if (!options.open_existing) { + Close(); + return false; + } + } + + return true; +} + +bool SharedMemory::Delete(const std::string& name) { + // intentionally empty -- there is nothing for us to do on Windows. + return true; +} + +bool SharedMemory::Open(const std::string& name, bool read_only) { + DCHECK(!mapped_file_); + + 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()); + if (mapped_file_ != NULL) { + // Note: size_ is not set in this case. + return true; + } + return false; +} + +bool SharedMemory::Map(size_t bytes) { + if (mapped_file_ == NULL) + return false; + + if (bytes > static_cast<size_t>(std::numeric_limits<int>::max())) + return false; + + memory_ = MapViewOfFile(mapped_file_, + read_only_ ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS, 0, 0, bytes); + if (memory_ != NULL) { + DCHECK_EQ(0U, reinterpret_cast<uintptr_t>(memory_) & + (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1)); + return true; + } + return false; +} + +bool SharedMemory::Unmap() { + if (memory_ == NULL) + return false; + + UnmapViewOfFile(memory_); + memory_ = NULL; + return true; +} + +bool SharedMemory::ShareToProcessCommon(ProcessHandle process, + SharedMemoryHandle *new_handle, + bool close_self) { + *new_handle = 0; + DWORD access = STANDARD_RIGHTS_REQUIRED | FILE_MAP_READ; + DWORD options = 0; + HANDLE mapped_file = mapped_file_; + HANDLE result; + if (!read_only_) + access |= FILE_MAP_WRITE; + if (close_self) { + // DUPLICATE_CLOSE_SOURCE causes DuplicateHandle to close mapped_file. + options = DUPLICATE_CLOSE_SOURCE; + mapped_file_ = NULL; + Unmap(); + } + + if (process == GetCurrentProcess() && close_self) { + *new_handle = mapped_file; + return true; + } + + if (!DuplicateHandle(GetCurrentProcess(), mapped_file, process, + &result, access, FALSE, options)) + return false; + *new_handle = result; + return true; +} + + +void SharedMemory::Close() { + if (memory_ != NULL) { + UnmapViewOfFile(memory_); + memory_ = NULL; + } + + if (mapped_file_ != NULL) { + CloseHandle(mapped_file_); + mapped_file_ = NULL; + } +} + +void SharedMemory::Lock() { + Lock(INFINITE, NULL); +} + +bool SharedMemory::Lock(uint32 timeout_ms, SECURITY_ATTRIBUTES* sec_attr) { + if (lock_ == NULL) { + std::wstring name = name_; + name.append(L"lock"); + lock_ = CreateMutex(sec_attr, FALSE, name.c_str()); + if (lock_ == NULL) { + DPLOG(ERROR) << "Could not create mutex."; + return false; // there is nothing good we can do here. + } + } + DWORD result = WaitForSingleObject(lock_, timeout_ms); + + // Return false for WAIT_ABANDONED, WAIT_TIMEOUT or WAIT_FAILED. + return (result == WAIT_OBJECT_0); +} + +void SharedMemory::Unlock() { + DCHECK(lock_ != NULL); + ReleaseMutex(lock_); +} + +SharedMemoryHandle SharedMemory::handle() const { + return mapped_file_; +} + +} // namespace base
diff --git a/src/base/single_thread_task_runner.h b/src/base/single_thread_task_runner.h new file mode 100644 index 0000000..993d829 --- /dev/null +++ b/src/base/single_thread_task_runner.h
@@ -0,0 +1,40 @@ +// 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_SINGLE_THREAD_TASK_RUNNER_H_ +#define BASE_SINGLE_THREAD_TASK_RUNNER_H_ + +#include "base/base_export.h" +#include "base/sequenced_task_runner.h" + +namespace base { + +// A SingleThreadTaskRunner is a SequencedTaskRunner with one more +// guarantee; namely, that all tasks are run on a single dedicated +// thread. Most use cases require only a SequencedTaskRunner, unless +// there is a specific need to run tasks on only a single dedicated. +// +// Some theoretical implementations of SingleThreadTaskRunner: +// +// - A SingleThreadTaskRunner that uses a single worker thread to +// run posted tasks (i.e., a message loop). +// +// - A SingleThreadTaskRunner that stores the list of posted tasks +// and has a method Run() that runs each runnable task in FIFO +// order that must be run only from the thread the +// SingleThreadTaskRunner was created on. +class BASE_EXPORT SingleThreadTaskRunner : public SequencedTaskRunner { + public: + // A more explicit alias to RunsTasksOnCurrentThread(). + bool BelongsToCurrentThread() const { + return RunsTasksOnCurrentThread(); + } + + protected: + virtual ~SingleThreadTaskRunner() {} +}; + +} // namespace base + +#endif // BASE_SERIAL_TASK_RUNNER_H_
diff --git a/src/base/state_machine_shell.cc b/src/base/state_machine_shell.cc new file mode 100644 index 0000000..29a8786 --- /dev/null +++ b/src/base/state_machine_shell.cc
@@ -0,0 +1,292 @@ +// Copyright (c) 2014 Google Inc. 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/state_machine_shell.h" + +#include "base/logging.h" + +// --- Macros for common logging idioms --- + +#define SM_STATE(s) GetStateString(s) << "(" << s << ")" +#define SM_STATEN(s) GetStateString(s) << "(" << s.value_or(0) << ")" +#define SM_EVENT(e) GetEventString(e) << "(" << e << ")" +#define SM_PREFIX \ + name_ << "-" << SM_STATEN(state_) << "v" << version_ << ": " +#define SM_DLOG(level) DLOG(level) << SM_PREFIX + +namespace base { + +StateMachineBaseShell::StateMachineBaseShell(const std::string &name) + : name_(name), + version_(0), + is_handling_(false), + is_initialized_(false), + should_log_(false) { +} + +void StateMachineBaseShell::Initialize() { + if (is_initialized_) { + return; + } + + if (should_log_) { + SM_DLOG(INFO) << "INITIALIZING"; + } + DCHECK(!state_) << SM_PREFIX; + DCHECK(!GetParentState(GetUserInitialState())) << SM_PREFIX; + is_initialized_ = true; + FollowInitialSubstates(); +} + +bool StateMachineBaseShell::IsIn(State state) const { + StateN currentState = state_; + while (currentState) { + if (state == currentState.value()) { + return true; + } + + currentState = GetParentState(currentState); + } + + return false; +} + +const char *StateMachineBaseShell::GetStateString(StateN state) const { + if (!state) { + return "<none>"; + } + + const char *user = GetUserStateString(state.value()); + if (user) { + return user; + } + + return "UNKNOWN_STATE"; +} + +const char *StateMachineBaseShell::GetEventString(EventN event) const { + if (!event) { + return "<none>"; + } + + const char *user = GetUserEventString(event.value()); + if (user) { + return user; + } + + return "UNKNOWN_EVENT"; +} + +void StateMachineBaseShell::Handle(Event event, void *data) { + Initialize(); + + if (is_handling_) { + if (should_log_) { + SM_DLOG(INFO) << "QUEUEING " << SM_EVENT(event); + } + + event_queue_.push(Bind(&StateMachineBaseShell::HandleOneEvent, Unretained(this), + event, Unretained(data))); + return; + } + + HandleOneEvent(event, data); + HandleQueuedEvents(); +} + +StateMachineBaseShell::StateN +StateMachineBaseShell::GetParentState(StateN state) const { + if (!state) { + return StateN(); + } + + return GetUserParentState(state.value()); +} + +StateMachineBaseShell::StateN +StateMachineBaseShell::GetInitialSubstate(StateN state) const { + if (!state) { + return StateN(); + } + + return GetUserInitialSubstate(state.value()); +} + +void StateMachineBaseShell::HandleQueuedEvents() { + while (!event_queue_.empty()) { + event_queue_.front().Run(); + event_queue_.pop(); + } +} + +void StateMachineBaseShell::HandleOneEvent(Event event, void *data) { + if (should_log_) { + SM_DLOG(INFO) << "HANDLING " << SM_EVENT(event); + } + + DCHECK(!is_handling_) << SM_PREFIX; + is_handling_ = true; + + // Walk up the hierarchy from the simplest current state, looking for event + // handlers, stopping at the first state that decides to handle the event. + StateN source = state_; + Result result; + while (source) { + result = HandleUserStateEvent(source.value(), event, data); + if (!result.is_handled) { + // Result was not handled, so we continue up the state chain. + source = GetParentState(source); + continue; + } + + break; + } + + // Log that the event was completely unhandled, because that's kinda weird. + // It's better if the state machine handler explicitly consumes and ignores + // events that should be ignored. + if (!result.is_handled && should_log_) { + SM_DLOG(WARNING) << "Event " << SM_EVENT(event) << " was unhandled."; + } + + // If a transition was triggered, execute it. + if (result.is_transition) { + Transition(event, source.value(), result.target.value(), + result.is_external); + } + + is_handling_ = false; +} + +// Returns the index of |state| in |path|, or -1 if not found in |path_length| +// elements. If |state| is null, then it will always return -1. +static int IndexOf(StateMachineBaseShell::StateN state, + const StateMachineBaseShell::State *path, + size_t path_length) { + if (!state) { + return -1; + } + + int i; + for (i = 0; !(path[i] == state) && i < path_length; ++i) { + } + return (i == path_length ? -1 : i); +} + +// Finds the least common ancestor between the two state paths. If there is no +// common ancestor, returns null. +static StateMachineBaseShell::StateN FindLeastCommonAncestor( + const StateMachineBaseShell::State *path_a, + size_t length_a, + const StateMachineBaseShell::State *path_b, + size_t length_b) { + size_t max = std::min(length_a, length_b); + size_t i; + for (i = 0; path_a[i] == path_b[i] && i < max; ++i) { } + return (i == 0 ? StateMachineBaseShell::StateN() : path_a[i - 1]); +} + +void StateMachineBaseShell::GetPath(State state, size_t max_depth, State *out_path, + size_t *out_depth) const { + if (max_depth == 0) { + SM_DLOG(FATAL) << "max_depth must be > 0"; + if (out_depth) { + *out_depth = 0; + } + return; + } + + size_t depth = 0; + StateN currentState = state; + while (currentState && depth < max_depth) { + out_path[depth] = currentState.value(); + ++depth; + currentState = GetParentState(currentState); + } + + // Reverse so the path is in ascending depth order. + std::reverse(out_path, out_path + depth); + if (out_depth) { + *out_depth = depth; + } +} + +void StateMachineBaseShell::Transition(Event event, State source, State target, + bool is_external) { + if (should_log_) { + SM_DLOG(INFO) << SM_EVENT(event) << " caused " << SM_STATE(source) << " -> " + << SM_STATE(target) << (is_external ? " (external)" : ""); + } + + // Define a reasonable max depth so we don't have to heap allocate. I've never + // seen an HSM as deep as whatever arbitrary value this is defined to be at + // the moment. + static const size_t kMaxDepth = 16; + State source_path[kMaxDepth]; + size_t source_depth; + GetPath(source, kMaxDepth, source_path, &source_depth); + State target_path[kMaxDepth]; + size_t target_depth; + GetPath(target, kMaxDepth, target_path, &target_depth); + StateN least_common_ancestor = + FindLeastCommonAncestor(source_path, source_depth, target_path, + target_depth); + + // External transitions must exit the source state and enter the target + // state, so if the LCA is one of the two, we need to go one level up. + if (is_external && (least_common_ancestor == source || + least_common_ancestor == target)) { + least_common_ancestor = GetParentState(least_common_ancestor); + } + + // Unwind (exit) states up to the closest common ancestor. + while (state_ && !(state_ == least_common_ancestor)) { + ExitCurrentState(); + } + + // Update version before we wind down to the target. + ++version_; + + // Wind (enter) states down to the target state. + size_t next_depth = IndexOf(state_, target_path, target_depth) + 1; + DCHECK_LE(next_depth, target_depth); + while (next_depth < target_depth) { + EnterState(target_path[next_depth]); + ++next_depth; + } + + FollowInitialSubstates(); +} + +void StateMachineBaseShell::FollowInitialSubstates() { + while (true) { + StateN substate = + (state_ ? GetInitialSubstate(state_) : GetUserInitialState()); + if (!substate) { + break; + } + EnterState(substate.value()); + } +} + +void StateMachineBaseShell::EnterState(State state) { + state_ = state; + + if (should_log_) { + SM_DLOG(INFO) << "ENTER"; + } + + HandleUserStateEnter(state); +} + +void StateMachineBaseShell::ExitCurrentState() { + if (should_log_) { + SM_DLOG(INFO) << "EXIT"; + } + + HandleUserStateExit(state_.value()); + state_ = GetParentState(state_); +} + +} // namespace base
diff --git a/src/base/state_machine_shell.h b/src/base/state_machine_shell.h new file mode 100644 index 0000000..a3e6d5b --- /dev/null +++ b/src/base/state_machine_shell.h
@@ -0,0 +1,552 @@ +// Copyright (c) 2014 Google Inc. 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_STATE_MACHINE_SHELL_H_ +#define BASE_STATE_MACHINE_SHELL_H_ + +#include <stdint.h> + +#include <algorithm> +#include <climits> +#include <queue> +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/optional.h" + +namespace base { + +// An approximate implementation of a run-to-completion (RTC) Hierarchical State +// Machine (HSM), supporting most UML Statechart semantics. +// +// Some good background information on UML Statecharts, mostly written by Miro +// Samek: +// http://en.wikipedia.org/wiki/UML_state_machine +// +// And Miro Samek's 3-part article on practical UML Statecharts: +// http://www.embedded.com/design/prototyping-and-development/4008247/A-crash-course-in-UML-state-machines-Part-1 +// +// This class does not provide any intrinsic support for "orthogonal regions," +// "extended state," or "deferred events." "Guard conditions" are easily +// implemented by the client directly in the event handler. It also minorly +// deviates from the UML Statechart specification by calling transition handlers +// before exiting the current states. This is because this implementation uses a +// simplification which combines the transition handlers and the +// state-event-transition map into a single function (HandleUserStateEvent). +// +// This class is not thread-safe. It is the user's responsibility to synchronize +// calls into the state machine, either with locks or by simply using from a +// single thread. +// +// Terse suggestions for using this class: +// * Use the templated StateMachineShell wrapper class instead of this class +// directly. +// * Define two enums, one for your states, and one for your events. +// * Subclass to define your state machine and event handlers. +// * Avoid directly exposing or passing around state machines (wrap instead). +// Handle() is not a great public interface. +// * Include your state machine in another class as a private by-value member. +// * Synchronize access to the state machine. +// * Prefer writing state machine event handlers over checking if the machine +// IsIn() a particular state. +// * Convert public methods into events, get into the state machine as quickly +// as possible. +// * You only need to define a state when you are going to leave the state +// machine in a particular condition where events can occur. +// * Create a superstate when you have an event you want to handle the same +// way for a collection of states. +// * When managing resources, create a state or superstate that represents the +// acquisition of that resource, and release the resource in the Exit +// handler. +// +// Some Definitions: +// Simple State - A State with no substates. The state machine is always +// left in exactly one Simple State. +// Composite State - A State with at least one substate. +// Guard Condition - An external condition on which an event handler +// branches. +// Run-To-Completion - A property specifying that the state machine handles +// one event at a time, and no events are handled until +// the previous event is done being handled. +// +// See the unittests for this class for a contrived example state machine +// implementation. +class BASE_EXPORT StateMachineBaseShell { + public: + // --- Nested Types and Constants --- + + typedef uint32_t State; + typedef uint32_t Event; + + typedef optional<State> StateN; + typedef optional<Event> EventN; + + // --- Public Methods --- + + // Constructor with name. The name is helpful for debugging. + explicit StateMachineBaseShell(const std::string &name); + virtual ~StateMachineBaseShell() { } + + // Enters the initial state, as specified by |GetUserInitialState()| and + // follows the initial substates down to the first simple (childless) state + // found. Idempotent. Will happen automatically on the first |Handle()| call, + // if not done explicitly. + void Initialize(); + + // Gets the name of this state machine, for logging purposes. + const char *name() const { + return name_.c_str(); + } + + // Gets a version number that increases monotonically with each state + // transition (unless it overflows |uint64_t|). This can be useful for timers + // and asynchronous events that only want to do their action if the state has + // not changed since they were queued. + // + // The state version changes exactly once per transition. In other words, it + // doesn't change for every Enter and Exit for a transition. + uint64_t version() const { + return version_; + } + + // Gets the simplest current state that this state machine is in. To check if + // the state machine is in a particular composite state, use |IsIn()|. Returns + // null if uninitialized. + StateN state() const { + return state_; + } + + // Reports whether the state machine is currently in the given state. A state + // machine is considered to be "in" the current simple state, but also "in" + // all superstates of the current simple state. + bool IsIn(State state) const; + + // Gets a printable string for the given state. + const char *GetStateString(StateN state) const; + + // Gets a printable string for the given event. + const char *GetEventString(EventN event) const; + + // Handles the given event in the context of the state machine's current + // state, executing the appropriate event handlers and performing any + // precipitate state transitions. If called reentrantly, the event will be + // queued until the current event has run to completion. + // + // |data| is a way to pass auxiliary data to the event handler. No retention + // or ref-counting is done on that data, it is simply passed through to the + // event handler. Since |Handle()| will queue the event if (and only if) + // called from an event handler, it is up to the caller to ensure the lifetime + // of whatever is passed in here. + void Handle(Event event, void *data = NULL); + + + protected: + // --- Protected Nested Types --- + + // A type that can be returned from a state-event handler, which will be + // coerced into the appropriate Result structure. + enum HandledState { + // The event handler returns this to say that the handler consume the event, + // but caused no state transition. + kHandled, + + // The event handler returns this to say that the handler did not consume + // the event, and it should bubble up to the parent state, if any. + kNotHandled, + }; + + // Structure that handlers return, allowing them to cause state transitions or + // prevent the event from bubbling. State-event handlers may just return a + // HandledState or a state to transition to, and it will be coerced into this + // structure. + struct Result { + // Default constructor is unhandled. + Result() + : is_transition(false), + is_external(false), + is_handled(false) { } + + // The no-transition constructor. Non-explicit so that the implementor of + // |HandleUserStateEvent()| just needs to return a |HandledState| and it + // does the right thing. + Result(HandledState handled) + : is_transition(false), + is_external(false), + is_handled(handled == kHandled) { } + + // The state transition constructor. This implies that the event was + // handled. Non-explicit so that the implementor of |HandleUserStateEvent()| + // just needs to return a State and it does a non-external transition. + Result(State transition_target, bool external = false) + : target(transition_target), + is_transition(true), + is_external(external), + is_handled(true) { } + + Result &operator=(HandledState rhs) { + target = base::nullopt; + is_transition = false; + is_external = false; + is_handled = (rhs == kHandled); + return *this; + } + + Result &operator=(State transition_target) { + target = transition_target; + is_transition = true; + is_external = false; + is_handled = true; + return *this; + } + + // State to transition to. Only valid if is_transition is true. + StateN target; + + // Whether this result indicates a state transition. + bool is_transition; + + // Whether the specified transition is external. Only meaningful if + // is_transition is true. + // + // For more on state transitions, see: + // http://en.wikipedia.org/wiki/UML_state_machine#Transition_execution_sequence + bool is_external; + + // Whether the event was handled by the handler. If true, consumes the + // event, and prevents bubbling up to superstates. False propagates the + // event to superstates. + bool is_handled; + }; + + + // --- Implementation Interface for Subclasses --- + + // Abstract method for subclasses to define the state hierarchy. It is + // recommended that all user-defined states be descendents of a single "top" + // state. + virtual StateN GetUserParentState(State state) const = 0; + + // Abstract method for subclasses to define the initial substate of their + // states, which is required for all complex states. If a state is a simple + // state (no substates), returns null. + virtual StateN GetUserInitialSubstate(State state) const = 0; + + // Abstract method for subclasses to define the initial (top) state of their + // state machine. This must be a state that has no parent. This state will be + // entered upon calling |Initialize()|. + virtual State GetUserInitialState() const = 0; + + // Optional method for subclasses to define strings for their states, solely + // used for debugging purposes. + virtual const char *GetUserStateString(State state) const { return NULL; } + + // Optional method for subclasses to define strings for their events, solely + // used for debugging purposes. + virtual const char *GetUserEventString(Event event) const { return NULL; } + + // Abstract method for subclasses to define handlers for events in given + // states. This method must return either: + // a) a state to transition to, meaning the event was handled and caused + // a transition + // b) |kHandled| meaning the event was handled but no transition occurred + // c) |kNotHandled|, meaning the event should bubble up to the parent state + // d) an explicit Result structure, mainly for external transitions. + // + // Implementations wishing to catch all unhandled events may do so in their + // top state. + // + // This method is generally implemented as a nested switch statement, with the + // outer switch on |state| and the inner switch on |event|. You may want to + // break this apart into per-state or per-state-event functions for + // readability and maintainability. + virtual Result HandleUserStateEvent(State state, Event event, void *data) = 0; + + // Abstract method for subclasses to define state entry behaviors. + virtual void HandleUserStateEnter(State state) = 0; + + // Abstract method for subclasses to define state exit behaviors. + virtual void HandleUserStateExit(State state) = 0; + + + // --- Helper Methods for Subclasses --- + + // Subclasses can call this method to turn on logging. Logging is opt-in, + // because it can be very verbose, and is likely only useful during + // development of a particular state machine. + void EnableLogging() { + should_log_ = true; + } + + + private: + // --- Private Helper Methods --- + + // Gets the parent state of the given state. + StateN GetParentState(StateN state) const; + + // Gets the initial substate of given state. + StateN GetInitialSubstate(StateN state) const; + + // Handles all queued events until there are no more to run. Event handlers + // may queue more events by calling |Handle()|, and this method will also + // process all precipitate events. + void HandleQueuedEvents(); + + // Workhorse method for handling a single event. Event handling is queued by + // binding the parameters to this method and queueing the resulting Closure. + void HandleOneEvent(Event event, void *data); + + // Gets the path from the Top state to the given |state|, storing it in the + // given |out_path| array, up to |max_depth| entries. If specified, + // |out_depth| will be set to the number of valid states that fit in the given + // array. + void GetPath(State state, size_t max_depth, State *out_path, + size_t *out_depth) const; + + // Transitions between the given source and target states, assuming that the + // source state is in the current state path to the Top state. The source + // state is the state whose handler generated the transition. + // + // See: http://en.wikipedia.org/wiki/UML_state_machine#Transition_execution_sequence + void Transition(Event event, State source, State target, bool is_external); + + // Follows the initial substates from the current state until it reaches a + // simple state. + void FollowInitialSubstates(); + + // Enters the given state. + void EnterState(State state); + + // Exits the current state to its parent. + void ExitCurrentState(); + + + // --- Members --- + + // The name of this state machine, for debugging purposes. + const std::string name_; + + // The current state of this state machine. Null until initialized. + StateN state_; + + // The unique version of this state machine's state, updated on every + // transition. + uint64_t version_; + + // Queue of events to handle once the current event is done being + // handled. Should always be empty unless |is_handling_| is true. + std::queue<Closure> event_queue_; + + // Whether this state machine is actively handling an event. Used to detect + // reentrant calls to |Handle()|. + bool is_handling_; + + // Whether the state machine has been initialized into its initial state + // yet. Used to make |Initialize()| idempotent. + bool is_initialized_; + + // Whether the state machine should DLOG information about state transitions. + bool should_log_; +}; + + +// A convenience template wrapper for StateMachineBaseShell. See the above class +// for complete documentation. Basically, you define your states and events as +// two enums, and then pass them as template args to this template class. Your +// state machine should then subclass this template class. It then does the work +// of casting and converting back and forth from your enums to +// StateMachineBaseShell's numeric State and Event definitions. +// +// All the methods in this class, protected and public, match the description +// and behavioral contracts of the equivalently named method in +// StateMachineBaseShell. +template <typename StateEnum, typename EventEnum> +class BASE_EXPORT StateMachineShell { + public: + // --- Nested Types and Constants --- + + typedef optional<StateEnum> StateEnumN; + typedef optional<EventEnum> EventEnumN; + + explicit StateMachineShell(const std::string &name) + : machine_(this, name) { } + virtual ~StateMachineShell() { } + + void Initialize() { + machine_.Initialize(); + } + + const char *name() const { + return machine_.name(); + } + + uint64_t version() const { + return machine_.version(); + } + + StateEnumN state() const { + BaseStateN wrappedState = machine_.state(); + return (wrappedState ? static_cast<StateEnum>(*wrappedState) + : StateEnumN()); + } + + bool IsIn(StateEnum state) const { + return machine_.IsIn(static_cast<BaseState>(state)); + } + + const char *GetStateString(StateEnumN state) const { + return machine_.GetStateString(state ? static_cast<BaseState>(*state) + : BaseStateN()); + } + + const char *GetEventString(EventEnumN event) const { + return machine_.GetEventString(event ? static_cast<BaseEvent>(*event) + : BaseEventN()); + } + + void Handle(EventEnum event, void *data = NULL) { + machine_.Handle(static_cast<BaseEvent>(event), data); + } + + protected: + // See the other HandledState in StateMachineBaseShell. + enum HandledState { + kHandled, + kNotHandled, + }; + + // See the other Result in StateMachineBaseShell. + struct Result { + // Not explicit on purpose, please see the other Result for justification. + Result(HandledState handled) + : target(), + is_transition(false), + is_external(false), + is_handled(handled == kHandled) { } + + // Not explicit on purpose, please see the other Result for justification. + Result(StateEnum transition_target, bool external = false) + : target(transition_target), + is_transition(true), + is_external(external), + is_handled(true) { } + + Result &operator=(HandledState rhs) { + target = base::nullopt; + is_transition = false; + is_external = false; + is_handled = (rhs == kHandled); + return *this; + } + + Result &operator=(StateEnum transition_target) { + target = transition_target; + is_transition = true; + is_external = false; + is_handled = true; + return *this; + } + + StateEnumN target; + bool is_transition; + bool is_external; + bool is_handled; + }; + + virtual StateEnumN GetUserParentState(StateEnum state) const = 0; + virtual StateEnumN GetUserInitialSubstate(StateEnum state) const = 0; + virtual StateEnum GetUserInitialState() const = 0; + virtual const char *GetUserStateString(StateEnum state) const { return NULL; } + virtual const char *GetUserEventString(EventEnum event) const { return NULL; } + virtual Result HandleUserStateEvent(StateEnum state, + EventEnum event, + void *data) = 0; + virtual void HandleUserStateEnter(StateEnum state) = 0; + virtual void HandleUserStateExit(StateEnum state) = 0; + + void EnableLogging() { + machine_.EnableLoggingPublic(); + } + + private: + typedef StateMachineBaseShell::State BaseState; + typedef StateMachineBaseShell::Event BaseEvent; + typedef StateMachineBaseShell::StateN BaseStateN; + typedef StateMachineBaseShell::EventN BaseEventN; + + // Private contained subclass that forwards and adapts all virtual methods + // into this class's equivalent virtual methods. + class WrappedMachine : public StateMachineBaseShell { + public: + WrappedMachine(StateMachineShell<StateEnum, EventEnum> *wrapper, + const std::string &name) + : StateMachineBaseShell(name), + wrapper_(wrapper) { + } + + StateN GetUserParentState(State state) const OVERRIDE { + StateEnumN result = + wrapper_->GetUserParentState(static_cast<StateEnum>(state)); + return (result ? static_cast<State>(*result) : StateN()); + } + + StateN GetUserInitialSubstate(State state) const OVERRIDE { + StateEnumN result = + wrapper_->GetUserInitialSubstate(static_cast<StateEnum>(state)); + return (result ? static_cast<State>(*result) : StateN()); + } + + State GetUserInitialState() const OVERRIDE { + return static_cast<State>(wrapper_->GetUserInitialState()); + } + + const char *GetUserStateString(State state) const OVERRIDE { + return wrapper_->GetUserStateString(static_cast<StateEnum>(state)); + } + + const char *GetUserEventString(Event event) const OVERRIDE { + return wrapper_->GetUserEventString(static_cast<EventEnum>(event)); + } + + Result HandleUserStateEvent(State state, Event event, void *data) OVERRIDE { + StateMachineShell<StateEnum, EventEnum>::Result result = + wrapper_->HandleUserStateEvent(static_cast<StateEnum>(state), + static_cast<EventEnum>(event), + data); + if (result.is_transition) { + return Result(static_cast<State>(*result.target), + result.is_external); + } + + return result.is_handled ? kHandled : kNotHandled; + } + + void HandleUserStateEnter(State state) OVERRIDE { + wrapper_->HandleUserStateEnter(static_cast<StateEnum>(state)); + } + + void HandleUserStateExit(State state) OVERRIDE { + wrapper_->HandleUserStateExit(static_cast<StateEnum>(state)); + } + + // A public exposure of EnableLogging so the wrapper can access it. Since + // this class is private to the wrapper, it is the only one who can see it. + void EnableLoggingPublic() { + EnableLogging(); + } + + private: + StateMachineShell<StateEnum, EventEnum> *wrapper_; + }; + + WrappedMachine machine_; +}; + +} // namespace base + +#endif // BASE_CIRCULAR_BUFFER_SHELL_H_
diff --git a/src/base/state_machine_shell_unittest.cc b/src/base/state_machine_shell_unittest.cc new file mode 100644 index 0000000..0d2bda5 --- /dev/null +++ b/src/base/state_machine_shell_unittest.cc
@@ -0,0 +1,727 @@ +// Copyright (c) 2014 Google Inc. 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/state_machine_shell.h" + +#include <list> +#include <iostream> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace hsm { + +// Constant to represent no version for TestHsm::Expect. +static const uint64_t kNoVersion = static_cast<uint64_t>(-1); + +// States for the test HSM +enum TestState { + kStateS0, + kStateS1, + kStateS11, + kStateS2, + kStateS21, + kStateS211, + kStateT0, +}; + +// Events for the test HSM +enum TestEvent { + kEventA, + kEventB, + kEventC, + kEventD, + kEventE, + kEventF, + kEventG, + kEventH, + kEventI, + kEventJ, +}; + +// An enumeration of things that the HSM does that we can sense and then +// assert about. +enum HsmEvent { + kHsmEnter, + kHsmExit, + kHsmHandled +}; + +} // namespace hsm + +// --- Test Subclass --- + +// StateMachineShell is an abstract class, so we must subclass it to test it. +// This class uses the sample test state machine specified by Miro Samek in his +// Practical Statecharts book. It covers the interesting transitions and +// topologies, so if it's fully exercised, it should represent a +// close-to-canonical test of the state machine's facilities. +// +// The diagram of this statechart is reproduced here: +// http://www.state-machine.com/resources/Heinzmann04.pdf +// +// This version has: +// - A new event, I, in state S11, to test reentrant event handling. +// - A new event, J, and new state T0, to test the no top state case. +class TestHsm : public base::StateMachineShell<hsm::TestState, hsm::TestEvent> { + public: + + // --- Some types to aid sensing --- + + struct ResultEvent { + // The state the HSM was in when it handled the event. + const StateEnumN state; + + // The data passed into the event, if any. + const void *data; + + // The state that actually handled the event (could be an ancestor of the + // current state. + const StateEnumN event_state; + + // The event that was handled. + const EventEnumN event; + + // The "HSM Event" that occurred causing this to be recorded. + const hsm::HsmEvent hsm_event; + + // The state version at the time the HsmEvent occured. + const uint64_t version; + }; + + // --- Extra state to aid sensing --- + + // Foo is used as extended state and in guard conditions in the test HSM (see + // link above). + bool foo_; + + // A counter for how many times the S11 I handler is invoked. + int event_i_count_; + + // A collection of interesting things that has happened to this state + // machine. Used to validate that the HSM behaved as expected. + std::list<ResultEvent> results; + + TestHsm() + : base::StateMachineShell<hsm::TestState, hsm::TestEvent>("TestHsm"), + foo_(true), + event_i_count_(0) { + if (VLOG_IS_ON(1)) { + EnableLogging(); + } + } + virtual ~TestHsm() { } + + // Clears the results list, so nothing bleeds between test cases. + void ClearResults() { + results.clear(); + } + + // Consumes and validates a ResultEvent from the results list. + void Expect(hsm::HsmEvent hsm_event, + StateEnumN event_state = StateEnumN(), + EventEnumN event = EventEnumN(), + StateEnumN current_state = StateEnumN(), + void *data = NULL, + uint64_t version = hsm::kNoVersion) { + VLOG(1) << __FUNCTION__ << ": hsm_event=" << hsm_event + << ", event_state=" << GetStateString(event_state) + << ", event=" << GetEventString(event) + << ", current_state=" << GetStateString(current_state) + << ", data=0x" << std::hex << data << ", version=" << version; + + EXPECT_FALSE(results.empty()); + TestHsm::ResultEvent result = results.front(); + results.pop_front(); + EXPECT_EQ(hsm_event, result.hsm_event); + if (event_state) { + EXPECT_EQ(event_state, result.event_state); + if (!current_state) { + EXPECT_EQ(event_state, result.state); + } + } + + if (current_state) { + EXPECT_EQ(current_state, result.state); + } + + if (event) { + EXPECT_EQ(event, result.event); + } + EXPECT_EQ(data, result.data); + + if (version != hsm::kNoVersion) { + EXPECT_EQ(version, result.version); + } + } + + + // --- StateMachineShell Implementation --- + protected: + virtual StateEnumN GetUserParentState(hsm::TestState state) const OVERRIDE { + switch (state) { + case hsm::kStateS1: + case hsm::kStateS2: + return hsm::kStateS0; + case hsm::kStateS11: + return hsm::kStateS1; + case hsm::kStateS21: + return hsm::kStateS2; + case hsm::kStateS211: + return hsm::kStateS21; + default: + return StateEnumN(); + } + } + + virtual StateEnumN GetUserInitialSubstate(hsm::TestState state) const OVERRIDE { + switch (state) { + case hsm::kStateS0: + return hsm::kStateS1; + case hsm::kStateS1: + return hsm::kStateS11; + case hsm::kStateS2: + return hsm::kStateS21; + case hsm::kStateS21: + return hsm::kStateS211; + default: + return StateEnumN(); + } + } + + virtual hsm::TestState GetUserInitialState() const OVERRIDE { + return hsm::kStateS0; + } + + virtual const char *GetUserStateString(hsm::TestState state) const OVERRIDE { + switch (state) { + case hsm::kStateS0: + return "S0"; + case hsm::kStateS1: + return "S1"; + case hsm::kStateS11: + return "S11"; + case hsm::kStateS2: + return "S2"; + case hsm::kStateS21: + return "S21"; + case hsm::kStateS211: + return "S211"; + case hsm::kStateT0: + return "T0"; + default: + return NULL; + } + } + + virtual const char *GetUserEventString(hsm::TestEvent event) const OVERRIDE { + switch (event) { + case hsm::kEventA: + return "A"; + case hsm::kEventB: + return "B"; + case hsm::kEventC: + return "C"; + case hsm::kEventD: + return "D"; + case hsm::kEventE: + return "E"; + case hsm::kEventF: + return "F"; + case hsm::kEventG: + return "G"; + case hsm::kEventH: + return "H"; + case hsm::kEventI: + return "I"; + case hsm::kEventJ: + return "J"; + default: + return NULL; + } + } + + virtual Result HandleUserStateEvent(hsm::TestState state, + hsm::TestEvent event, + void *data) OVERRIDE { + VLOG(1) << __FUNCTION__ << "(" << GetStateString(state) << ", " + << GetEventString(event) << ", 0x" << std::hex << data << ");"; + + Result result(kNotHandled); + switch (state) { + case hsm::kStateS0: + switch (event) { + case hsm::kEventE: + result = hsm::kStateS211; + break; + case hsm::kEventJ: + result = hsm::kStateT0; + break; + default: + // Not Handled + break; + } + break; + + case hsm::kStateS1: + switch (event) { + case hsm::kEventA: + result = Result(hsm::kStateS1, true); + break; + case hsm::kEventB: + result = hsm::kStateS11; + break; + case hsm::kEventC: + result = hsm::kStateS2; + break; + case hsm::kEventD: + result = hsm::kStateS0; + break; + case hsm::kEventF: + result = hsm::kStateS211; + break; + default: + // Not Handled + break; + } + break; + + case hsm::kStateS11: + switch (event) { + case hsm::kEventG: + result = hsm::kStateS211; + break; + case hsm::kEventH: + if (foo_) { + foo_ = false; + result = kHandled; + break; + } + break; + case hsm::kEventI: + // Inject another I every other time I is handled so every I should + // ultimately increase event_i_count_ by 2. + ++event_i_count_; + if (event_i_count_ % 2 == 1) { + // This should queue and be run before Handle() returns. + Handle(hsm::kEventI); + result = hsm::kStateS1; + break; + } else { + result = kHandled; + break; + } + break; + default: + // Not Handled + break; + } + break; + + case hsm::kStateS2: + switch (event) { + case hsm::kEventC: + result = hsm::kStateS1; + break; + case hsm::kEventF: + result = hsm::kStateS11; + break; + default: + // Not Handled + break; + } + break; + + case hsm::kStateS21: + switch (event) { + case hsm::kEventB: + result = hsm::kStateS211; + break; + case hsm::kEventH: + if (!foo_) { + foo_ = true; + result = Result(hsm::kStateS21, true); + break; + } + break; + default: + // Not Handled + break; + } + break; + + case hsm::kStateS211: + switch (event) { + case hsm::kEventD: + result = hsm::kStateS21; + break; + case hsm::kEventG: + result = hsm::kStateS0; + break; + default: + // Not Handled + break; + } + break; + + case hsm::kStateT0: + switch (event) { + case hsm::kEventJ: + result = hsm::kStateS0; + break; + default: + // Not Handled + break; + } + break; + + default: + // Not Handled + break; + } + + if (result.is_handled) { + AddEvent(state, event, data, hsm::kHsmHandled); + } + + return result; + } + + virtual void HandleUserStateEnter(hsm::TestState state) OVERRIDE { + VLOG(1) << __FUNCTION__ << "(" << GetStateString(state) << ", ENTER);"; + + AddEvent(state, EventEnumN(), NULL, hsm::kHsmEnter); + } + + virtual void HandleUserStateExit(hsm::TestState state) OVERRIDE { + VLOG(1) << __FUNCTION__ << "(" << GetStateString(state) << ", EXIT);"; + + AddEvent(state, EventEnumN(), NULL, hsm::kHsmExit); + } + + private: + // Adds a new record to the result list. + void AddEvent(hsm::TestState state, + EventEnumN event, + void *data, + hsm::HsmEvent hsm_event) { + ResultEvent result_event = { this->state(), data, state, event, hsm_event, + this->version() }; + results.push_back(result_event); + } +}; + + +// --- Test Definitions --- + +// This test validates that a state machine will initialize itself when it +// handles its first event, even if the user has not explicitly called +// initialize. +TEST(StateMachineShellTest, AutoInit) { + TestHsm hsm; + + hsm.ClearResults(); + hsm.Handle(hsm::kEventA); + + // The HSM should Auto-Initialize + hsm.Expect(hsm::kHsmEnter, hsm::kStateS0); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS11); + + // Then it should handle the event + hsm.Expect(hsm::kHsmHandled, hsm::kStateS1, hsm::kEventA, hsm::kStateS11, 0); + hsm.Expect(hsm::kHsmExit, hsm::kStateS11); + hsm.Expect(hsm::kHsmExit, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS11); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(1, hsm.version()); + EXPECT_EQ(hsm::kStateS11, hsm.state()); +} + + +// This test validates that if Handle() is called from within an event handler, +// it queues the event rather than trying to Handle the next event +// reentrantly. This behavior, or something like it, is required to make the +// state machine truly run-to-completion. +TEST(StateMachineShellTest, ReentrantHandle) { + TestHsm hsm; + hsm.Initialize(); + EXPECT_EQ(0, hsm.version()); + hsm.ClearResults(); + + // Test a Handle() inside Handle() + EXPECT_EQ(0, hsm.event_i_count_); + hsm.Handle(hsm::kEventI); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS11, hsm::kEventI, hsm::kStateS11, + NULL, 0); + hsm.Expect(hsm::kHsmExit, hsm::kStateS11); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS11); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS11, hsm::kEventI, hsm::kStateS11, + NULL, 1); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(1, hsm.version()); + EXPECT_EQ(2, hsm.event_i_count_); +} + +// This test validates that every meaningful event in every state in the test +// state machine behaves as expected. This should cover all normal operation of +// the state machine framework, except what is extracted into their own test +// cases above. +TEST(StateMachineShellTest, KitNKaboodle) { + TestHsm hsm; + + // Test the initial state + EXPECT_EQ(0, hsm.version()); + hsm.Initialize(); + EXPECT_EQ(0, hsm.version()); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS0); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS11); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateS11, hsm.state()); + + // Test IsIn + EXPECT_TRUE(hsm.IsIn(hsm::kStateS11)); + EXPECT_TRUE(hsm.IsIn(hsm::kStateS1)); + EXPECT_TRUE(hsm.IsIn(hsm::kStateS0)); + EXPECT_FALSE(hsm.IsIn(hsm::kStateS2)); + EXPECT_FALSE(hsm.IsIn(hsm::kStateS21)); + EXPECT_FALSE(hsm.IsIn(hsm::kStateS211)); + + // State: S11, Event: A + hsm.ClearResults(); + hsm.Handle(hsm::kEventA); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS1, hsm::kEventA, hsm::kStateS11, 0); + hsm.Expect(hsm::kHsmExit, hsm::kStateS11); + hsm.Expect(hsm::kHsmExit, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS11); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(1, hsm.version()); + EXPECT_EQ(hsm::kStateS11, hsm.state()); + + // State: S11, Event: B + hsm.ClearResults(); + hsm.Handle(hsm::kEventB); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS1, hsm::kEventB, hsm::kStateS11, + NULL, 1); + hsm.Expect(hsm::kHsmExit, hsm::kStateS11); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS11); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(2, hsm.version()); + EXPECT_EQ(hsm::kStateS11, hsm.state()); + + // State: S11, Event: D + hsm.ClearResults(); + hsm.Handle(hsm::kEventD); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS1, hsm::kEventD, hsm::kStateS11, + NULL, 2); + hsm.Expect(hsm::kHsmExit, hsm::kStateS11); + hsm.Expect(hsm::kHsmExit, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS11); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(3, hsm.version()); + EXPECT_EQ(hsm::kStateS11, hsm.state()); + + // State: S11, Event: H (foo == true) + hsm.ClearResults(); + EXPECT_TRUE(hsm.foo_); + hsm.Handle(hsm::kEventH); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS11, hsm::kEventH, hsm::kStateS11, + NULL, 3); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(3, hsm.version()); + EXPECT_EQ(hsm::kStateS11, hsm.state()); + EXPECT_FALSE(hsm.foo_); + + // State: S11, Event: H (foo == false) + hsm.ClearResults(); + hsm.Handle(hsm::kEventH); + EXPECT_FALSE(hsm.foo_); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(3, hsm.version()); + EXPECT_EQ(hsm::kStateS11, hsm.state()); + + // State: S11, Event: C + hsm.ClearResults(); + hsm.Handle(hsm::kEventC); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS1, hsm::kEventC, hsm::kStateS11, + NULL, 3); + hsm.Expect(hsm::kHsmExit, hsm::kStateS11); + hsm.Expect(hsm::kHsmExit, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS2); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS21); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS211); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(4, hsm.version()); + EXPECT_EQ(hsm::kStateS211, hsm.state()); + + // State: S211, Event: A + hsm.ClearResults(); + hsm.Handle(hsm::kEventA); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateS211, hsm.state()); + + // State: S211, Event: B + hsm.ClearResults(); + hsm.Handle(hsm::kEventB); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS21, hsm::kEventB, hsm::kStateS211); + hsm.Expect(hsm::kHsmExit, hsm::kStateS211); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS211); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateS211, hsm.state()); + + // State: S211, Event: D + hsm.ClearResults(); + hsm.Handle(hsm::kEventD); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS211, hsm::kEventD, hsm::kStateS211); + hsm.Expect(hsm::kHsmExit, hsm::kStateS211); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS211); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateS211, hsm.state()); + + // State: S211, Event: E + hsm.ClearResults(); + hsm.Handle(hsm::kEventE); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS0, hsm::kEventE, hsm::kStateS211); + hsm.Expect(hsm::kHsmExit, hsm::kStateS211); + hsm.Expect(hsm::kHsmExit, hsm::kStateS21); + hsm.Expect(hsm::kHsmExit, hsm::kStateS2); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS2); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS21); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS211); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateS211, hsm.state()); + + // State: S211, Event: F + hsm.ClearResults(); + hsm.Handle(hsm::kEventF); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS2, hsm::kEventF, hsm::kStateS211); + hsm.Expect(hsm::kHsmExit, hsm::kStateS211); + hsm.Expect(hsm::kHsmExit, hsm::kStateS21); + hsm.Expect(hsm::kHsmExit, hsm::kStateS2); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS11); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateS11, hsm.state()); + + // State: S11, Event: E + hsm.ClearResults(); + hsm.Handle(hsm::kEventE); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS0, hsm::kEventE, hsm::kStateS11); + hsm.Expect(hsm::kHsmExit, hsm::kStateS11); + hsm.Expect(hsm::kHsmExit, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS2); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS21); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS211); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateS211, hsm.state()); + + // State: S211, Event: G + hsm.ClearResults(); + hsm.Handle(hsm::kEventG); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS211, hsm::kEventG, hsm::kStateS211); + hsm.Expect(hsm::kHsmExit, hsm::kStateS211); + hsm.Expect(hsm::kHsmExit, hsm::kStateS21); + hsm.Expect(hsm::kHsmExit, hsm::kStateS2); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS11); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateS11, hsm.state()); + + // State: S11, Event: F + hsm.ClearResults(); + hsm.Handle(hsm::kEventF); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS1, hsm::kEventF, hsm::kStateS11); + hsm.Expect(hsm::kHsmExit, hsm::kStateS11); + hsm.Expect(hsm::kHsmExit, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS2); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS21); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS211); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateS211, hsm.state()); + + // State: S211, Event: H (foo == false) + EXPECT_FALSE(hsm.foo_); + hsm.ClearResults(); + hsm.Handle(hsm::kEventH); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS21, hsm::kEventH, hsm::kStateS211); + hsm.Expect(hsm::kHsmExit, hsm::kStateS211); + hsm.Expect(hsm::kHsmExit, hsm::kStateS21); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS21); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS211); + EXPECT_TRUE(hsm.foo_); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateS211, hsm.state()); + + // State: S211, Event: H (foo == true) + hsm.ClearResults(); + hsm.Handle(hsm::kEventH); + EXPECT_TRUE(hsm.foo_); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateS211, hsm.state()); + + // State: S211, Event: C + hsm.ClearResults(); + hsm.Handle(hsm::kEventC); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS2, hsm::kEventC, hsm::kStateS211); + hsm.Expect(hsm::kHsmExit, hsm::kStateS211); + hsm.Expect(hsm::kHsmExit, hsm::kStateS21); + hsm.Expect(hsm::kHsmExit, hsm::kStateS2); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS11); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateS11, hsm.state()); + + // State: S11, Event: G + hsm.ClearResults(); + hsm.Handle(hsm::kEventG); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS11, hsm::kEventG, hsm::kStateS11); + hsm.Expect(hsm::kHsmExit, hsm::kStateS11); + hsm.Expect(hsm::kHsmExit, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS2); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS21); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS211); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateS211, hsm.state()); + + // State: S211, Event: J + hsm.ClearResults(); + hsm.Handle(hsm::kEventJ); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS0, hsm::kEventJ, hsm::kStateS211); + hsm.Expect(hsm::kHsmExit, hsm::kStateS211); + hsm.Expect(hsm::kHsmExit, hsm::kStateS21); + hsm.Expect(hsm::kHsmExit, hsm::kStateS2); + hsm.Expect(hsm::kHsmExit, hsm::kStateS0); + hsm.Expect(hsm::kHsmEnter, hsm::kStateT0); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateT0, hsm.state()); + + // State: T0, Event: J + hsm.ClearResults(); + hsm.Handle(hsm::kEventJ); + hsm.Expect(hsm::kHsmHandled, hsm::kStateT0, hsm::kEventJ); + hsm.Expect(hsm::kHsmExit, hsm::kStateT0); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS0); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS11); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateS11, hsm.state()); + + // Test that event data gets passed through to the handler + hsm.ClearResults(); + void *data = reinterpret_cast<void *>(7); + hsm.Handle(hsm::kEventC, data); + hsm.Expect(hsm::kHsmHandled, hsm::kStateS1, hsm::kEventC, hsm::kStateS11, + data); + hsm.Expect(hsm::kHsmExit, hsm::kStateS11); + hsm.Expect(hsm::kHsmExit, hsm::kStateS1); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS2); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS21); + hsm.Expect(hsm::kHsmEnter, hsm::kStateS211); + EXPECT_EQ(0, hsm.results.size()); + EXPECT_EQ(hsm::kStateS211, hsm.state()); +}
diff --git a/src/base/stl_util.h b/src/base/stl_util.h new file mode 100644 index 0000000..c612769 --- /dev/null +++ b/src/base/stl_util.h
@@ -0,0 +1,223 @@ +// 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. + +#ifndef BASE_STL_UTIL_H_ +#define BASE_STL_UTIL_H_ + +#include <algorithm> +#include <functional> +#if defined(__LB_SHELL__) // TODO: Starboard? +#include <iterator> +#endif +#include <string> +#include <vector> + +#include "base/logging.h" + +// Clears internal memory of an STL object. +// STL clear()/reserve(0) does not always free internal memory allocated +// This function uses swap/destructor to ensure the internal memory is freed. +template<class T> +void STLClearObject(T* obj) { + T tmp; + tmp.swap(*obj); + // Sometimes "T tmp" allocates objects with memory (arena implementation?). + // Hence using additional reserve(0) even if it doesn't always work. + obj->reserve(0); +} + +// For a range within a container of pointers, calls delete (non-array version) +// on these pointers. +// NOTE: for these three functions, we could just implement a DeleteObject +// functor and then call for_each() on the range and functor, but this +// requires us to pull in all of algorithm.h, which seems expensive. +// For hash_[multi]set, it is important that this deletes behind the iterator +// because the hash_set may call the hash function on the iterator when it is +// advanced, which could result in the hash function trying to deference a +// stale pointer. +template <class ForwardIterator> +void STLDeleteContainerPointers(ForwardIterator begin, ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete *temp; + } +} + +// For a range within a container of pairs, calls delete (non-array version) on +// BOTH items in the pairs. +// NOTE: Like STLDeleteContainerPointers, it is important that this deletes +// behind the iterator because if both the key and value are deleted, the +// container may call the hash function on the iterator when it is advanced, +// which could result in the hash function trying to dereference a stale +// pointer. +template <class ForwardIterator> +void STLDeleteContainerPairPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete temp->first; + delete temp->second; + } +} + +// For a range within a container of pairs, calls delete (non-array version) on +// the FIRST item in the pairs. +// NOTE: Like STLDeleteContainerPointers, deleting behind the iterator. +template <class ForwardIterator> +void STLDeleteContainerPairFirstPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete temp->first; + } +} + +// For a range within a container of pairs, calls delete. +// NOTE: Like STLDeleteContainerPointers, deleting behind the iterator. +// Deleting the value does not always invalidate the iterator, but it may +// do so if the key is a pointer into the value object. +template <class ForwardIterator> +void STLDeleteContainerPairSecondPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete temp->second; + } +} + +// To treat a possibly-empty vector as an array, use these functions. +// If you know the array will never be empty, you can use &*v.begin() +// directly, but that is undefined behaviour if |v| is empty. +template<typename T> +inline T* vector_as_array(std::vector<T>* v) { + return v->empty() ? NULL : &*v->begin(); +} + +template<typename T> +inline const T* vector_as_array(const std::vector<T>* v) { + return v->empty() ? NULL : &*v->begin(); +} + +// Return a mutable char* pointing to a string's internal buffer, +// which may not be null-terminated. Writing through this pointer will +// modify the string. +// +// string_as_array(&str)[i] is valid for 0 <= i < str.size() until the +// next call to a string method that invalidates iterators. +// +// As of 2006-04, there is no standard-blessed way of getting a +// mutable reference to a string's internal buffer. However, issue 530 +// (http://www.open-std.org/JTC1/SC22/WG21/docs/lwg-active.html#530) +// proposes this as the method. According to Matt Austern, this should +// already work on all current implementations. +inline char* string_as_array(std::string* str) { + // DO NOT USE const_cast<char*>(str->data()) + return str->empty() ? NULL : &*str->begin(); +} + +// The following functions are useful for cleaning up STL containers whose +// elements point to allocated memory. + +// STLDeleteElements() deletes all the elements in an STL container and clears +// the container. This function is suitable for use with a vector, set, +// hash_set, or any other STL container which defines sensible begin(), end(), +// and clear() methods. +// +// If container is NULL, this function is a no-op. +// +// As an alternative to calling STLDeleteElements() directly, consider +// STLElementDeleter (defined below), which ensures that your container's +// elements are deleted when the STLElementDeleter goes out of scope. +template <class T> +void STLDeleteElements(T* container) { + if (!container) + return; + STLDeleteContainerPointers(container->begin(), container->end()); + container->clear(); +} + +// Given an STL container consisting of (key, value) pairs, STLDeleteValues +// deletes all the "value" components and clears the container. Does nothing +// in the case it's given a NULL pointer. +template <class T> +void STLDeleteValues(T* container) { + if (!container) + return; + for (typename T::iterator i(container->begin()); i != container->end(); ++i) + delete i->second; + container->clear(); +} + + +// The following classes provide a convenient way to delete all elements or +// values from STL containers when they goes out of scope. This greatly +// simplifies code that creates temporary objects and has multiple return +// statements. Example: +// +// vector<MyProto *> tmp_proto; +// STLElementDeleter<vector<MyProto *> > d(&tmp_proto); +// if (...) return false; +// ... +// return success; + +// Given a pointer to an STL container this class will delete all the element +// pointers when it goes out of scope. +template<class T> +class STLElementDeleter { + public: + STLElementDeleter<T>(T* container) : container_(container) {} + ~STLElementDeleter<T>() { STLDeleteElements(container_); } + + private: + T* container_; +}; + +// Given a pointer to an STL container this class will delete all the value +// pointers when it goes out of scope. +template<class T> +class STLValueDeleter { + public: + STLValueDeleter<T>(T* container) : container_(container) {} + ~STLValueDeleter<T>() { STLDeleteValues(container_); } + + private: + T* container_; +}; + +// Test to see if a set, map, hash_set or hash_map contains a particular key. +// Returns true if the key is in the collection. +template <typename Collection, typename Key> +bool ContainsKey(const Collection& collection, const Key& key) { + return collection.find(key) != collection.end(); +} + +namespace base { + +// Returns true if the container is sorted. +template <typename Container> +bool STLIsSorted(const Container& cont) { + return std::adjacent_find(cont.begin(), cont.end(), + std::greater<typename Container::value_type>()) + == cont.end(); +} + +// Returns a new ResultType containing the difference of two sorted containers. +template <typename ResultType, typename Arg1, typename Arg2> +ResultType STLSetDifference(const Arg1& a1, const Arg2& a2) { + DCHECK(STLIsSorted(a1)); + DCHECK(STLIsSorted(a2)); + ResultType difference; + std::set_difference(a1.begin(), a1.end(), + a2.begin(), a2.end(), + std::inserter(difference, difference.end())); + return difference; +} + +} // namespace base + +#endif // BASE_STL_UTIL_H_
diff --git a/src/base/stl_util_unittest.cc b/src/base/stl_util_unittest.cc new file mode 100644 index 0000000..63d5c5c --- /dev/null +++ b/src/base/stl_util_unittest.cc
@@ -0,0 +1,82 @@ +// Copyright 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/stl_util.h" + +#include <set> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +TEST(STLUtilTest, STLIsSorted) { + { + std::set<int> set; + set.insert(24); + set.insert(1); + set.insert(12); + EXPECT_TRUE(STLIsSorted(set)); + } + + { + std::vector<int> vector; + vector.push_back(1); + vector.push_back(1); + vector.push_back(4); + vector.push_back(64); + vector.push_back(12432); + EXPECT_TRUE(STLIsSorted(vector)); + vector.back() = 1; + EXPECT_FALSE(STLIsSorted(vector)); + } +} + +TEST(STLUtilTest, STLSetDifference) { + std::set<int> a1; + a1.insert(1); + a1.insert(2); + a1.insert(3); + a1.insert(4); + + std::set<int> a2; + a2.insert(3); + a2.insert(4); + a2.insert(5); + a2.insert(6); + a2.insert(7); + + { + std::set<int> difference; + difference.insert(1); + difference.insert(2); + EXPECT_EQ(difference, STLSetDifference<std::set<int> >(a1, a2)); + } + + { + std::set<int> difference; + difference.insert(5); + difference.insert(6); + difference.insert(7); + EXPECT_EQ(difference, STLSetDifference<std::set<int> >(a2, a1)); + } + + { + std::vector<int> difference; + difference.push_back(1); + difference.push_back(2); + EXPECT_EQ(difference, STLSetDifference<std::vector<int> >(a1, a2)); + } + + { + std::vector<int> difference; + difference.push_back(5); + difference.push_back(6); + difference.push_back(7); + EXPECT_EQ(difference, STLSetDifference<std::vector<int> >(a2, a1)); + } +} + +} // namespace +} // namespace base
diff --git a/src/base/string16.cc b/src/base/string16.cc new file mode 100644 index 0000000..930e09f --- /dev/null +++ b/src/base/string16.cc
@@ -0,0 +1,84 @@ +// 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/string16.h" + +#if defined(WCHAR_T_IS_UTF16) + +#error This file should not be used on 2-byte wchar_t systems +// If this winds up being needed on 2-byte wchar_t systems, either the +// definitions below can be used, or the host system's wide character +// functions like wmemcmp can be wrapped. + +#elif defined(WCHAR_T_IS_UTF32) + +#include <ostream> + +#include "base/utf_string_conversions.h" + +namespace base { + +int c16memcmp(const char16* s1, const char16* s2, size_t n) { + // We cannot call memcmp because that changes the semantics. + while (n-- > 0) { + if (*s1 != *s2) { + // We cannot use (*s1 - *s2) because char16 is unsigned. + return ((*s1 < *s2) ? -1 : 1); + } + ++s1; + ++s2; + } + return 0; +} + +size_t c16len(const char16* s) { + const char16 *s_orig = s; + while (*s) { + ++s; + } + return s - s_orig; +} + +const char16* c16memchr(const char16* s, char16 c, size_t n) { + while (n-- > 0) { + if (*s == c) { + return s; + } + ++s; + } + return 0; +} + +char16* c16memmove(char16* s1, const char16* s2, size_t n) { + return static_cast<char16*>(memmove(s1, s2, n * sizeof(char16))); +} + +char16* c16memcpy(char16* s1, const char16* s2, size_t n) { + return static_cast<char16*>(memcpy(s1, s2, n * sizeof(char16))); +} + +char16* c16memset(char16* s, char16 c, size_t n) { + char16 *s_orig = s; + while (n-- > 0) { + *s = c; + ++s; + } + return s_orig; +} + +} // namespace base + +template class std::basic_string<char16, base::string16_char_traits>; + +namespace base { +std::ostream& operator<<(std::ostream& out, const string16& str) { + return out << UTF16ToUTF8(str); +} + +void PrintTo(const string16& str, std::ostream* out) { + *out << str; +} +} + +#endif // WCHAR_T_IS_UTF32
diff --git a/src/base/string16.h b/src/base/string16.h new file mode 100644 index 0000000..e5d8e35 --- /dev/null +++ b/src/base/string16.h
@@ -0,0 +1,185 @@ +// 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_STRING16_H_ +#define BASE_STRING16_H_ + +// WHAT: +// A version of std::basic_string that provides 2-byte characters even when +// wchar_t is not implemented as a 2-byte type. You can access this class as +// string16. We also define char16, which string16 is based upon. +// +// WHY: +// On Windows, wchar_t is 2 bytes, and it can conveniently handle UTF-16/UCS-2 +// data. Plenty of existing code operates on strings encoded as UTF-16. +// +// On many other platforms, sizeof(wchar_t) is 4 bytes by default. We can make +// it 2 bytes by using the GCC flag -fshort-wchar. But then std::wstring fails +// at run time, because it calls some functions (like wcslen) that come from +// the system's native C library -- which was built with a 4-byte wchar_t! +// It's wasteful to use 4-byte wchar_t strings to carry UTF-16 data, and it's +// entirely improper on those systems where the encoding of wchar_t is defined +// as UTF-32. +// +// Here, we define string16, which is similar to std::wstring but replaces all +// libc functions with custom, 2-byte-char compatible routines. It is capable +// of carrying UTF-16-encoded data. + +#include <stdio.h> +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" + +#if defined(WCHAR_T_IS_UTF16) + +typedef wchar_t char16; +typedef std::wstring string16; + +namespace base { +typedef std::char_traits<wchar_t> string16_char_traits; +} + +#elif defined(WCHAR_T_IS_UTF32) + +typedef uint16 char16; + +namespace base { + +// char16 versions of the functions required by string16_char_traits; these +// are based on the wide character functions of similar names ("w" or "wcs" +// instead of "c16"). +BASE_EXPORT int c16memcmp(const char16* s1, const char16* s2, size_t n); +BASE_EXPORT size_t c16len(const char16* s); +BASE_EXPORT const char16* c16memchr(const char16* s, char16 c, size_t n); +BASE_EXPORT char16* c16memmove(char16* s1, const char16* s2, size_t n); +BASE_EXPORT char16* c16memcpy(char16* s1, const char16* s2, size_t n); +BASE_EXPORT char16* c16memset(char16* s, char16 c, size_t n); + +struct string16_char_traits { + typedef char16 char_type; + typedef int int_type; + + // int_type needs to be able to hold each possible value of char_type, and in + // addition, the distinct value of eof(). + COMPILE_ASSERT(sizeof(int_type) > sizeof(char_type), unexpected_type_width); + + typedef std::streamoff off_type; + typedef mbstate_t state_type; + typedef std::fpos<state_type> pos_type; + + static void assign(char_type& c1, const char_type& c2) { + c1 = c2; + } + + static bool eq(const char_type& c1, const char_type& c2) { + return c1 == c2; + } + static bool lt(const char_type& c1, const char_type& c2) { + return c1 < c2; + } + + static int compare(const char_type* s1, const char_type* s2, size_t n) { + return c16memcmp(s1, s2, n); + } + + static size_t length(const char_type* s) { + return c16len(s); + } + + static const char_type* find(const char_type* s, size_t n, + const char_type& a) { + return c16memchr(s, a, n); + } + + static char_type* move(char_type* s1, const char_type* s2, int_type n) { + return c16memmove(s1, s2, n); + } + + static char_type* copy(char_type* s1, const char_type* s2, size_t n) { + return c16memcpy(s1, s2, n); + } + + static char_type* assign(char_type* s, size_t n, char_type a) { + return c16memset(s, a, n); + } + + static int_type not_eof(const int_type& c) { + return eq_int_type(c, eof()) ? 0 : c; + } + + static char_type to_char_type(const int_type& c) { + return char_type(c); + } + + static int_type to_int_type(const char_type& c) { + return int_type(c); + } + + static bool eq_int_type(const int_type& c1, const int_type& c2) { + return c1 == c2; + } + + static int_type eof() { + return static_cast<int_type>(EOF); + } +}; + +} // namespace base + +// The string class will be explicitly instantiated only once, in string16.cc. +// +// std::basic_string<> in GNU libstdc++ contains a static data member, +// _S_empty_rep_storage, to represent empty strings. When an operation such +// as assignment or destruction is performed on a string, causing its existing +// data member to be invalidated, it must not be freed if this static data +// member is being used. Otherwise, it counts as an attempt to free static +// (and not allocated) data, which is a memory error. +// +// Generally, due to C++ template magic, _S_empty_rep_storage will be marked +// as a coalesced symbol, meaning that the linker will combine multiple +// instances into a single one when generating output. +// +// If a string class is used by multiple shared libraries, a problem occurs. +// Each library will get its own copy of _S_empty_rep_storage. When strings +// are passed across a library boundary for alteration or destruction, memory +// errors will result. GNU libstdc++ contains a configuration option, +// --enable-fully-dynamic-string (_GLIBCXX_FULLY_DYNAMIC_STRING), which +// disables the static data member optimization, but it's a good optimization +// and non-STL code is generally at the mercy of the system's STL +// configuration. Fully-dynamic strings are not the default for GNU libstdc++ +// libstdc++ itself or for the libstdc++ installations on the systems we care +// about, such as Mac OS X and relevant flavors of Linux. +// +// See also http://gcc.gnu.org/bugzilla/show_bug.cgi?id=24196 . +// +// To avoid problems, string classes need to be explicitly instantiated only +// once, in exactly one library. All other string users see it via an "extern" +// declaration. This is precisely how GNU libstdc++ handles +// std::basic_string<char> (string) and std::basic_string<wchar_t> (wstring). +// +// This also works around a Mac OS X linker bug in ld64-85.2.1 (Xcode 3.1.2), +// in which the linker does not fully coalesce symbols when dead code +// stripping is enabled. This bug causes the memory errors described above +// to occur even when a std::basic_string<> does not cross shared library +// boundaries, such as in statically-linked executables. +// +// TODO(mark): File this bug with Apple and update this note with a bug number. + +extern template +class BASE_EXPORT std::basic_string<char16, base::string16_char_traits>; + +typedef std::basic_string<char16, base::string16_char_traits> string16; + +namespace base { +BASE_EXPORT extern std::ostream& operator<<(std::ostream& out, + const string16& str); + +// This is required by googletest to print a readable output on test failures. +BASE_EXPORT extern void PrintTo(const string16& str, std::ostream* out); +} + +#endif // WCHAR_T_IS_UTF32 + +#endif // BASE_STRING16_H_
diff --git a/src/base/string16_unittest.cc b/src/base/string16_unittest.cc new file mode 100644 index 0000000..e833ed2 --- /dev/null +++ b/src/base/string16_unittest.cc
@@ -0,0 +1,53 @@ +// Copyright (c) 2009 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 <sstream> + +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(WCHAR_T_IS_UTF32) + +// We define a custom operator<< for string16 so we can use it with logging. +// This tests that conversion. +TEST(String16Test, OutputStream) { + // Basic stream test. + { + std::ostringstream stream; + stream << "Empty '" << string16() << "' standard '" + << string16(ASCIIToUTF16("Hello, world")) << "'"; + EXPECT_STREQ("Empty '' standard 'Hello, world'", + stream.str().c_str()); + } + + // Interesting edge cases. + { + // These should each get converted to the invalid character: EF BF BD. + string16 initial_surrogate; + initial_surrogate.push_back(0xd800); + string16 final_surrogate; + final_surrogate.push_back(0xdc00); + + // Old italic A = U+10300, will get converted to: F0 90 8C 80 'z'. + string16 surrogate_pair; + surrogate_pair.push_back(0xd800); + surrogate_pair.push_back(0xdf00); + surrogate_pair.push_back('z'); + + // Will get converted to the invalid char + 's': EF BF BD 's'. + string16 unterminated_surrogate; + unterminated_surrogate.push_back(0xd800); + unterminated_surrogate.push_back('s'); + + std::ostringstream stream; + stream << initial_surrogate << "," << final_surrogate << "," + << surrogate_pair << "," << unterminated_surrogate; + + EXPECT_STREQ("\xef\xbf\xbd,\xef\xbf\xbd,\xf0\x90\x8c\x80z,\xef\xbf\xbds", + stream.str().c_str()); + } +} + +#endif
diff --git a/src/base/string_number_conversions.cc b/src/base/string_number_conversions.cc new file mode 100644 index 0000000..1aafc4e --- /dev/null +++ b/src/base/string_number_conversions.cc
@@ -0,0 +1,426 @@ +// 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/string_number_conversions.h" + +#include <ctype.h> +#include <errno.h> +#include <stdlib.h> +#include <wctype.h> + +#include <limits> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/third_party/dmg_fp/dmg_fp.h" +#include "base/utf_string_conversions.h" + +namespace base { + +namespace { + +// The following template is used to convert a size in bytes value into an +// unsigned integral type. For example, if we want to use the unsigned type +// corresponding to int, we can use type MakeUnsigned<sizeof(int)>::Unsigned. +template <size_t size_in_bytes> +struct MakeUnsigned {}; + +template <> +struct MakeUnsigned<1> { + typedef uint8 Unsigned; +}; + +template <> +struct MakeUnsigned<2> { + typedef uint16 Unsigned; +}; + +template <> +struct MakeUnsigned<4> { + typedef uint32 Unsigned; +}; + +template <> +struct MakeUnsigned<8> { + typedef uint64 Unsigned; +}; + +template <typename STR, typename INT> +struct IntToStringT { + static STR IntToString(INT value) { + // log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4. + // So round up to allocate 3 output characters per byte, plus 1 for '-'. + const int kOutputBufSize = 3 * sizeof(INT) + 1; + + // Allocate the whole string right away, we will right back to front, and + // then return the substr of what we ended up using. + STR outbuf(kOutputBufSize, 0); + + // We cannot directly use 'value < 0' as it will generate a warning on + // unsigned types in certain compilers because such check is meaningless. + bool is_neg = value < 1 && value != 0; + // We cannot simply use '-value' as it will generate a warning on unsigned + // types in certain compilers which treats such operation as invalid. + typename MakeUnsigned<sizeof(value)>::Unsigned res = + is_neg ? 0 - value : value; + + for (typename STR::iterator it = outbuf.end();;) { + DCHECK(it != outbuf.begin()); + --it; + *it = static_cast<typename STR::value_type>((res % 10) + '0'); + res /= 10; + + // We're done.. + if (res == 0) { + if (is_neg) { + DCHECK(it != outbuf.begin()); + --it; + *it = static_cast<typename STR::value_type>('-'); + } + return STR(it, outbuf.end()); + } + } + NOTREACHED(); + return STR(); + } +}; + +// Utility to convert a character to a digit in a given base +template<typename CHAR, int BASE, bool BASE_LTE_10> class BaseCharToDigit { +}; + +// Faster specialization for bases <= 10 +template<typename CHAR, int BASE> class BaseCharToDigit<CHAR, BASE, true> { + public: + static bool Convert(CHAR c, uint8* digit) { + if (c >= '0' && c < '0' + BASE) { + *digit = c - '0'; + return true; + } + return false; + } +}; + +// Specialization for bases where 10 < base <= 36 +template<typename CHAR, int BASE> class BaseCharToDigit<CHAR, BASE, false> { + public: + static bool Convert(CHAR c, uint8* digit) { + if (c >= '0' && c <= '9') { + *digit = c - '0'; + } else if (c >= 'a' && c < 'a' + BASE - 10) { + *digit = c - 'a' + 10; + } else if (c >= 'A' && c < 'A' + BASE - 10) { + *digit = c - 'A' + 10; + } else { + return false; + } + return true; + } +}; + +template<int BASE, typename CHAR> bool CharToDigit(CHAR c, uint8* digit) { + return BaseCharToDigit<CHAR, BASE, BASE <= 10>::Convert(c, digit); +} + +// There is an IsWhitespace for wchars defined in string_util.h, but it is +// locale independent, whereas the functions we are replacing were +// locale-dependent. TBD what is desired, but for the moment let's not introduce +// a change in behaviour. +template<typename CHAR> class WhitespaceHelper { +}; + +template<> class WhitespaceHelper<char> { + public: + static bool Invoke(char c) { + return 0 != isspace(static_cast<unsigned char>(c)); + } +}; + +template<> class WhitespaceHelper<char16> { + public: + static bool Invoke(char16 c) { + return 0 != iswspace(c); + } +}; + +template<typename CHAR> bool LocalIsWhitespace(CHAR c) { + return WhitespaceHelper<CHAR>::Invoke(c); +} + +// IteratorRangeToNumberTraits should provide: +// - a typedef for iterator_type, the iterator type used as input. +// - a typedef for value_type, the target numeric type. +// - static functions min, max (returning the minimum and maximum permitted +// values) +// - constant kBase, the base in which to interpret the input +template<typename IteratorRangeToNumberTraits> +class IteratorRangeToNumber { + public: + typedef IteratorRangeToNumberTraits traits; + typedef typename traits::iterator_type const_iterator; + typedef typename traits::value_type value_type; + + // Generalized iterator-range-to-number conversion. + // + static bool Invoke(const_iterator begin, + const_iterator end, + value_type* output) { + bool valid = true; + + while (begin != end && LocalIsWhitespace(*begin)) { + valid = false; + ++begin; + } + + if (begin != end && *begin == '-') { + if (!Negative::Invoke(begin + 1, end, output)) { + valid = false; + } + // For unsigned types, any negative value is invalid and will be set to 0. + if (traits::min() == 0) { + if (*output != 0) { + valid = false; + } + *output = 0; + } + } else { + if (begin != end && *begin == '+') { + ++begin; + } + if (!Positive::Invoke(begin, end, output)) { + valid = false; + } + } + + return valid; + } + + private: + // Sign provides: + // - a static function, CheckBounds, that determines whether the next digit + // causes an overflow/underflow + // - a static function, Increment, that appends the next digit appropriately + // according to the sign of the number being parsed. + template<typename Sign> + class Base { + public: + static bool Invoke(const_iterator begin, const_iterator end, + typename traits::value_type* output) { + *output = 0; + + if (begin == end) { + return false; + } + + // Note: no performance difference was found when using template + // specialization to remove this check in bases other than 16 + if (traits::kBase == 16 && end - begin > 2 && *begin == '0' && + (*(begin + 1) == 'x' || *(begin + 1) == 'X')) { + begin += 2; + } + + for (const_iterator current = begin; current != end; ++current) { + uint8 new_digit = 0; + + if (!CharToDigit<traits::kBase>(*current, &new_digit)) { + return false; + } + + if (current != begin) { + if (!Sign::CheckBounds(output, new_digit)) { + return false; + } + *output *= traits::kBase; + } + + Sign::Increment(new_digit, output); + } + return true; + } + }; + + class Positive : public Base<Positive> { + public: + static bool CheckBounds(value_type* output, uint8 new_digit) { + if (*output > static_cast<value_type>(traits::max() / traits::kBase) || + (*output == static_cast<value_type>(traits::max() / traits::kBase) && + new_digit > traits::max() % traits::kBase)) { + *output = traits::max(); + return false; + } + return true; + } + static void Increment(uint8 increment, value_type* output) { + *output += increment; + } + }; + + class Negative : public Base<Negative> { + public: + static bool CheckBounds(value_type* output, uint8 new_digit) { + if (*output < traits::min() / traits::kBase || + (*output == traits::min() / traits::kBase && + new_digit > 0 - traits::min() % traits::kBase)) { + *output = traits::min(); + return false; + } + return true; + } + static void Increment(uint8 increment, value_type* output) { + *output -= increment; + } + }; +}; + +template<typename ITERATOR, typename VALUE, int BASE> +class BaseIteratorRangeToNumberTraits { + public: + typedef ITERATOR iterator_type; + typedef VALUE value_type; + static value_type min() { + return std::numeric_limits<value_type>::min(); + } + static value_type max() { + return std::numeric_limits<value_type>::max(); + } + static const int kBase = BASE; +}; + +template<typename ITERATOR> +class BaseHexIteratorRangeToIntTraits + : public BaseIteratorRangeToNumberTraits<ITERATOR, int, 16> { + public: + // Allow parsing of 0xFFFFFFFF, which is technically an overflow + static unsigned int max() { + return std::numeric_limits<unsigned int>::max(); + } +}; + +typedef BaseHexIteratorRangeToIntTraits<StringPiece::const_iterator> + HexIteratorRangeToIntTraits; + +template<typename STR> +bool HexStringToBytesT(const STR& input, std::vector<uint8>* output) { + DCHECK_EQ(output->size(), 0u); + size_t count = input.size(); + if (count == 0 || (count % 2) != 0) + return false; + for (uintptr_t i = 0; i < count / 2; ++i) { + uint8 msb = 0; // most significant 4 bits + uint8 lsb = 0; // least significant 4 bits + if (!CharToDigit<16>(input[i * 2], &msb) || + !CharToDigit<16>(input[i * 2 + 1], &lsb)) + return false; + output->push_back((msb << 4) | lsb); + } + return true; +} + +template <typename VALUE, int BASE> +class StringPieceToNumberTraits + : public BaseIteratorRangeToNumberTraits<StringPiece::const_iterator, + VALUE, + BASE> {}; + +template <typename VALUE> +bool StringToIntImpl(const StringPiece& input, VALUE* output) { + return IteratorRangeToNumber<StringPieceToNumberTraits<VALUE, 10> >::Invoke( + input.begin(), input.end(), output); +} + +template <typename VALUE, int BASE> +class StringPiece16ToNumberTraits + : public BaseIteratorRangeToNumberTraits<StringPiece16::const_iterator, + VALUE, + BASE> {}; + +template <typename VALUE> +bool String16ToIntImpl(const StringPiece16& input, VALUE* output) { + return IteratorRangeToNumber<StringPiece16ToNumberTraits<VALUE, 10> >::Invoke( + input.begin(), input.end(), output); +} + +} // namespace + +#define DEFINE_INTEGRAL_TO_STRING_CONVERSIONS(Name, CppType) \ + std::string Name##ToString(CppType value) { \ + return IntToStringT<std::string, CppType>::IntToString(value); \ + } \ + string16 Name##ToString16(CppType value) { \ + return IntToStringT<string16, CppType>::IntToString(value); \ + } + +INTEGRAL_STRING_CONVERSIONS_FOR_EACH(DEFINE_INTEGRAL_TO_STRING_CONVERSIONS) +#undef DEFINE_INTEGRAL_TO_STRING_CONVERSIONS + +std::string DoubleToString(double value) { + // According to g_fmt.cc, it is sufficient to declare a buffer of size 32. + char buffer[32]; + dmg_fp::g_fmt(buffer, value); + return std::string(buffer); +} + +#define DEFINE_STRING_TO_INTEGRAL_CONVERSIONS(Name, CppType) \ + bool StringTo##Name(const StringPiece& input, CppType* output) { \ + return StringToIntImpl(input, output); \ + } \ + bool StringTo##Name(const StringPiece16& input, CppType* output) { \ + return String16ToIntImpl(input, output); \ + } + +INTEGRAL_STRING_CONVERSIONS_FOR_EACH(DEFINE_STRING_TO_INTEGRAL_CONVERSIONS) +#undef DEFINE_STRING_TO_INTEGRAL_CONVERSIONS + +bool StringToDouble(const std::string& input, double* output) { + errno = 0; // Thread-safe? It is on at least Mac, Linux, and Windows. + char* endptr = NULL; + *output = dmg_fp::strtod(input.c_str(), &endptr); + + // Cases to return false: + // - If errno is ERANGE, there was an overflow or underflow. + // - If the input string is empty, there was nothing to parse. + // - If endptr does not point to the end of the string, there are either + // characters remaining in the string after a parsed number, or the string + // does not begin with a parseable number. endptr is compared to the + // expected end given the string's stated length to correctly catch cases + // where the string contains embedded NUL characters. + // - If the first character is a space, there was leading whitespace + return errno == 0 && + !input.empty() && + input.c_str() + input.length() == endptr && + !isspace(input[0]); +} + +// Note: if you need to add String16ToDouble, first ask yourself if it's +// really necessary. If it is, probably the best implementation here is to +// convert to 8-bit and then use the 8-bit version. + +// Note: if you need to add an iterator range version of StringToDouble, first +// ask yourself if it's really necessary. If it is, probably the best +// implementation here is to instantiate a string and use the string version. + +std::string HexEncode(const void* bytes, size_t size) { + static const char kHexChars[] = "0123456789ABCDEF"; + + // Each input byte creates two output hex characters. + std::string ret(size * 2, '\0'); + + for (size_t i = 0; i < size; ++i) { + char b = reinterpret_cast<const char*>(bytes)[i]; + ret[(i * 2)] = kHexChars[(b >> 4) & 0xf]; + ret[(i * 2) + 1] = kHexChars[b & 0xf]; + } + return ret; +} + +bool HexStringToInt(const StringPiece& input, int* output) { + return IteratorRangeToNumber<HexIteratorRangeToIntTraits>::Invoke( + input.begin(), input.end(), output); +} + +bool HexStringToBytes(const std::string& input, std::vector<uint8>* output) { + return HexStringToBytesT(input, output); +} + +} // namespace base
diff --git a/src/base/string_number_conversions.h b/src/base/string_number_conversions.h new file mode 100644 index 0000000..7b7cfe1 --- /dev/null +++ b/src/base/string_number_conversions.h
@@ -0,0 +1,110 @@ +// 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_STRING_NUMBER_CONVERSIONS_H_ +#define BASE_STRING_NUMBER_CONVERSIONS_H_ + +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/string_piece.h" +#include "base/string16.h" + +// ---------------------------------------------------------------------------- +// IMPORTANT MESSAGE FROM YOUR SPONSOR +// +// This file contains no "wstring" variants. New code should use string16. If +// you need to make old code work, use the UTF8 version and convert. Please do +// not add wstring variants. +// +// Please do not add "convenience" functions for converting strings to integers +// that return the value and ignore success/failure. That encourages people to +// write code that doesn't properly handle the error conditions. +// ---------------------------------------------------------------------------- + +namespace base { + +// The following macro expects a macro functor accepting two parameters: a name +// and a C++ type. It is used to generate the declarations, definitions and +// unit tests for types listed below. +#define INTEGRAL_STRING_CONVERSIONS_FOR_EACH(MacroOp) \ + MacroOp(Int, int) \ + MacroOp(Uint, unsigned int) \ + MacroOp(Int8, int8) \ + MacroOp(Uint8, uint8) \ + MacroOp(Int16, int16) \ + MacroOp(Uint16, uint16) \ + MacroOp(Int32, int32) \ + MacroOp(Uint32, uint32) \ + MacroOp(Int64, int64) \ + MacroOp(Uint64, uint64) \ + MacroOp(SizeT, size_t) + +// Number -> string conversions ------------------------------------------------ +#define DECLARE_INTEGRAL_TO_STRING_CONVERSIONS(Name, CppType) \ + BASE_EXPORT std::string Name##ToString(CppType value); \ + BASE_EXPORT string16 Name##ToString16(CppType value); + +INTEGRAL_STRING_CONVERSIONS_FOR_EACH(DECLARE_INTEGRAL_TO_STRING_CONVERSIONS) +#undef DECLARE_INTEGRAL_TO_STRING_CONVERSIONS + +// DoubleToString converts the double to a string format that ignores the +// locale. If you want to use locale specific formatting, use ICU. +BASE_EXPORT std::string DoubleToString(double value); + +// String -> number conversions ------------------------------------------------ + +// Perform a best-effort conversion of the input string to a numeric type, +// setting |*output| to the result of the conversion. Returns true for +// "perfect" conversions; returns false in the following cases: +// - Overflow/underflow. |*output| will be set to the maximum value supported +// by the data type. +// - Trailing characters in the string after parsing the number. |*output| +// will be set to the value of the number that was parsed. +// - Leading whitespace in the string before parsing the number. |*output| will +// be set to the value of the number that was parsed. +// - No characters parseable as a number at the beginning of the string. +// |*output| will be set to 0. +// - Empty string. |*output| will be set to 0. +#define DECLARE_STRING_TO_INTEGRAL_CONVERSIONS(Name, CppType) \ + BASE_EXPORT bool StringTo##Name(const StringPiece& input, CppType* output); \ + BASE_EXPORT bool StringTo##Name(const StringPiece16& input, CppType* output); + +INTEGRAL_STRING_CONVERSIONS_FOR_EACH(DECLARE_STRING_TO_INTEGRAL_CONVERSIONS) +#undef DECLARE_STRING_TO_INTEGRAL_CONVERSIONS + +// For floating-point conversions, only conversions of input strings in decimal +// form are defined to work. Behavior with strings representing floating-point +// numbers in hexadecimal, and strings representing non-fininte values (such as +// NaN and inf) is undefined. Otherwise, these behave the same as the integral +// variants. This expects the input string to NOT be specific to the locale. +// If your input is locale specific, use ICU to read the number. +BASE_EXPORT bool StringToDouble(const std::string& input, double* output); + +// Hex encoding ---------------------------------------------------------------- + +// Returns a hex string representation of a binary buffer. The returned hex +// string will be in upper case. This function does not check if |size| is +// within reasonable limits since it's written with trusted data in mind. If +// you suspect that the data you want to format might be large, the absolute +// max size for |size| should be is +// std::numeric_limits<size_t>::max() / 2 +BASE_EXPORT std::string HexEncode(const void* bytes, size_t size); + +// Best effort conversion, see StringToInt above for restrictions. +BASE_EXPORT bool HexStringToInt(const StringPiece& input, int* output); + +// Similar to the previous functions, except that output is a vector of bytes. +// |*output| will contain as many bytes as were successfully parsed prior to the +// error. There is no overflow, but input.size() must be evenly divisible by 2. +// Leading 0x or +/- are not allowed. +BASE_EXPORT bool HexStringToBytes(const std::string& input, + std::vector<uint8>* output); + +} // namespace base + +#endif // BASE_STRING_NUMBER_CONVERSIONS_H_ +
diff --git a/src/base/string_number_conversions_unittest.cc b/src/base/string_number_conversions_unittest.cc new file mode 100644 index 0000000..0bb33b5 --- /dev/null +++ b/src/base/string_number_conversions_unittest.cc
@@ -0,0 +1,388 @@ +// 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 <math.h> + +#include <limits> +#include <sstream> + +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +// When insert values of one byte type like char or uint8 into output streams, +// the values will be output as a character. In our tests we'd like it to be +// output as its numeric value. We introduce the following template class so +// we can use 'oss << static_cast<StreamSafeType<int8>::Type>(value);' to output +// the value in int8 as a number. +template <typename T> +struct StreamSafeType { + typedef T Type; +}; +template <> +struct StreamSafeType<int8> { + typedef int16 Type; +}; +template <> +struct StreamSafeType<uint8> { + typedef uint16 Type; +}; + +TEST(StringNumberConversionsTest, IntegralToString) { +#define DEFINE_INTEGRAL_TO_STRING_TEST(Name, CppType) \ + { \ + static const CppType kValuesToTest[] = { \ + 0, static_cast<CppType>(-1), 42, std::numeric_limits<CppType>::min(), \ + std::numeric_limits<CppType>::max()}; \ + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kValuesToTest); ++i) { \ + CppType value = static_cast<CppType>(kValuesToTest[i]); \ + std::ostringstream oss; \ + oss << static_cast<StreamSafeType<CppType>::Type>(value); \ + EXPECT_EQ(oss.str(), Name##ToString(value)); \ + EXPECT_EQ(UTF8ToUTF16(oss.str()), Name##ToString16(value)); \ + } \ + } + + INTEGRAL_STRING_CONVERSIONS_FOR_EACH(DEFINE_INTEGRAL_TO_STRING_TEST) +#undef DEFINE_INTEGRAL_TO_STRING_TEST +} + +TEST(StringNumberConversionsTest, StringToIntegral) { +#define DEFINE_STRING_TO_INTEGRAL_TEST(Name, CppType) \ + { \ + static const CppType kValuesToTest[] = { \ + 0, 42, std::numeric_limits<CppType>::min(), \ + std::numeric_limits<CppType>::max()}; \ + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kValuesToTest); ++i) { \ + std::ostringstream oss; \ + oss << static_cast<StreamSafeType<CppType>::Type>(kValuesToTest[i]); \ + \ + CppType output = 0; \ + EXPECT_TRUE(StringTo##Name(oss.str(), &output)); \ + EXPECT_EQ(kValuesToTest[i], output); \ + \ + string16 utf16_input = UTF8ToUTF16(oss.str()); \ + output = 0; \ + EXPECT_TRUE(StringTo##Name(utf16_input, &output)); \ + EXPECT_EQ(kValuesToTest[i], 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[] = "6\06"; \ + std::string input_string(input, arraysize(input) - 1); \ + CppType output; \ + EXPECT_FALSE(StringTo##Name(input_string, &output)); \ + EXPECT_EQ(6, output); \ + \ + string16 utf16_input = UTF8ToUTF16(input_string); \ + output = 0; \ + EXPECT_FALSE(StringTo##Name(utf16_input, &output)); \ + EXPECT_EQ(6, output); \ + \ + output = 0; \ + const char16 negative_wide_input[] = {0xFF4D, '4', '2', 0}; \ + EXPECT_FALSE(StringTo##Name(string16(negative_wide_input), &output)); \ + EXPECT_EQ(0, output); \ + } + + INTEGRAL_STRING_CONVERSIONS_FOR_EACH(DEFINE_STRING_TO_INTEGRAL_TEST) +#undef DEFINE_STRING_TO_INTEGRAL_TEST +} + +TEST(StringNumberConversionsTest, StringToIntegralFailure) { +#define DEFINE_STRING_TO_INTEGRAL_TEST(Name, CppType) \ + { \ + static const struct { \ + std::string input; \ + CppType output; \ + } kCasesToTest[] = { \ + {"42\x99", 42}, \ + {"\x99" "42\x99", 0}, \ + {"", 0}, \ + {" 42", 42}, \ + {"42 ", 42}, \ + {"\t\n\v\f\r 42", 42}, \ + {"blah42", 0}, \ + {"42blah", 42}, \ + {"blah42blah", 0}, \ + {"-73.15", -73}, \ + {"+98.6", 98}, \ + {"--123", 0}, \ + {"++123", 0}, \ + {"-+123", 0}, \ + {"+-123", 0}, \ + {"-", 0}, \ + {"-99999999999999999999999", std::numeric_limits<CppType>::min()}, \ + {"99999999999999999999999", std::numeric_limits<CppType>::max()}, \ + }; \ + \ + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kCasesToTest); ++i) { \ + CppType expected_value = kCasesToTest[i].output; \ + if (std::numeric_limits<CppType>::min() == 0 && \ + kCasesToTest[i].input[0] == '-') { \ + /* Any negative values should be mapped to 0 on unsigned types. */ \ + expected_value = 0; \ + } \ + CppType output = 0; \ + EXPECT_FALSE(StringTo##Name(kCasesToTest[i].input, &output)); \ + EXPECT_EQ(expected_value, output); \ + \ + string16 utf16_input = UTF8ToUTF16(kCasesToTest[i].input); \ + output = 0; \ + EXPECT_FALSE(StringTo##Name(utf16_input, &output)); \ + EXPECT_EQ(expected_value, output); \ + } \ + } + + INTEGRAL_STRING_CONVERSIONS_FOR_EACH(DEFINE_STRING_TO_INTEGRAL_TEST) +#undef DEFINE_STRING_TO_INTEGRAL_TEST +} + +TEST(StringNumberConversionsTest, StringToIntegralOffByOne) { +#define DEFINE_STRING_TO_INTEGRAL_TEST(Name, CppType) \ + { \ + CppType expected_value = std::numeric_limits<CppType>::min(); \ + /* This check doesn't apply to unsigned types */ \ + if (expected_value != 0) { \ + std::ostringstream oss; \ + oss << static_cast<StreamSafeType<CppType>::Type>(expected_value); \ + std::string input = oss.str(); \ + /* The lowest digit of the value cannot be '9'. */ \ + ASSERT_NE(*input.rbegin(), '9'); \ + /* Decrease the value by one, so the value is too small to represent. */ \ + /* Note that to decrease a negative value by one, we have to actually */ \ + /* increase its last digit by one. */ \ + ++*input.rbegin(); \ + \ + CppType output = 0; \ + EXPECT_FALSE(StringTo##Name(input, &output)); \ + EXPECT_EQ(expected_value, output); \ + \ + string16 utf16_input = UTF8ToUTF16(input); \ + output = 0; \ + EXPECT_FALSE(StringTo##Name(utf16_input, &output)); \ + EXPECT_EQ(expected_value, output); \ + } \ + expected_value = std::numeric_limits<CppType>::max(); \ + std::ostringstream oss; \ + oss << static_cast<StreamSafeType<CppType>::Type>(expected_value); \ + std::string input = oss.str(); \ + /* The lowest digit of the value cannot be '9'. */ \ + ASSERT_NE(*input.rbegin(), '0'); \ + /* Increase the value by one, so the value is too large to represent. */ \ + ++*input.rbegin(); \ + \ + CppType output = 0; \ + EXPECT_FALSE(StringTo##Name(input, &output)); \ + EXPECT_EQ(expected_value, output); \ + \ + string16 utf16_input = UTF8ToUTF16(input); \ + output = 0; \ + EXPECT_FALSE(StringTo##Name(utf16_input, &output)); \ + EXPECT_EQ(expected_value, output); \ + } + + INTEGRAL_STRING_CONVERSIONS_FOR_EACH(DEFINE_STRING_TO_INTEGRAL_TEST) +#undef DEFINE_STRING_TO_INTEGRAL_TEST +} + +TEST(StringNumberConversionsTest, HexStringToInt) { + static const struct { + std::string input; + int output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 66, true}, + {"-42", -66, true}, + {"+42", 66, true}, + {"7fffffff", INT_MAX, true}, + {"80000000", INT_MIN, true}, + {"ffffffff", -1, true}, + {"DeadBeef", 0xdeadbeef, true}, + {"0x42", 66, true}, + {"-0x42", -66, true}, + {"+0x42", 66, true}, + {"0x7fffffff", INT_MAX, true}, + {"0x80000000", INT_MIN, true}, + {"0xffffffff", -1, true}, + {"0XDeadBeef", 0xdeadbeef, true}, + {"0x0f", 15, true}, + {"0f", 15, 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}, + {"100000000", -1, false}, // don't care about |output|, just |success| + {"-", 0, false}, + {"", 0, false}, + {"0x", 0, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + int output = 0; + EXPECT_EQ(cases[i].success, HexStringToInt(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); + int output; + EXPECT_FALSE(HexStringToInt(input_string, &output)); + EXPECT_EQ(0xc0ffee, output); +} + +TEST(StringNumberConversionsTest, HexStringToBytes) { + static const struct { + const std::string input; + const char* output; + size_t output_len; + bool success; + } cases[] = { + {"0", "", 0, false}, // odd number of characters fails + {"00", "\0", 1, true}, + {"42", "\x42", 1, true}, + {"-42", "", 0, false}, // any non-hex value fails + {"+42", "", 0, false}, + {"7fffffff", "\x7f\xff\xff\xff", 4, true}, + {"80000000", "\x80\0\0\0", 4, true}, + {"deadbeef", "\xde\xad\xbe\xef", 4, true}, + {"DeadBeef", "\xde\xad\xbe\xef", 4, true}, + {"0x42", "", 0, false}, // leading 0x fails (x is not hex) + {"0f", "\xf", 1, true}, + {"45 ", "\x45", 1, false}, + {"efgh", "\xef", 1, false}, + {"", "", 0, false}, + {"0123456789ABCDEF", "\x01\x23\x45\x67\x89\xAB\xCD\xEF", 8, true}, + {"0123456789ABCDEF012345", + "\x01\x23\x45\x67\x89\xAB\xCD\xEF\x01\x23\x45", 11, true}, + }; + + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + std::vector<uint8> output; + std::vector<uint8> compare; + EXPECT_EQ(cases[i].success, HexStringToBytes(cases[i].input, &output)) << + i << ": " << cases[i].input; + for (size_t j = 0; j < cases[i].output_len; ++j) + compare.push_back(static_cast<uint8>(cases[i].output[j])); + ASSERT_EQ(output.size(), compare.size()) << i << ": " << cases[i].input; + EXPECT_TRUE(std::equal(output.begin(), output.end(), compare.begin())) << + i << ": " << cases[i].input; + } +} + +TEST(StringNumberConversionsTest, StringToDouble) { + static const struct { + std::string input; + double output; + bool success; + } cases[] = { + {"0", 0.0, true}, + {"42", 42.0, true}, + {"-42", -42.0, true}, + {"123.45", 123.45, true}, + {"-123.45", -123.45, true}, + {"+123.45", 123.45, true}, + {"2.99792458e8", 299792458.0, true}, + {"149597870.691E+3", 149597870691.0, true}, + {"6.", 6.0, true}, + {"9e99999999999999999999", HUGE_VAL, false}, + {"-9e99999999999999999999", -HUGE_VAL, false}, + {"1e-2", 0.01, true}, + {"42 ", 42.0, false}, + {" 1e-2", 0.01, false}, + {"1e-2 ", 0.01, false}, + {"-1E-7", -0.0000001, true}, + {"01e02", 100, true}, + {"2.3e15", 2.3e15, true}, + {"\t\n\v\f\r -123.45e2", -12345.0, false}, + {"+123 e4", 123.0, false}, + {"123e ", 123.0, false}, + {"123e", 123.0, false}, + {" 2.99", 2.99, false}, + {"1e3.4", 1000.0, false}, + {"nothing", 0.0, false}, + {"-", 0.0, false}, + {"+", 0.0, false}, + {"", 0.0, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + double output; + EXPECT_EQ(cases[i].success, StringToDouble(cases[i].input, &output)); + EXPECT_DOUBLE_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[] = "3.14\0159"; + std::string input_string(input, arraysize(input) - 1); + double output; + EXPECT_FALSE(StringToDouble(input_string, &output)); + EXPECT_DOUBLE_EQ(3.14, output); +} + +TEST(StringNumberConversionsTest, DoubleToString) { + static const struct { + double input; + const char* expected; + } cases[] = { + {0.0, "0"}, + {1.25, "1.25"}, + {1.33518e+012, "1.33518e+12"}, + {1.33489e+012, "1.33489e+12"}, + {1.33505e+012, "1.33505e+12"}, + {1.33545e+009, "1335450000"}, + {1.33503e+009, "1335030000"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + EXPECT_EQ(cases[i].expected, DoubleToString(cases[i].input)); + } + + // The following two values were seen in crashes in the wild. +#if defined(ARCH_CPU_BIG_ENDIAN) + const char input_bytes[8] = {'\x42', '\x73', '\x6d', '\xee', 0, 0, 0, 0}; +#else + const char input_bytes[8] = {0, 0, 0, 0, '\xee', '\x6d', '\x73', '\x42'}; +#endif + double input = 0; + memcpy(&input, input_bytes, arraysize(input_bytes)); + EXPECT_EQ("1335179083776", DoubleToString(input)); +#if defined(ARCH_CPU_BIG_ENDIAN) + const char input_bytes2[8] = + {'\x42', '\x73', '\x6c', '\xda', '\xa0', 0, 0, 0}; +#else + const char input_bytes2[8] = + {0, 0, 0, '\xa0', '\xda', '\x6c', '\x73', '\x42'}; +#endif + input = 0; + memcpy(&input, input_bytes2, arraysize(input_bytes2)); + EXPECT_EQ("1334890332160", DoubleToString(input)); +} + +TEST(StringNumberConversionsTest, HexEncode) { + std::string hex(HexEncode(NULL, 0)); + EXPECT_EQ(hex.length(), 0U); + unsigned char bytes[] = {0x01, 0xff, 0x02, 0xfe, 0x03, 0x80, 0x81}; + hex = HexEncode(bytes, sizeof(bytes)); + EXPECT_EQ(hex.compare("01FF02FE038081"), 0); +} + +} // namespace +} // namespace base
diff --git a/src/base/string_piece.cc b/src/base/string_piece.cc new file mode 100644 index 0000000..a018cb7 --- /dev/null +++ b/src/base/string_piece.cc
@@ -0,0 +1,255 @@ +// 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. +// Copied from strings/stringpiece.cc with modifications + +#include <algorithm> +#include <ostream> + +#include "base/string_piece.h" + +namespace base { + +// MSVC doesn't like complex extern templates and DLLs. +#if !defined(COMPILER_MSVC) && !defined(__SNC__) +namespace internal { +template class StringPieceDetail<std::string>; +template class StringPieceDetail<string16>; +} // namespace internal + +template class BasicStringPiece<string16>; +#endif + +bool operator==(const StringPiece& x, const StringPiece& y) { + if (x.size() != y.size()) + return false; + + return StringPiece::wordmemcmp(x.data(), y.data(), x.size()) == 0; +} + +std::ostream& operator<<(std::ostream& o, const StringPiece& piece) { + o.write(piece.data(), static_cast<std::streamsize>(piece.size())); + return o; +} + +namespace internal { +void CopyToString(const StringPiece& self, std::string* target) { + target->assign(!self.empty() ? self.data() : "", self.size()); +} + +void AppendToString(const StringPiece& self, std::string* target) { + if (!self.empty()) + target->append(self.data(), self.size()); +} + +StringPiece::size_type copy(const StringPiece& self, + char* buf, + StringPiece::size_type n, + StringPiece::size_type pos) { + StringPiece::size_type ret = std::min(self.size() - pos, n); + memcpy(buf, self.data() + pos, ret); + return ret; +} + +StringPiece::size_type find(const StringPiece& self, + const StringPiece& s, + StringPiece::size_type pos) { + if (pos > self.size()) + return StringPiece::npos; + + StringPiece::const_iterator result = + std::search(self.begin() + pos, self.end(), s.begin(), s.end()); + const StringPiece::size_type xpos = + static_cast<size_t>(result - self.begin()); + return xpos + s.size() <= self.size() ? xpos : StringPiece::npos; +} + +StringPiece::size_type find(const StringPiece& self, + char c, + StringPiece::size_type pos) { + if (pos >= self.size()) + return StringPiece::npos; + + StringPiece::const_iterator result = + std::find(self.begin() + pos, self.end(), c); + return result != self.end() ? + static_cast<size_t>(result - self.begin()) : StringPiece::npos; +} + +StringPiece::size_type rfind(const StringPiece& self, + const StringPiece& s, + StringPiece::size_type pos) { + if (self.size() < s.size()) + return StringPiece::npos; + + if (s.empty()) + return std::min(self.size(), pos); + + StringPiece::const_iterator last = + self.begin() + std::min(self.size() - s.size(), pos) + s.size(); + StringPiece::const_iterator result = + std::find_end(self.begin(), last, s.begin(), s.end()); + return result != last ? + static_cast<size_t>(result - self.begin()) : StringPiece::npos; +} + +StringPiece::size_type rfind(const StringPiece& self, + char c, + StringPiece::size_type pos) { + if (self.size() == 0) + return StringPiece::npos; + + for (StringPiece::size_type i = std::min(pos, self.size() - 1); ; --i) { + if (self.data()[i] == c) + return i; + if (i == 0) + break; + } + return StringPiece::npos; +} + +// For each character in characters_wanted, sets the index corresponding +// to the ASCII code of that character to 1 in table. This is used by +// the find_.*_of methods below to tell whether or not a character is in +// the lookup table in constant time. +// The argument `table' must be an array that is large enough to hold all +// the possible values of an unsigned char. Thus it should be be declared +// as follows: +// bool table[UCHAR_MAX + 1] +static inline void BuildLookupTable(const StringPiece& characters_wanted, + bool* table) { + const StringPiece::size_type length = characters_wanted.length(); + const char* const data = characters_wanted.data(); + for (StringPiece::size_type i = 0; i < length; ++i) { + table[static_cast<unsigned char>(data[i])] = true; + } +} + +StringPiece::size_type find_first_of(const StringPiece& self, + const StringPiece& s, + StringPiece::size_type pos) { + if (self.size() == 0 || s.size() == 0) + return StringPiece::npos; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.size() == 1) + return find(self, s.data()[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (StringPiece::size_type i = pos; i < self.size(); ++i) { + if (lookup[static_cast<unsigned char>(self.data()[i])]) { + return i; + } + } + return StringPiece::npos; +} + +StringPiece::size_type find_first_not_of(const StringPiece& self, + const StringPiece& s, + StringPiece::size_type pos) { + if (self.size() == 0) + return StringPiece::npos; + + if (s.size() == 0) + return 0; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.size() == 1) + return find_first_not_of(self, s.data()[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (StringPiece::size_type i = pos; i < self.size(); ++i) { + if (!lookup[static_cast<unsigned char>(self.data()[i])]) { + return i; + } + } + return StringPiece::npos; +} + +StringPiece::size_type find_first_not_of(const StringPiece& self, + char c, + StringPiece::size_type pos) { + if (self.size() == 0) + return StringPiece::npos; + + for (; pos < self.size(); ++pos) { + if (self.data()[pos] != c) { + return pos; + } + } + return StringPiece::npos; +} + +StringPiece::size_type find_last_of(const StringPiece& self, + const StringPiece& s, + StringPiece::size_type pos) { + if (self.size() == 0 || s.size() == 0) + return StringPiece::npos; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.size() == 1) + return rfind(self, s.data()[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (StringPiece::size_type i = std::min(pos, self.size() - 1); ; --i) { + if (lookup[static_cast<unsigned char>(self.data()[i])]) + return i; + if (i == 0) + break; + } + return StringPiece::npos; +} + +StringPiece::size_type find_last_not_of(const StringPiece& self, + const StringPiece& s, + StringPiece::size_type pos) { + if (self.size() == 0) + return StringPiece::npos; + + StringPiece::size_type i = std::min(pos, self.size() - 1); + if (s.size() == 0) + return i; + + // Avoid the cost of BuildLookupTable() for a single-character search. + if (s.size() == 1) + return find_last_not_of(self, s.data()[0], pos); + + bool lookup[UCHAR_MAX + 1] = { false }; + BuildLookupTable(s, lookup); + for (; ; --i) { + if (!lookup[static_cast<unsigned char>(self.data()[i])]) + return i; + if (i == 0) + break; + } + return StringPiece::npos; +} + +StringPiece::size_type find_last_not_of(const StringPiece& self, + char c, + StringPiece::size_type pos) { + if (self.size() == 0) + return StringPiece::npos; + + for (StringPiece::size_type i = std::min(pos, self.size() - 1); ; --i) { + if (self.data()[i] != c) + return i; + if (i == 0) + break; + } + return StringPiece::npos; +} + +StringPiece substr(const StringPiece& self, + StringPiece::size_type pos, + StringPiece::size_type n) { + if (pos > self.size()) pos = self.size(); + if (n > self.size() - pos) n = self.size() - pos; + return StringPiece(self.data() + pos, n); +} + +} // namespace internal +} // namespace base
diff --git a/src/base/string_piece.h b/src/base/string_piece.h new file mode 100644 index 0000000..7881e21 --- /dev/null +++ b/src/base/string_piece.h
@@ -0,0 +1,457 @@ +// 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. +// Copied from strings/stringpiece.h with modifications +// +// A string-like object that points to a sized piece of memory. +// +// Functions or methods may use const StringPiece& parameters to accept either +// a "const char*" or a "string" value that will be implicitly converted to +// a StringPiece. The implicit conversion means that it is often appropriate +// to include this .h file in other files rather than forward-declaring +// StringPiece as would be appropriate for most other Google classes. +// +// Systematic usage of StringPiece is encouraged as it will reduce unnecessary +// conversions from "const char*" to "string" and back again. +// +// StringPiece16 is similar to StringPiece but for base::string16 instead of +// std::string. We do not define as large of a subset of the STL functions +// from basic_string as in StringPiece, but this can be changed if these +// functions (find, find_first_of, etc.) are found to be useful in this context. +// + +#ifndef BASE_STRING_PIECE_H_ +#define BASE_STRING_PIECE_H_ + +#include <algorithm> +#include <iosfwd> +#include <string> +#include <stddef.h> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "base/string16.h" + +namespace base { + +template <typename STRING_TYPE> class BasicStringPiece; +typedef BasicStringPiece<std::string> StringPiece; +typedef BasicStringPiece<string16> StringPiece16; + +namespace internal { + +// Defines the types, methods, operators, and data members common to both +// StringPiece and StringPiece16. Do not refer to this class directly, but +// rather to BasicStringPiece, StringPiece, or StringPiece16. +template <typename STRING_TYPE> class StringPieceDetail { + public: + // standard STL container boilerplate + typedef size_t size_type; + typedef typename STRING_TYPE::value_type value_type; + typedef const value_type* pointer; + typedef const value_type& reference; + typedef const value_type& const_reference; + typedef ptrdiff_t difference_type; + typedef const value_type* const_iterator; + typedef std::reverse_iterator<const_iterator> const_reverse_iterator; + + static const size_type npos; + + public: + // We provide non-explicit singleton constructors so users can pass + // in a "const char*" or a "string" wherever a "StringPiece" is + // expected (likewise for char16, string16, StringPiece16). + StringPieceDetail() : ptr_(NULL), length_(0) {} + StringPieceDetail(const value_type* str) + : ptr_(str), + length_((str == NULL) ? 0 : STRING_TYPE::traits_type::length(str)) {} + StringPieceDetail(const STRING_TYPE& str) + : ptr_(str.data()), length_(str.size()) {} + StringPieceDetail(const value_type* offset, size_type len) + : ptr_(offset), length_(len) {} + StringPieceDetail(const typename STRING_TYPE::const_iterator& begin, + const typename STRING_TYPE::const_iterator& end) + : ptr_((end > begin) ? &(*begin) : NULL), + length_((end > begin) ? (size_type)(end - begin) : 0) {} + + // data() may return a pointer to a buffer with embedded NULs, and the + // returned buffer may or may not be null terminated. Therefore it is + // typically a mistake to pass data() to a routine that expects a NUL + // terminated string. + const value_type* data() const { return ptr_; } + size_type size() const { return length_; } + size_type length() const { return length_; } + bool empty() const { return length_ == 0; } + + void clear() { + ptr_ = NULL; + length_ = 0; + } + void set(const value_type* data, size_type len) { + ptr_ = data; + length_ = len; + } + void set(const value_type* str) { + ptr_ = str; + length_ = str ? STRING_TYPE::traits_type::length(str) : 0; + } + + value_type operator[](size_type i) const { return ptr_[i]; } + + void remove_prefix(size_type n) { + ptr_ += n; + length_ -= n; + } + + void remove_suffix(size_type n) { + length_ -= n; + } + + int compare(const BasicStringPiece<STRING_TYPE>& x) const { + int r = wordmemcmp( + ptr_, x.ptr_, (length_ < x.length_ ? length_ : x.length_)); + if (r == 0) { + if (length_ < x.length_) r = -1; + else if (length_ > x.length_) r = +1; + } + return r; + } + + STRING_TYPE as_string() const { + // std::string doesn't like to take a NULL pointer even with a 0 size. + return empty() ? STRING_TYPE() : STRING_TYPE(data(), size()); + } + + const_iterator begin() const { return ptr_; } + const_iterator end() const { return ptr_ + length_; } + const_reverse_iterator rbegin() const { + return const_reverse_iterator(ptr_ + length_); + } + const_reverse_iterator rend() const { + return const_reverse_iterator(ptr_); + } + + size_type max_size() const { return length_; } + size_type capacity() const { return length_; } + + static int wordmemcmp(const value_type* p, + const value_type* p2, + size_type N) { + return STRING_TYPE::traits_type::compare(p, p2, N); + } + + protected: + const value_type* ptr_; + size_type length_; +}; + +template <typename STRING_TYPE> +const typename StringPieceDetail<STRING_TYPE>::size_type +StringPieceDetail<STRING_TYPE>::npos = + typename StringPieceDetail<STRING_TYPE>::size_type(-1); + +// MSVC doesn't like complex extern templates and DLLs. +#if !defined(COMPILER_MSVC) && !defined(COMPILER_SNC) && !defined(COMPILER_GHS) +extern template class BASE_EXPORT StringPieceDetail<std::string>; +extern template class BASE_EXPORT StringPieceDetail<string16>; +#endif + +BASE_EXPORT void CopyToString(const StringPiece& self, std::string* target); +BASE_EXPORT void AppendToString(const StringPiece& self, std::string* target); +BASE_EXPORT StringPieceDetail<std::string>::size_type copy( + const StringPiece& self, + char* buf, + StringPieceDetail<std::string>::size_type n, + StringPieceDetail<std::string>::size_type pos); +BASE_EXPORT StringPieceDetail<std::string>::size_type find( + const StringPiece& self, + const StringPiece& s, + StringPieceDetail<std::string>::size_type pos); +BASE_EXPORT StringPieceDetail<std::string>::size_type find( + const StringPiece& self, + char c, + StringPieceDetail<std::string>::size_type pos); +BASE_EXPORT StringPieceDetail<std::string>::size_type rfind( + const StringPiece& self, + const StringPiece& s, + StringPieceDetail<std::string>::size_type pos); +BASE_EXPORT StringPieceDetail<std::string>::size_type rfind( + const StringPiece& self, + char c, + StringPieceDetail<std::string>::size_type pos); +BASE_EXPORT StringPieceDetail<std::string>::size_type find_first_of( + const StringPiece& self, + const StringPiece& s, + StringPieceDetail<std::string>::size_type pos); +BASE_EXPORT StringPieceDetail<std::string>::size_type find_first_not_of( + const StringPiece& self, + const StringPiece& s, + StringPieceDetail<std::string>::size_type pos); +BASE_EXPORT StringPieceDetail<std::string>::size_type find_first_not_of( + const StringPiece& self, + char c, + StringPieceDetail<std::string>::size_type pos); +BASE_EXPORT StringPieceDetail<std::string>::size_type find_last_of( + const StringPiece& self, + const StringPiece& s, + StringPieceDetail<std::string>::size_type pos); +BASE_EXPORT StringPieceDetail<std::string>::size_type find_last_of( + const StringPiece& self, + char c, + StringPieceDetail<std::string>::size_type pos); +BASE_EXPORT StringPieceDetail<std::string>::size_type find_last_not_of( + const StringPiece& self, + const StringPiece& s, + StringPieceDetail<std::string>::size_type pos); +BASE_EXPORT StringPieceDetail<std::string>::size_type find_last_not_of( + const StringPiece& self, + char c, + StringPieceDetail<std::string>::size_type pos); +BASE_EXPORT StringPiece substr(const StringPiece& self, + StringPieceDetail<std::string>::size_type pos, + StringPieceDetail<std::string>::size_type n); +} // namespace internal + +// Defines the template type that is instantiated as either StringPiece or +// StringPiece16. +template <typename STRING_TYPE> class BasicStringPiece : + public internal::StringPieceDetail<STRING_TYPE> { + public: + typedef typename internal::StringPieceDetail<STRING_TYPE>::value_type + value_type; + typedef typename internal::StringPieceDetail<STRING_TYPE>::size_type + size_type; + + BasicStringPiece() {} + BasicStringPiece(const value_type*str) + : internal::StringPieceDetail<STRING_TYPE>(str) {} + BasicStringPiece(const STRING_TYPE& str) + : internal::StringPieceDetail<STRING_TYPE>(str) {} + BasicStringPiece(const value_type* offset, size_type len) + : internal::StringPieceDetail<STRING_TYPE>(offset, len) {} + BasicStringPiece(const typename STRING_TYPE::const_iterator& begin, + const typename STRING_TYPE::const_iterator& end) + : internal::StringPieceDetail<STRING_TYPE>(begin, end) {} +}; + +// Specializes BasicStringPiece for std::string to add a few operations that +// are not needed for string16. +template <> class BasicStringPiece<std::string> : + public internal::StringPieceDetail<std::string> { + public: + BasicStringPiece() {} + BasicStringPiece(const char* str) + : internal::StringPieceDetail<std::string>(str) {} + BasicStringPiece(const std::string& str) + : internal::StringPieceDetail<std::string>(str) {} + BasicStringPiece(const char* offset, size_type len) + : internal::StringPieceDetail<std::string>(offset, len) {} + BasicStringPiece(const std::string::const_iterator& begin, + const std::string::const_iterator& end) + : internal::StringPieceDetail<std::string>(begin, end) {} + + // Prevent the following overload of set() from hiding the definitions in the + // base class. + using internal::StringPieceDetail<std::string>::set; + + void set(const void* data, size_type len) { + ptr_ = reinterpret_cast<const value_type*>(data); + length_ = len; + } + + void CopyToString(std::string* target) const { + internal::CopyToString(*this, target); + } + + void AppendToString(std::string* target) const { + internal::AppendToString(*this, target); + } + + // Does "this" start with "x" + bool starts_with(const BasicStringPiece& x) const { + return ((length_ >= x.length_) && + (wordmemcmp(ptr_, x.ptr_, x.length_) == 0)); + } + + // Does "this" end with "x" + bool ends_with(const BasicStringPiece& x) const { + return ((length_ >= x.length_) && + (wordmemcmp(ptr_ + (length_-x.length_), x.ptr_, x.length_) == 0)); + } + + size_type copy(char* buf, size_type n, size_type pos = 0) const { + return internal::copy(*this, buf, n, pos); + } + + size_type find(const BasicStringPiece& s, size_type pos = 0) const { + return internal::find(*this, s, pos); + } + + size_type find(char c, size_type pos = 0) const { + return internal::find(*this, c, pos); + } + + size_type rfind(const BasicStringPiece& s, size_type pos = npos) const { + return internal::rfind(*this, s, pos); + } + + size_type rfind(char c, size_type pos = npos) const { + return internal::rfind(*this, c, pos); + } + + size_type find_first_of(const BasicStringPiece& s, size_type pos = 0) const { + return internal::find_first_of(*this, s, pos); + } + + size_type find_first_of(char c, size_type pos = 0) const { + return find(c, pos); + } + + size_type find_first_not_of(const BasicStringPiece& s, + size_type pos = 0) const { + return internal::find_first_not_of(*this, s, pos); + } + + size_type find_first_not_of(char c, size_type pos = 0) const { + return internal::find_first_not_of(*this, c, pos); + } + + size_type find_last_of(const BasicStringPiece& s, + size_type pos = npos) const { + return internal::find_last_of(*this, s, pos); + } + + size_type find_last_of(char c, size_type pos = npos) const { + return rfind(c, pos); + } + + size_type find_last_not_of(const BasicStringPiece& s, + size_type pos = npos) const { + return internal::find_last_not_of(*this, s, pos); + } + + size_type find_last_not_of(char c, size_type pos = npos) const { + return internal::find_last_not_of(*this, c, pos); + } + + BasicStringPiece substr(size_type pos, size_type n = npos) const { + return internal::substr(*this, pos, n); + } +}; + +// MSVC doesn't like complex extern templates and DLLs. +#if !defined(COMPILER_MSVC) && !defined(COMPILER_SNC) && !defined(COMPILER_GHS) +// We can't explicitly declare the std::string instantiation here because it was +// already instantiated when specialized, above. Not only is it a no-op, but +// currently it also crashes Clang (see http://crbug.com/107412). +extern template class BASE_EXPORT BasicStringPiece<string16>; +#endif + +BASE_EXPORT bool operator==(const StringPiece& x, const StringPiece& y); + +inline bool operator!=(const StringPiece& x, const StringPiece& y) { + return !(x == y); +} + +inline bool operator<(const StringPiece& x, const StringPiece& y) { + const int r = StringPiece::wordmemcmp( + x.data(), y.data(), (x.size() < y.size() ? x.size() : y.size())); + return ((r < 0) || ((r == 0) && (x.size() < y.size()))); +} + +inline bool operator>(const StringPiece& x, const StringPiece& y) { + return y < x; +} + +inline bool operator<=(const StringPiece& x, const StringPiece& y) { + return !(x > y); +} + +inline bool operator>=(const StringPiece& x, const StringPiece& y) { + return !(x < y); +} + +inline bool operator==(const StringPiece16& x, const StringPiece16& y) { + if (x.size() != y.size()) + return false; + + return StringPiece16::wordmemcmp(x.data(), y.data(), x.size()) == 0; +} + +inline bool operator!=(const StringPiece16& x, const StringPiece16& y) { + return !(x == y); +} + +inline bool operator<(const StringPiece16& x, const StringPiece16& y) { + const int r = StringPiece16::wordmemcmp( + x.data(), y.data(), (x.size() < y.size() ? x.size() : y.size())); + return ((r < 0) || ((r == 0) && (x.size() < y.size()))); +} + +inline bool operator>(const StringPiece16& x, const StringPiece16& y) { + return y < x; +} + +inline bool operator<=(const StringPiece16& x, const StringPiece16& y) { + return !(x > y); +} + +inline bool operator>=(const StringPiece16& x, const StringPiece16& y) { + return !(x < y); +} + +BASE_EXPORT std::ostream& operator<<(std::ostream& o, + const StringPiece& piece); + +} // namespace base + +// We provide appropriate hash functions so StringPiece and StringPiece16 can +// be used as keys in hash sets and maps. + +// This hash function is copied from base/hash_tables.h. We don't use the +// ones already defined for string and string16 directly because it would +// require the string constructors to be called, which we don't want. +#define HASH_STRING_PIECE(StringPieceType, string_piece) \ + std::size_t result = 0; \ + for (StringPieceType::const_iterator i = string_piece.begin(); \ + i != string_piece.end(); ++i) \ + result = (result * 131) + *i; \ + return result; \ + +namespace BASE_HASH_NAMESPACE { +#if (!defined(OS_STARBOARD) && defined(COMPILER_GCC) && \ + !(defined(__LB_SHELL__) && !defined(__LB_LINUX__))) || \ + (defined(OS_STARBOARD) && \ + !(defined(SB_HAS_HASH_VALUE) && SB_HAS_HASH_VALUE)) + +template<> +struct hash<base::StringPiece> { + std::size_t operator()(const base::StringPiece& sp) const { + HASH_STRING_PIECE(base::StringPiece, sp); + } +}; +template<> +struct hash<base::StringPiece16> { + std::size_t operator()(const base::StringPiece16& sp16) const { + HASH_STRING_PIECE(base::StringPiece16, sp16); + } +}; + +#elif (!defined(OS_STARBOARD) && \ + (defined(COMPILER_MSVC) || defined(__LB_SHELL__))) || \ + (defined(OS_STARBOARD) && \ + defined(SB_HAS_HASH_VALUE) && SB_HAS_HASH_VALUE) + +inline size_t hash_value(const base::StringPiece& sp) { + HASH_STRING_PIECE(base::StringPiece, sp); +} +inline size_t hash_value(const base::StringPiece16& sp16) { + HASH_STRING_PIECE(base::StringPiece16, sp16); +} + +#endif // COMPILER + +} // namespace BASE_HASH_NAMESPACE + +#endif // BASE_STRING_PIECE_H_
diff --git a/src/base/string_piece_unittest.cc b/src/base/string_piece_unittest.cc new file mode 100644 index 0000000..e93dba8 --- /dev/null +++ b/src/base/string_piece_unittest.cc
@@ -0,0 +1,677 @@ +// 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 <string> + +#include "base/string16.h" +#include "base/string_piece.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +template <typename T> +class CommonStringPieceTest : public ::testing::Test { + public: + static const T as_string(const char* input) { + return T(input); + } + static const T& as_string(const T& input) { + return input; + } +}; + +template <> +class CommonStringPieceTest<string16> : public ::testing::Test { + public: + static const string16 as_string(const char* input) { + return ASCIIToUTF16(input); + } + static const string16 as_string(const std::string& input) { + return ASCIIToUTF16(input); + } +}; + +typedef ::testing::Types<std::string, string16> SupportedStringTypes; + +TYPED_TEST_CASE(CommonStringPieceTest, SupportedStringTypes); + +TYPED_TEST(CommonStringPieceTest, CheckComparisonOperators) { +#define CMP_Y(op, x, y) \ + { \ + TypeParam lhs(TestFixture::as_string(x)); \ + TypeParam rhs(TestFixture::as_string(y)); \ + ASSERT_TRUE( (BasicStringPiece<TypeParam>((lhs.c_str())) op \ + BasicStringPiece<TypeParam>((rhs.c_str())))); \ + ASSERT_TRUE( (BasicStringPiece<TypeParam>((lhs.c_str())).compare( \ + BasicStringPiece<TypeParam>((rhs.c_str()))) op 0)); \ + } + +#define CMP_N(op, x, y) \ + { \ + TypeParam lhs(TestFixture::as_string(x)); \ + TypeParam rhs(TestFixture::as_string(y)); \ + ASSERT_FALSE( (BasicStringPiece<TypeParam>((lhs.c_str())) op \ + BasicStringPiece<TypeParam>((rhs.c_str())))); \ + ASSERT_FALSE( (BasicStringPiece<TypeParam>((lhs.c_str())).compare( \ + BasicStringPiece<TypeParam>((rhs.c_str()))) op 0)); \ + } + + CMP_Y(==, "", ""); + CMP_Y(==, "a", "a"); + CMP_Y(==, "aa", "aa"); + CMP_N(==, "a", ""); + CMP_N(==, "", "a"); + CMP_N(==, "a", "b"); + CMP_N(==, "a", "aa"); + CMP_N(==, "aa", "a"); + + CMP_N(!=, "", ""); + CMP_N(!=, "a", "a"); + CMP_N(!=, "aa", "aa"); + CMP_Y(!=, "a", ""); + CMP_Y(!=, "", "a"); + CMP_Y(!=, "a", "b"); + CMP_Y(!=, "a", "aa"); + CMP_Y(!=, "aa", "a"); + + CMP_Y(<, "a", "b"); + CMP_Y(<, "a", "aa"); + CMP_Y(<, "aa", "b"); + CMP_Y(<, "aa", "bb"); + CMP_N(<, "a", "a"); + CMP_N(<, "b", "a"); + CMP_N(<, "aa", "a"); + CMP_N(<, "b", "aa"); + CMP_N(<, "bb", "aa"); + + CMP_Y(<=, "a", "a"); + CMP_Y(<=, "a", "b"); + CMP_Y(<=, "a", "aa"); + CMP_Y(<=, "aa", "b"); + CMP_Y(<=, "aa", "bb"); + CMP_N(<=, "b", "a"); + CMP_N(<=, "aa", "a"); + CMP_N(<=, "b", "aa"); + CMP_N(<=, "bb", "aa"); + + CMP_N(>=, "a", "b"); + CMP_N(>=, "a", "aa"); + CMP_N(>=, "aa", "b"); + CMP_N(>=, "aa", "bb"); + CMP_Y(>=, "a", "a"); + CMP_Y(>=, "b", "a"); + CMP_Y(>=, "aa", "a"); + CMP_Y(>=, "b", "aa"); + CMP_Y(>=, "bb", "aa"); + + CMP_N(>, "a", "a"); + CMP_N(>, "a", "b"); + CMP_N(>, "a", "aa"); + CMP_N(>, "aa", "b"); + CMP_N(>, "aa", "bb"); + CMP_Y(>, "b", "a"); + CMP_Y(>, "aa", "a"); + CMP_Y(>, "b", "aa"); + CMP_Y(>, "bb", "aa"); + + std::string x; + for (int i = 0; i < 256; i++) { + x += 'a'; + std::string y = x; + CMP_Y(==, x, y); + for (int j = 0; j < i; j++) { + std::string z = x; + z[j] = 'b'; // Differs in position 'j' + CMP_N(==, x, z); + } + } + +#undef CMP_Y +#undef CMP_N +} + +TYPED_TEST(CommonStringPieceTest, CheckSTL) { + TypeParam alphabet(TestFixture::as_string("abcdefghijklmnopqrstuvwxyz")); + TypeParam abc(TestFixture::as_string("abc")); + TypeParam xyz(TestFixture::as_string("xyz")); + TypeParam foobar(TestFixture::as_string("foobar")); + + BasicStringPiece<TypeParam> a(alphabet); + BasicStringPiece<TypeParam> b(abc); + BasicStringPiece<TypeParam> c(xyz); + BasicStringPiece<TypeParam> d(foobar); + BasicStringPiece<TypeParam> e; + TypeParam temp(TestFixture::as_string("123")); + temp += static_cast<typename TypeParam::value_type>(0); + temp += TestFixture::as_string("456"); + BasicStringPiece<TypeParam> f(temp); + + ASSERT_EQ(a[6], static_cast<typename TypeParam::value_type>('g')); + ASSERT_EQ(b[0], static_cast<typename TypeParam::value_type>('a')); + ASSERT_EQ(c[2], static_cast<typename TypeParam::value_type>('z')); + ASSERT_EQ(f[3], static_cast<typename TypeParam::value_type>('\0')); + ASSERT_EQ(f[5], static_cast<typename TypeParam::value_type>('5')); + + ASSERT_EQ(*d.data(), static_cast<typename TypeParam::value_type>('f')); + ASSERT_EQ(d.data()[5], static_cast<typename TypeParam::value_type>('r')); + ASSERT_TRUE(e.data() == NULL); + + ASSERT_EQ(*a.begin(), static_cast<typename TypeParam::value_type>('a')); + ASSERT_EQ(*(b.begin() + 2), static_cast<typename TypeParam::value_type>('c')); + ASSERT_EQ(*(c.end() - 1), static_cast<typename TypeParam::value_type>('z')); + + ASSERT_EQ(*a.rbegin(), static_cast<typename TypeParam::value_type>('z')); + ASSERT_EQ(*(b.rbegin() + 2), + static_cast<typename TypeParam::value_type>('a')); + ASSERT_EQ(*(c.rend() - 1), static_cast<typename TypeParam::value_type>('x')); + ASSERT_TRUE(a.rbegin() + 26 == a.rend()); + + ASSERT_EQ(a.size(), 26U); + ASSERT_EQ(b.size(), 3U); + ASSERT_EQ(c.size(), 3U); + ASSERT_EQ(d.size(), 6U); + ASSERT_EQ(e.size(), 0U); + ASSERT_EQ(f.size(), 7U); + + ASSERT_TRUE(!d.empty()); + ASSERT_TRUE(d.begin() != d.end()); + ASSERT_TRUE(d.begin() + 6 == d.end()); + + ASSERT_TRUE(e.empty()); + ASSERT_TRUE(e.begin() == e.end()); + + d.clear(); + ASSERT_EQ(d.size(), 0U); + ASSERT_TRUE(d.empty()); + ASSERT_TRUE(d.data() == NULL); + ASSERT_TRUE(d.begin() == d.end()); + + ASSERT_GE(a.max_size(), a.capacity()); + ASSERT_GE(a.capacity(), a.size()); +} + +// STL stuff only supported by the std::string version +TEST(StringPieceTest, CheckSTL) { + StringPiece a("abcdefghijklmnopqrstuvwxyz"); + StringPiece b("abc"); + StringPiece c("xyz"); + StringPiece d("foobar"); + d.clear(); + StringPiece e; + std::string temp("123"); + temp += '\0'; + temp += "456"; + StringPiece f(temp); + + char buf[4] = { '%', '%', '%', '%' }; + ASSERT_EQ(a.copy(buf, 4), 4U); + ASSERT_EQ(buf[0], a[0]); + ASSERT_EQ(buf[1], a[1]); + ASSERT_EQ(buf[2], a[2]); + ASSERT_EQ(buf[3], a[3]); + ASSERT_EQ(a.copy(buf, 3, 7), 3U); + ASSERT_EQ(buf[0], a[7]); + ASSERT_EQ(buf[1], a[8]); + ASSERT_EQ(buf[2], a[9]); + ASSERT_EQ(buf[3], a[3]); + ASSERT_EQ(c.copy(buf, 99), 3U); + ASSERT_EQ(buf[0], c[0]); + ASSERT_EQ(buf[1], c[1]); + ASSERT_EQ(buf[2], c[2]); + ASSERT_EQ(buf[3], a[3]); + + ASSERT_EQ(StringPiece::npos, std::string::npos); + + ASSERT_EQ(a.find(b), 0U); + ASSERT_EQ(a.find(b, 1), StringPiece::npos); + ASSERT_EQ(a.find(c), 23U); + ASSERT_EQ(a.find(c, 9), 23U); + ASSERT_EQ(a.find(c, StringPiece::npos), StringPiece::npos); + ASSERT_EQ(b.find(c), StringPiece::npos); + ASSERT_EQ(b.find(c, StringPiece::npos), StringPiece::npos); + ASSERT_EQ(a.find(d), 0U); + ASSERT_EQ(a.find(e), 0U); + ASSERT_EQ(a.find(d, 12), 12U); + ASSERT_EQ(a.find(e, 17), 17U); + StringPiece g("xx not found bb"); + ASSERT_EQ(a.find(g), StringPiece::npos); + // empty string nonsense + ASSERT_EQ(d.find(b), StringPiece::npos); + ASSERT_EQ(e.find(b), StringPiece::npos); + ASSERT_EQ(d.find(b, 4), StringPiece::npos); + ASSERT_EQ(e.find(b, 7), StringPiece::npos); + + size_t empty_search_pos = std::string().find(std::string()); + ASSERT_EQ(d.find(d), empty_search_pos); + ASSERT_EQ(d.find(e), empty_search_pos); + ASSERT_EQ(e.find(d), empty_search_pos); + ASSERT_EQ(e.find(e), empty_search_pos); + ASSERT_EQ(d.find(d, 4), std::string().find(std::string(), 4)); + ASSERT_EQ(d.find(e, 4), std::string().find(std::string(), 4)); + ASSERT_EQ(e.find(d, 4), std::string().find(std::string(), 4)); + ASSERT_EQ(e.find(e, 4), std::string().find(std::string(), 4)); + + ASSERT_EQ(a.find('a'), 0U); + ASSERT_EQ(a.find('c'), 2U); + ASSERT_EQ(a.find('z'), 25U); + ASSERT_EQ(a.find('$'), StringPiece::npos); + ASSERT_EQ(a.find('\0'), StringPiece::npos); + ASSERT_EQ(f.find('\0'), 3U); + ASSERT_EQ(f.find('3'), 2U); + ASSERT_EQ(f.find('5'), 5U); + ASSERT_EQ(g.find('o'), 4U); + ASSERT_EQ(g.find('o', 4), 4U); + ASSERT_EQ(g.find('o', 5), 8U); + ASSERT_EQ(a.find('b', 5), StringPiece::npos); + // empty string nonsense + ASSERT_EQ(d.find('\0'), StringPiece::npos); + ASSERT_EQ(e.find('\0'), StringPiece::npos); + ASSERT_EQ(d.find('\0', 4), StringPiece::npos); + ASSERT_EQ(e.find('\0', 7), StringPiece::npos); + ASSERT_EQ(d.find('x'), StringPiece::npos); + ASSERT_EQ(e.find('x'), StringPiece::npos); + ASSERT_EQ(d.find('x', 4), StringPiece::npos); + ASSERT_EQ(e.find('x', 7), StringPiece::npos); + + ASSERT_EQ(a.rfind(b), 0U); + ASSERT_EQ(a.rfind(b, 1), 0U); + ASSERT_EQ(a.rfind(c), 23U); + ASSERT_EQ(a.rfind(c, 22U), StringPiece::npos); + ASSERT_EQ(a.rfind(c, 1U), StringPiece::npos); + ASSERT_EQ(a.rfind(c, 0U), StringPiece::npos); + ASSERT_EQ(b.rfind(c), StringPiece::npos); + ASSERT_EQ(b.rfind(c, 0U), StringPiece::npos); + ASSERT_EQ(a.rfind(d), (size_t) a.as_string().rfind(std::string())); + ASSERT_EQ(a.rfind(e), a.as_string().rfind(std::string())); + ASSERT_EQ(a.rfind(d, 12), 12U); + ASSERT_EQ(a.rfind(e, 17), 17U); + ASSERT_EQ(a.rfind(g), StringPiece::npos); + ASSERT_EQ(d.rfind(b), StringPiece::npos); + ASSERT_EQ(e.rfind(b), StringPiece::npos); + ASSERT_EQ(d.rfind(b, 4), StringPiece::npos); + ASSERT_EQ(e.rfind(b, 7), StringPiece::npos); + // empty string nonsense + ASSERT_EQ(d.rfind(d, 4), std::string().rfind(std::string())); + ASSERT_EQ(e.rfind(d, 7), std::string().rfind(std::string())); + ASSERT_EQ(d.rfind(e, 4), std::string().rfind(std::string())); + ASSERT_EQ(e.rfind(e, 7), std::string().rfind(std::string())); + ASSERT_EQ(d.rfind(d), std::string().rfind(std::string())); + ASSERT_EQ(e.rfind(d), std::string().rfind(std::string())); + ASSERT_EQ(d.rfind(e), std::string().rfind(std::string())); + ASSERT_EQ(e.rfind(e), std::string().rfind(std::string())); + + ASSERT_EQ(g.rfind('o'), 8U); + ASSERT_EQ(g.rfind('q'), StringPiece::npos); + ASSERT_EQ(g.rfind('o', 8), 8U); + ASSERT_EQ(g.rfind('o', 7), 4U); + ASSERT_EQ(g.rfind('o', 3), StringPiece::npos); + ASSERT_EQ(f.rfind('\0'), 3U); + ASSERT_EQ(f.rfind('\0', 12), 3U); + ASSERT_EQ(f.rfind('3'), 2U); + ASSERT_EQ(f.rfind('5'), 5U); + // empty string nonsense + ASSERT_EQ(d.rfind('o'), StringPiece::npos); + ASSERT_EQ(e.rfind('o'), StringPiece::npos); + ASSERT_EQ(d.rfind('o', 4), StringPiece::npos); + ASSERT_EQ(e.rfind('o', 7), StringPiece::npos); + + ASSERT_EQ( + StringPiece("one,two:three;four").find_first_of(StringPiece(",:"), 1), + 3U); + ASSERT_EQ(a.find_first_of(b), 0U); + ASSERT_EQ(a.find_first_of(b, 0), 0U); + ASSERT_EQ(a.find_first_of(b, 1), 1U); + ASSERT_EQ(a.find_first_of(b, 2), 2U); + ASSERT_EQ(a.find_first_of(b, 3), StringPiece::npos); + ASSERT_EQ(a.find_first_of(c), 23U); + ASSERT_EQ(a.find_first_of(c, 23), 23U); + ASSERT_EQ(a.find_first_of(c, 24), 24U); + ASSERT_EQ(a.find_first_of(c, 25), 25U); + ASSERT_EQ(a.find_first_of(c, 26), StringPiece::npos); + ASSERT_EQ(g.find_first_of(b), 13U); + ASSERT_EQ(g.find_first_of(c), 0U); + ASSERT_EQ(a.find_first_of(f), StringPiece::npos); + ASSERT_EQ(f.find_first_of(a), StringPiece::npos); + // empty string nonsense + ASSERT_EQ(a.find_first_of(d), StringPiece::npos); + ASSERT_EQ(a.find_first_of(e), StringPiece::npos); + ASSERT_EQ(d.find_first_of(b), StringPiece::npos); + ASSERT_EQ(e.find_first_of(b), StringPiece::npos); + ASSERT_EQ(d.find_first_of(d), StringPiece::npos); + ASSERT_EQ(e.find_first_of(d), StringPiece::npos); + ASSERT_EQ(d.find_first_of(e), StringPiece::npos); + ASSERT_EQ(e.find_first_of(e), StringPiece::npos); + + ASSERT_EQ(a.find_first_not_of(b), 3U); + ASSERT_EQ(a.find_first_not_of(c), 0U); + ASSERT_EQ(b.find_first_not_of(a), StringPiece::npos); + ASSERT_EQ(c.find_first_not_of(a), StringPiece::npos); + ASSERT_EQ(f.find_first_not_of(a), 0U); + ASSERT_EQ(a.find_first_not_of(f), 0U); + ASSERT_EQ(a.find_first_not_of(d), 0U); + ASSERT_EQ(a.find_first_not_of(e), 0U); + // empty string nonsense + ASSERT_EQ(d.find_first_not_of(a), StringPiece::npos); + ASSERT_EQ(e.find_first_not_of(a), StringPiece::npos); + ASSERT_EQ(d.find_first_not_of(d), StringPiece::npos); + ASSERT_EQ(e.find_first_not_of(d), StringPiece::npos); + ASSERT_EQ(d.find_first_not_of(e), StringPiece::npos); + ASSERT_EQ(e.find_first_not_of(e), StringPiece::npos); + + StringPiece h("===="); + ASSERT_EQ(h.find_first_not_of('='), StringPiece::npos); + ASSERT_EQ(h.find_first_not_of('=', 3), StringPiece::npos); + ASSERT_EQ(h.find_first_not_of('\0'), 0U); + ASSERT_EQ(g.find_first_not_of('x'), 2U); + ASSERT_EQ(f.find_first_not_of('\0'), 0U); + ASSERT_EQ(f.find_first_not_of('\0', 3), 4U); + ASSERT_EQ(f.find_first_not_of('\0', 2), 2U); + // empty string nonsense + ASSERT_EQ(d.find_first_not_of('x'), StringPiece::npos); + ASSERT_EQ(e.find_first_not_of('x'), StringPiece::npos); + ASSERT_EQ(d.find_first_not_of('\0'), StringPiece::npos); + ASSERT_EQ(e.find_first_not_of('\0'), StringPiece::npos); + + // StringPiece g("xx not found bb"); + StringPiece i("56"); + ASSERT_EQ(h.find_last_of(a), StringPiece::npos); + ASSERT_EQ(g.find_last_of(a), g.size()-1); + ASSERT_EQ(a.find_last_of(b), 2U); + ASSERT_EQ(a.find_last_of(c), a.size()-1); + ASSERT_EQ(f.find_last_of(i), 6U); + ASSERT_EQ(a.find_last_of('a'), 0U); + ASSERT_EQ(a.find_last_of('b'), 1U); + ASSERT_EQ(a.find_last_of('z'), 25U); + ASSERT_EQ(a.find_last_of('a', 5), 0U); + ASSERT_EQ(a.find_last_of('b', 5), 1U); + ASSERT_EQ(a.find_last_of('b', 0), StringPiece::npos); + ASSERT_EQ(a.find_last_of('z', 25), 25U); + ASSERT_EQ(a.find_last_of('z', 24), StringPiece::npos); + ASSERT_EQ(f.find_last_of(i, 5), 5U); + ASSERT_EQ(f.find_last_of(i, 6), 6U); + ASSERT_EQ(f.find_last_of(a, 4), StringPiece::npos); + // empty string nonsense + ASSERT_EQ(f.find_last_of(d), StringPiece::npos); + ASSERT_EQ(f.find_last_of(e), StringPiece::npos); + ASSERT_EQ(f.find_last_of(d, 4), StringPiece::npos); + ASSERT_EQ(f.find_last_of(e, 4), StringPiece::npos); + ASSERT_EQ(d.find_last_of(d), StringPiece::npos); + ASSERT_EQ(d.find_last_of(e), StringPiece::npos); + ASSERT_EQ(e.find_last_of(d), StringPiece::npos); + ASSERT_EQ(e.find_last_of(e), StringPiece::npos); + ASSERT_EQ(d.find_last_of(f), StringPiece::npos); + ASSERT_EQ(e.find_last_of(f), StringPiece::npos); + ASSERT_EQ(d.find_last_of(d, 4), StringPiece::npos); + ASSERT_EQ(d.find_last_of(e, 4), StringPiece::npos); + ASSERT_EQ(e.find_last_of(d, 4), StringPiece::npos); + ASSERT_EQ(e.find_last_of(e, 4), StringPiece::npos); + ASSERT_EQ(d.find_last_of(f, 4), StringPiece::npos); + ASSERT_EQ(e.find_last_of(f, 4), StringPiece::npos); + + ASSERT_EQ(a.find_last_not_of(b), a.size()-1); + ASSERT_EQ(a.find_last_not_of(c), 22U); + ASSERT_EQ(b.find_last_not_of(a), StringPiece::npos); + ASSERT_EQ(b.find_last_not_of(b), StringPiece::npos); + ASSERT_EQ(f.find_last_not_of(i), 4U); + ASSERT_EQ(a.find_last_not_of(c, 24), 22U); + ASSERT_EQ(a.find_last_not_of(b, 3), 3U); + ASSERT_EQ(a.find_last_not_of(b, 2), StringPiece::npos); + // empty string nonsense + ASSERT_EQ(f.find_last_not_of(d), f.size()-1); + ASSERT_EQ(f.find_last_not_of(e), f.size()-1); + ASSERT_EQ(f.find_last_not_of(d, 4), 4U); + ASSERT_EQ(f.find_last_not_of(e, 4), 4U); + ASSERT_EQ(d.find_last_not_of(d), StringPiece::npos); + ASSERT_EQ(d.find_last_not_of(e), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of(d), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of(e), StringPiece::npos); + ASSERT_EQ(d.find_last_not_of(f), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of(f), StringPiece::npos); + ASSERT_EQ(d.find_last_not_of(d, 4), StringPiece::npos); + ASSERT_EQ(d.find_last_not_of(e, 4), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of(d, 4), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of(e, 4), StringPiece::npos); + ASSERT_EQ(d.find_last_not_of(f, 4), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of(f, 4), StringPiece::npos); + + ASSERT_EQ(h.find_last_not_of('x'), h.size() - 1); + ASSERT_EQ(h.find_last_not_of('='), StringPiece::npos); + ASSERT_EQ(b.find_last_not_of('c'), 1U); + ASSERT_EQ(h.find_last_not_of('x', 2), 2U); + ASSERT_EQ(h.find_last_not_of('=', 2), StringPiece::npos); + ASSERT_EQ(b.find_last_not_of('b', 1), 0U); + // empty string nonsense + ASSERT_EQ(d.find_last_not_of('x'), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of('x'), StringPiece::npos); + ASSERT_EQ(d.find_last_not_of('\0'), StringPiece::npos); + ASSERT_EQ(e.find_last_not_of('\0'), StringPiece::npos); + + ASSERT_EQ(a.substr(0, 3), b); + ASSERT_EQ(a.substr(23), c); + ASSERT_EQ(a.substr(23, 3), c); + ASSERT_EQ(a.substr(23, 99), c); + ASSERT_EQ(a.substr(0), a); + ASSERT_EQ(a.substr(3, 2), "de"); + // empty string nonsense + ASSERT_EQ(a.substr(99, 2), e); + ASSERT_EQ(d.substr(99), e); + ASSERT_EQ(d.substr(0, 99), e); + ASSERT_EQ(d.substr(99, 99), e); +} + +TYPED_TEST(CommonStringPieceTest, CheckCustom) { + TypeParam foobar(TestFixture::as_string("foobar")); + BasicStringPiece<TypeParam> a(foobar); + TypeParam s1(TestFixture::as_string("123")); + s1 += static_cast<typename TypeParam::value_type>('\0'); + s1 += TestFixture::as_string("456"); + BasicStringPiece<TypeParam> b(s1); + BasicStringPiece<TypeParam> e; + TypeParam s2; + + // remove_prefix + BasicStringPiece<TypeParam> c(a); + c.remove_prefix(3); + ASSERT_EQ(c, TestFixture::as_string("bar")); + c = a; + c.remove_prefix(0); + ASSERT_EQ(c, a); + c.remove_prefix(c.size()); + ASSERT_EQ(c, e); + + // remove_suffix + c = a; + c.remove_suffix(3); + ASSERT_EQ(c, TestFixture::as_string("foo")); + c = a; + c.remove_suffix(0); + ASSERT_EQ(c, a); + c.remove_suffix(c.size()); + ASSERT_EQ(c, e); + + // set + c.set(foobar.c_str()); + ASSERT_EQ(c, a); + c.set(foobar.c_str(), 6); + ASSERT_EQ(c, a); + c.set(foobar.c_str(), 0); + ASSERT_EQ(c, e); + c.set(foobar.c_str(), 7); // Note, has an embedded NULL + ASSERT_NE(c, a); + + // as_string + TypeParam s3(a.as_string().c_str(), 7); // Note, has an embedded NULL + ASSERT_TRUE(c == s3); + TypeParam s4(e.as_string()); + ASSERT_TRUE(s4.empty()); +} + +TEST(StringPieceTest, CheckCustom) { + StringPiece a("foobar"); + std::string s1("123"); + s1 += '\0'; + s1 += "456"; + StringPiece b(s1); + StringPiece e; + std::string s2; + + // CopyToString + a.CopyToString(&s2); + ASSERT_EQ(s2.size(), 6U); + ASSERT_EQ(s2, "foobar"); + b.CopyToString(&s2); + ASSERT_EQ(s2.size(), 7U); + ASSERT_EQ(s1, s2); + e.CopyToString(&s2); + ASSERT_TRUE(s2.empty()); + + // AppendToString + s2.erase(); + a.AppendToString(&s2); + ASSERT_EQ(s2.size(), 6U); + ASSERT_EQ(s2, "foobar"); + a.AppendToString(&s2); + ASSERT_EQ(s2.size(), 12U); + ASSERT_EQ(s2, "foobarfoobar"); + + // starts_with + ASSERT_TRUE(a.starts_with(a)); + ASSERT_TRUE(a.starts_with("foo")); + ASSERT_TRUE(a.starts_with(e)); + ASSERT_TRUE(b.starts_with(s1)); + ASSERT_TRUE(b.starts_with(b)); + ASSERT_TRUE(b.starts_with(e)); + ASSERT_TRUE(e.starts_with("")); + ASSERT_TRUE(!a.starts_with(b)); + ASSERT_TRUE(!b.starts_with(a)); + ASSERT_TRUE(!e.starts_with(a)); + + // ends with + ASSERT_TRUE(a.ends_with(a)); + ASSERT_TRUE(a.ends_with("bar")); + ASSERT_TRUE(a.ends_with(e)); + ASSERT_TRUE(b.ends_with(s1)); + ASSERT_TRUE(b.ends_with(b)); + ASSERT_TRUE(b.ends_with(e)); + ASSERT_TRUE(e.ends_with("")); + ASSERT_TRUE(!a.ends_with(b)); + ASSERT_TRUE(!b.ends_with(a)); + ASSERT_TRUE(!e.ends_with(a)); + + StringPiece c; + c.set(static_cast<const void*>("foobar"), 6); + ASSERT_EQ(c, a); + c.set(static_cast<const void*>("foobar"), 0); + ASSERT_EQ(c, e); + c.set(static_cast<const void*>("foobar"), 7); + ASSERT_NE(c, a); +} + +TYPED_TEST(CommonStringPieceTest, CheckNULL) { + // we used to crash here, but now we don't. + BasicStringPiece<TypeParam> s(NULL); + ASSERT_EQ(s.data(), (const typename TypeParam::value_type*)NULL); + ASSERT_EQ(s.size(), 0U); + + s.set(NULL); + ASSERT_EQ(s.data(), (const typename TypeParam::value_type*)NULL); + ASSERT_EQ(s.size(), 0U); + + TypeParam str = s.as_string(); + ASSERT_EQ(str.length(), 0U); + ASSERT_EQ(str, TypeParam()); +} + +TYPED_TEST(CommonStringPieceTest, CheckComparisons2) { + TypeParam alphabet(TestFixture::as_string("abcdefghijklmnopqrstuvwxyz")); + TypeParam alphabet_z(TestFixture::as_string("abcdefghijklmnopqrstuvwxyzz")); + TypeParam alphabet_y(TestFixture::as_string("abcdefghijklmnopqrstuvwxyy")); + BasicStringPiece<TypeParam> abc(alphabet); + + // check comparison operations on strings longer than 4 bytes. + ASSERT_TRUE(abc == BasicStringPiece<TypeParam>(alphabet)); + ASSERT_TRUE(abc.compare(BasicStringPiece<TypeParam>(alphabet)) == 0); + + ASSERT_TRUE(abc < BasicStringPiece<TypeParam>(alphabet_z)); + ASSERT_TRUE(abc.compare(BasicStringPiece<TypeParam>(alphabet_z)) < 0); + + ASSERT_TRUE(abc > BasicStringPiece<TypeParam>(alphabet_y)); + ASSERT_TRUE(abc.compare(BasicStringPiece<TypeParam>(alphabet_y)) > 0); +} + +// Test operations only supported by std::string version. +TEST(StringPieceTest, CheckComparisons2) { + StringPiece abc("abcdefghijklmnopqrstuvwxyz"); + + // starts_with + ASSERT_TRUE(abc.starts_with(abc)); + ASSERT_TRUE(abc.starts_with("abcdefghijklm")); + ASSERT_TRUE(!abc.starts_with("abcdefguvwxyz")); + + // ends_with + ASSERT_TRUE(abc.ends_with(abc)); + ASSERT_TRUE(!abc.ends_with("abcdefguvwxyz")); + ASSERT_TRUE(abc.ends_with("nopqrstuvwxyz")); +} + +TYPED_TEST(CommonStringPieceTest, StringCompareNotAmbiguous) { + ASSERT_TRUE(TestFixture::as_string("hello").c_str() == + TestFixture::as_string("hello")); + ASSERT_TRUE(TestFixture::as_string("hello").c_str() < + TestFixture::as_string("world")); +} + +TYPED_TEST(CommonStringPieceTest, HeterogenousStringPieceEquals) { + TypeParam hello(TestFixture::as_string("hello")); + + ASSERT_TRUE(BasicStringPiece<TypeParam>(hello) == hello); + ASSERT_TRUE(hello.c_str() == BasicStringPiece<TypeParam>(hello)); +} + +// string16-specific stuff +TEST(StringPiece16Test, CheckSTL) { + // Check some non-ascii characters. + string16 fifth(ASCIIToUTF16("123")); + fifth.push_back(0x0000); + fifth.push_back(0xd8c5); + fifth.push_back(0xdffe); + StringPiece16 f(fifth); + + ASSERT_EQ(f[3], '\0'); + ASSERT_EQ(f[5], static_cast<char16>(0xdffe)); + + ASSERT_EQ(f.size(), 6U); +} + + + +TEST(StringPiece16Test, CheckConversion) { + // Make sure that we can convert from UTF8 to UTF16 and back. We use a two + // byte character (G clef) to test this. + ASSERT_EQ( + UTF16ToUTF8( + StringPiece16(UTF8ToUTF16("\xf0\x9d\x84\x9e")).as_string()), + "\xf0\x9d\x84\x9e"); +} + +TYPED_TEST(CommonStringPieceTest, CheckConstructors) { + TypeParam str(TestFixture::as_string("hello world")); + TypeParam empty; + + ASSERT_TRUE(str == BasicStringPiece<TypeParam>(str)); + ASSERT_TRUE(str == BasicStringPiece<TypeParam>(str.c_str())); + ASSERT_TRUE(TestFixture::as_string("hello") == + BasicStringPiece<TypeParam>(str.c_str(), 5)); + ASSERT_TRUE(empty == BasicStringPiece<TypeParam>(str.c_str(), 0U)); + ASSERT_TRUE(empty == BasicStringPiece<TypeParam>(NULL)); + ASSERT_TRUE(empty == BasicStringPiece<TypeParam>(NULL, 0U)); + ASSERT_TRUE(empty == BasicStringPiece<TypeParam>()); + ASSERT_TRUE(str == BasicStringPiece<TypeParam>(str.begin(), str.end())); + ASSERT_TRUE(empty == BasicStringPiece<TypeParam>(str.begin(), str.begin())); + ASSERT_TRUE(empty == BasicStringPiece<TypeParam>(empty)); + ASSERT_TRUE(empty == BasicStringPiece<TypeParam>(empty.begin(), empty.end())); +} + +} // namespace base
diff --git a/src/base/string_split.cc b/src/base/string_split.cc new file mode 100644 index 0000000..e5befcf --- /dev/null +++ b/src/base/string_split.cc
@@ -0,0 +1,219 @@ +// 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/string_split.h" + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/third_party/icu/icu_utf.h" +#include "base/utf_string_conversions.h" + +namespace base { + +template<typename STR> +static void SplitStringT(const STR& str, + const typename STR::value_type s, + bool trim_whitespace, + std::vector<STR>* r) { + r->clear(); + size_t last = 0; + size_t c = str.size(); + for (size_t i = 0; i <= c; ++i) { + if (i == c || str[i] == s) { + STR tmp(str, last, i - last); + if (trim_whitespace) + TrimWhitespace(tmp, TRIM_ALL, &tmp); + // Avoid converting an empty or all-whitespace source string into a vector + // of one empty string. + if (i != c || !r->empty() || !tmp.empty()) + r->push_back(tmp); + last = i + 1; + } + } +} + +void SplitString(const string16& str, + char16 c, + std::vector<string16>* r) { + DCHECK(CBU16_IS_SINGLE(c)); + SplitStringT(str, c, true, r); +} + +void SplitString(const std::string& str, + char c, + std::vector<std::string>* r) { +#if CHAR_MIN < 0 + DCHECK(c >= 0); +#endif + DCHECK(c < 0x7F); + SplitStringT(str, c, true, r); +} + +bool SplitStringIntoKeyValues( + const std::string& line, + char key_value_delimiter, + std::string* key, std::vector<std::string>* values) { + key->clear(); + values->clear(); + + // Find the key string. + size_t end_key_pos = line.find_first_of(key_value_delimiter); + if (end_key_pos == std::string::npos) { + DVLOG(1) << "cannot parse key from line: " << line; + return false; // no key + } + key->assign(line, 0, end_key_pos); + + // Find the values string. + std::string remains(line, end_key_pos, line.size() - end_key_pos); + size_t begin_values_pos = remains.find_first_not_of(key_value_delimiter); + if (begin_values_pos == std::string::npos) { + DVLOG(1) << "cannot parse value from line: " << line; + return false; // no value + } + std::string values_string(remains, begin_values_pos, + remains.size() - begin_values_pos); + + // Construct the values vector. + values->push_back(values_string); + return true; +} + +bool SplitStringIntoKeyValuePairs( + const std::string& line, + char key_value_delimiter, + char key_value_pair_delimiter, + std::vector<std::pair<std::string, std::string> >* kv_pairs) { + kv_pairs->clear(); + + std::vector<std::string> pairs; + SplitString(line, key_value_pair_delimiter, &pairs); + + bool success = true; + for (size_t i = 0; i < pairs.size(); ++i) { + // Empty pair. SplitStringIntoKeyValues is more strict about an empty pair + // line, so continue with the next pair. + if (pairs[i].empty()) + continue; + + std::string key; + std::vector<std::string> value; + if (!SplitStringIntoKeyValues(pairs[i], + key_value_delimiter, + &key, &value)) { + // Don't return here, to allow for keys without associated + // values; just record that our split failed. + success = false; + } + DCHECK_LE(value.size(), 1U); + kv_pairs->push_back(make_pair(key, value.empty()? "" : value[0])); + } + return success; +} + +template <typename STR> +static void SplitStringUsingSubstrT(const STR& str, + const STR& s, + std::vector<STR>* r) { + r->clear(); + typename STR::size_type begin_index = 0; + while (true) { + const typename STR::size_type end_index = str.find(s, begin_index); + if (end_index == STR::npos) { + const STR term = str.substr(begin_index); + STR tmp; + TrimWhitespace(term, TRIM_ALL, &tmp); + r->push_back(tmp); + return; + } + const STR term = str.substr(begin_index, end_index - begin_index); + STR tmp; + TrimWhitespace(term, TRIM_ALL, &tmp); + r->push_back(tmp); + begin_index = end_index + s.size(); + } +} + +void SplitStringUsingSubstr(const string16& str, + const string16& s, + std::vector<string16>* r) { + SplitStringUsingSubstrT(str, s, r); +} + +void SplitStringUsingSubstr(const std::string& str, + const std::string& s, + std::vector<std::string>* r) { + SplitStringUsingSubstrT(str, s, r); +} + +void SplitStringDontTrim(const string16& str, + char16 c, + std::vector<string16>* r) { + DCHECK(CBU16_IS_SINGLE(c)); + SplitStringT(str, c, false, r); +} + +void SplitStringDontTrim(const std::string& str, + char c, + std::vector<std::string>* r) { + DCHECK(IsStringUTF8(str)); +#if CHAR_MIN < 0 + DCHECK(c >= 0); +#endif + DCHECK(c < 0x7F); + SplitStringT(str, c, false, r); +} + +template<typename STR> +void SplitStringAlongWhitespaceT(const STR& str, std::vector<STR>* result) { + result->clear(); + const size_t length = str.length(); + if (!length) + return; + + bool last_was_ws = false; + size_t last_non_ws_start = 0; + for (size_t i = 0; i < length; ++i) { + switch (str[i]) { + // HTML 5 defines whitespace as: space, tab, LF, line tab, FF, or CR. + case L' ': + case L'\t': + case L'\xA': + case L'\xB': + case L'\xC': + case L'\xD': + if (!last_was_ws) { + if (i > 0) { + result->push_back( + str.substr(last_non_ws_start, i - last_non_ws_start)); + } + last_was_ws = true; + } + break; + + default: // Not a space character. + if (last_was_ws) { + last_was_ws = false; + last_non_ws_start = i; + } + break; + } + } + if (!last_was_ws) { + result->push_back( + str.substr(last_non_ws_start, length - last_non_ws_start)); + } +} + +void SplitStringAlongWhitespace(const string16& str, + std::vector<string16>* result) { + SplitStringAlongWhitespaceT(str, result); +} + +void SplitStringAlongWhitespace(const std::string& str, + std::vector<std::string>* result) { + SplitStringAlongWhitespaceT(str, result); +} + +} // namespace base
diff --git a/src/base/string_split.h b/src/base/string_split.h new file mode 100644 index 0000000..f8022e3 --- /dev/null +++ b/src/base/string_split.h
@@ -0,0 +1,81 @@ +// 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_STRING_SPLIT_H_ +#define BASE_STRING_SPLIT_H_ + +#include <string> +#include <utility> +#include <vector> + +#include "base/base_export.h" +#include "base/string16.h" + +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. +// +// Every substring is trimmed of any leading or trailing white space. +// NOTE: |c| must be in BMP (Basic Multilingual Plane) +BASE_EXPORT void SplitString(const string16& str, + char16 c, + std::vector<string16>* r); +// |str| should not be in a multi-byte encoding like Shift-JIS or GBK in which +// the trailing byte of a multi-byte character can be in the ASCII range. +// UTF-8, and other single/multi-byte ASCII-compatible encodings are OK. +// Note: |c| must be in the ASCII range. +BASE_EXPORT void SplitString(const std::string& str, + char c, + std::vector<std::string>* r); + +BASE_EXPORT bool SplitStringIntoKeyValues( + const std::string& line, + char key_value_delimiter, + std::string* key, std::vector<std::string>* values); + +BASE_EXPORT bool SplitStringIntoKeyValuePairs( + const std::string& line, + char key_value_delimiter, + char key_value_pair_delimiter, + std::vector<std::pair<std::string, std::string> >* kv_pairs); + +// The same as SplitString, but use a substring delimiter instead of a char. +BASE_EXPORT void SplitStringUsingSubstr(const string16& str, + const string16& s, + std::vector<string16>* r); +BASE_EXPORT void SplitStringUsingSubstr(const std::string& str, + const std::string& s, + std::vector<std::string>* r); + +// The same as SplitString, but don't trim white space. +// NOTE: |c| must be in BMP (Basic Multilingual Plane) +BASE_EXPORT void SplitStringDontTrim(const string16& str, + char16 c, + std::vector<string16>* r); +// |str| should not be in a multi-byte encoding like Shift-JIS or GBK in which +// the trailing byte of a multi-byte character can be in the ASCII range. +// UTF-8, and other single/multi-byte ASCII-compatible encodings are OK. +// Note: |c| must be in the ASCII range. +BASE_EXPORT void SplitStringDontTrim(const std::string& str, + char c, + std::vector<std::string>* r); + +// WARNING: this uses whitespace as defined by the HTML5 spec. If you need +// a function similar to this but want to trim all types of whitespace, then +// factor this out into a function that takes a string containing the characters +// that are treated as whitespace. +// +// Splits the string along whitespace (where whitespace is the five space +// characters defined by HTML 5). Each contiguous block of non-whitespace +// characters is added to result. +BASE_EXPORT void SplitStringAlongWhitespace(const string16& str, + std::vector<string16>* result); +BASE_EXPORT void SplitStringAlongWhitespace(const std::string& str, + std::vector<std::string>* result); + +} // namespace base + +#endif // BASE_STRING_SPLIT_H
diff --git a/src/base/string_split_unittest.cc b/src/base/string_split_unittest.cc new file mode 100644 index 0000000..a4412e4 --- /dev/null +++ b/src/base/string_split_unittest.cc
@@ -0,0 +1,316 @@ +// 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/string_split.h" + +#include "base/utf_string_conversions.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::ElementsAre; + +namespace base { + +namespace { + +#if !defined(WCHAR_T_IS_UTF16) +// Overload SplitString with a wide-char version to make it easier to +// test the string16 version with wide character literals. +void SplitString(const std::wstring& str, + wchar_t c, + std::vector<std::wstring>* result) { + std::vector<string16> result16; + SplitString(WideToUTF16(str), c, &result16); + for (size_t i = 0; i < result16.size(); ++i) + result->push_back(UTF16ToWide(result16[i])); +} +#endif + +} // anonymous namespace + +class SplitStringIntoKeyValuesTest : public testing::Test { + protected: + std::string key; + std::vector<std::string> values; +}; + +TEST_F(SplitStringIntoKeyValuesTest, EmptyInputMultipleValues) { + EXPECT_FALSE(SplitStringIntoKeyValues("", // Empty input + '\t', // Key separators + &key, &values)); + EXPECT_TRUE(key.empty()); + EXPECT_TRUE(values.empty()); +} + +TEST_F(SplitStringIntoKeyValuesTest, EmptyValueInputMultipleValues) { + EXPECT_FALSE(SplitStringIntoKeyValues("key_with_no_value\t", + '\t', // Key separators + &key, &values)); + EXPECT_EQ("key_with_no_value", key); + EXPECT_TRUE(values.empty()); +} + +TEST_F(SplitStringIntoKeyValuesTest, EmptyKeyInputMultipleValues) { + EXPECT_TRUE(SplitStringIntoKeyValues("\tvalue for empty key", + '\t', // Key separators + &key, &values)); + EXPECT_TRUE(key.empty()); + ASSERT_EQ(1U, values.size()); +} + +TEST_F(SplitStringIntoKeyValuesTest, KeyWithMultipleValues) { + EXPECT_TRUE(SplitStringIntoKeyValues("key1\tvalue1, value2 value3", + '\t', // Key separators + &key, &values)); + EXPECT_EQ("key1", key); + ASSERT_EQ(1U, values.size()); + EXPECT_EQ("value1, value2 value3", values[0]); +} + +TEST_F(SplitStringIntoKeyValuesTest, EmptyInputSingleValue) { + EXPECT_FALSE(SplitStringIntoKeyValues("", // Empty input + '\t', // Key separators + &key, &values)); + EXPECT_TRUE(key.empty()); + EXPECT_TRUE(values.empty()); +} + +TEST_F(SplitStringIntoKeyValuesTest, EmptyValueInputSingleValue) { + EXPECT_FALSE(SplitStringIntoKeyValues("key_with_no_value\t", + '\t', // Key separators + &key, &values)); + EXPECT_EQ("key_with_no_value", key); + EXPECT_TRUE(values.empty()); +} + +TEST_F(SplitStringIntoKeyValuesTest, EmptyKeyInputSingleValue) { + EXPECT_TRUE(SplitStringIntoKeyValues("\tvalue for empty key", + '\t', // Key separators + &key, &values)); + EXPECT_TRUE(key.empty()); + ASSERT_EQ(1U, values.size()); + EXPECT_EQ("value for empty key", values[0]); +} + +TEST_F(SplitStringIntoKeyValuesTest, KeyWithSingleValue) { + EXPECT_TRUE(SplitStringIntoKeyValues("key1\tvalue1, value2 value3", + '\t', // Key separators + &key, &values)); + EXPECT_EQ("key1", key); + ASSERT_EQ(1U, values.size()); + EXPECT_EQ("value1, value2 value3", values[0]); +} + +class SplitStringIntoKeyValuePairsTest : public testing::Test { + protected: + std::vector<std::pair<std::string, std::string> > kv_pairs; +}; + +TEST_F(SplitStringIntoKeyValuePairsTest, EmptyString) { + EXPECT_TRUE(SplitStringIntoKeyValuePairs("", + ':', // Key-value delimiters + ',', // Key-value pair delims + &kv_pairs)); + EXPECT_TRUE(kv_pairs.empty()); +} + +TEST_F(SplitStringIntoKeyValuePairsTest, EmptySecondPair) { + EXPECT_TRUE(SplitStringIntoKeyValuePairs("key1:value1,,key3:value3", + ':', // Key-value delimiters + ',', // Key-value pair delims + &kv_pairs)); + ASSERT_EQ(2U, kv_pairs.size()); + EXPECT_EQ("key1", kv_pairs[0].first); + EXPECT_EQ("value1", kv_pairs[0].second); + EXPECT_EQ("key3", kv_pairs[1].first); + EXPECT_EQ("value3", kv_pairs[1].second); +} + +TEST_F(SplitStringIntoKeyValuePairsTest, EmptySecondValue) { + EXPECT_FALSE(SplitStringIntoKeyValuePairs("key1:value1 , key2:", + ':', // Key-value delimiters + ',', // Key-value pair delims + &kv_pairs)); + ASSERT_EQ(2U, kv_pairs.size()); + EXPECT_EQ("key1", kv_pairs[0].first); + EXPECT_EQ("value1", kv_pairs[0].second); + EXPECT_EQ("key2", kv_pairs[1].first); + EXPECT_EQ("", kv_pairs[1].second); +} + +TEST_F(SplitStringIntoKeyValuePairsTest, DelimiterInValue) { + EXPECT_TRUE(SplitStringIntoKeyValuePairs("key1:va:ue1 , key2:value2", + ':', // Key-value delimiters + ',', // Key-value pair delims + &kv_pairs)); + ASSERT_EQ(2U, kv_pairs.size()); + EXPECT_EQ("key1", kv_pairs[0].first); + EXPECT_EQ("va:ue1", kv_pairs[0].second); + EXPECT_EQ("key2", kv_pairs[1].first); + EXPECT_EQ("value2", kv_pairs[1].second); +} + +TEST(SplitStringUsingSubstrTest, EmptyString) { + std::vector<std::string> results; + SplitStringUsingSubstr("", "DELIMITER", &results); + ASSERT_EQ(1u, results.size()); + EXPECT_THAT(results, ElementsAre("")); +} + +// Test for SplitString +TEST(StringUtilTest, SplitString) { + std::vector<std::wstring> r; + + SplitString(L"", L',', &r); + EXPECT_EQ(0U, r.size()); + r.clear(); + + SplitString(L"a,b,c", L',', &r); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], L"a"); + EXPECT_EQ(r[1], L"b"); + EXPECT_EQ(r[2], L"c"); + r.clear(); + + SplitString(L"a, b, c", L',', &r); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], L"a"); + EXPECT_EQ(r[1], L"b"); + EXPECT_EQ(r[2], L"c"); + r.clear(); + + SplitString(L"a,,c", L',', &r); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], L"a"); + EXPECT_EQ(r[1], L""); + EXPECT_EQ(r[2], L"c"); + r.clear(); + + SplitString(L" ", L'*', &r); + EXPECT_EQ(0U, r.size()); + r.clear(); + + SplitString(L"foo", L'*', &r); + ASSERT_EQ(1U, r.size()); + EXPECT_EQ(r[0], L"foo"); + r.clear(); + + SplitString(L"foo ,", L',', &r); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], L"foo"); + EXPECT_EQ(r[1], L""); + r.clear(); + + SplitString(L",", L',', &r); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], L""); + EXPECT_EQ(r[1], L""); + r.clear(); + + SplitString(L"\t\ta\t", L'\t', &r); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], L""); + EXPECT_EQ(r[1], L""); + EXPECT_EQ(r[2], L"a"); + EXPECT_EQ(r[3], L""); + r.clear(); + + SplitString(L"\ta\t\nb\tcc", L'\n', &r); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], L"a"); + EXPECT_EQ(r[1], L"b\tcc"); + r.clear(); +} + +TEST(SplitStringUsingSubstrTest, StringWithNoDelimiter) { + std::vector<std::string> results; + SplitStringUsingSubstr("alongwordwithnodelimiter", "DELIMITER", &results); + ASSERT_EQ(1u, results.size()); + EXPECT_THAT(results, ElementsAre("alongwordwithnodelimiter")); +} + +TEST(SplitStringUsingSubstrTest, LeadingDelimitersSkipped) { + std::vector<std::string> results; + SplitStringUsingSubstr( + "DELIMITERDELIMITERDELIMITERoneDELIMITERtwoDELIMITERthree", + "DELIMITER", + &results); + ASSERT_EQ(6u, results.size()); + EXPECT_THAT(results, ElementsAre("", "", "", "one", "two", "three")); +} + +TEST(SplitStringUsingSubstrTest, ConsecutiveDelimitersSkipped) { + std::vector<std::string> results; + SplitStringUsingSubstr( + "unoDELIMITERDELIMITERDELIMITERdosDELIMITERtresDELIMITERDELIMITERcuatro", + "DELIMITER", + &results); + ASSERT_EQ(7u, results.size()); + EXPECT_THAT(results, ElementsAre("uno", "", "", "dos", "tres", "", "cuatro")); +} + +TEST(SplitStringUsingSubstrTest, TrailingDelimitersSkipped) { + std::vector<std::string> results; + SplitStringUsingSubstr( + "unDELIMITERdeuxDELIMITERtroisDELIMITERquatreDELIMITERDELIMITERDELIMITER", + "DELIMITER", + &results); + ASSERT_EQ(7u, results.size()); + EXPECT_THAT( + results, ElementsAre("un", "deux", "trois", "quatre", "", "", "")); +} + +TEST(StringSplitTest, StringSplitDontTrim) { + std::vector<std::string> r; + + SplitStringDontTrim(" ", '*', &r); + ASSERT_EQ(1U, r.size()); + EXPECT_EQ(r[0], " "); + + SplitStringDontTrim("\t \ta\t ", '\t', &r); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], ""); + EXPECT_EQ(r[1], " "); + EXPECT_EQ(r[2], "a"); + EXPECT_EQ(r[3], " "); + + SplitStringDontTrim("\ta\t\nb\tcc", '\n', &r); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], "\ta\t"); + EXPECT_EQ(r[1], "b\tcc"); +} + +TEST(StringSplitTest, SplitStringAlongWhitespace) { + struct TestData { + const char* input; + const size_t expected_result_count; + const char* output1; + const char* output2; + } data[] = { + { "a", 1, "a", "" }, + { " ", 0, "", "" }, + { " a", 1, "a", "" }, + { " ab ", 1, "ab", "" }, + { " ab c", 2, "ab", "c" }, + { " ab c ", 2, "ab", "c" }, + { " ab cd", 2, "ab", "cd" }, + { " ab cd ", 2, "ab", "cd" }, + { " \ta\t", 1, "a", "" }, + { " b\ta\t", 2, "b", "a" }, + { " b\tat", 2, "b", "at" }, + { "b\tat", 2, "b", "at" }, + { "b\t at", 2, "b", "at" }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { + std::vector<std::string> results; + SplitStringAlongWhitespace(data[i].input, &results); + ASSERT_EQ(data[i].expected_result_count, results.size()); + if (data[i].expected_result_count > 0) + ASSERT_EQ(data[i].output1, results[0]); + if (data[i].expected_result_count > 1) + ASSERT_EQ(data[i].output2, results[1]); + } +} + +} // namespace base
diff --git a/src/base/string_tokenizer.h b/src/base/string_tokenizer.h new file mode 100644 index 0000000..c2307a5 --- /dev/null +++ b/src/base/string_tokenizer.h
@@ -0,0 +1,256 @@ +// 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. + +#ifndef BASE_STRING_TOKENIZER_H_ +#define BASE_STRING_TOKENIZER_H_ + +#include <algorithm> +#include <string> + +#include "base/string_piece.h" + +// StringTokenizerT is a simple string tokenizer class. It works like an +// iterator that with each step (see the Advance method) updates members that +// refer to the next token in the input string. The user may optionally +// configure the tokenizer to return delimiters. +// +// Warning: be careful not to pass a C string into the 2-arg constructor: +// StringTokenizer t("this is a test", " "); // WRONG +// This will create a temporary std::string, save the begin() and end() +// iterators, and then the string will be freed before we actually start +// tokenizing it. +// Instead, use a std::string or use the 3 arg constructor of CStringTokenizer. +// +// +// EXAMPLE 1: +// +// char input[] = "this is a test"; +// CStringTokenizer t(input, input + strlen(input), " "); +// while (t.GetNext()) { +// printf("%s\n", t.token().c_str()); +// } +// +// Output: +// +// this +// is +// a +// test +// +// +// EXAMPLE 2: +// +// std::string input = "no-cache=\"foo, bar\", private"; +// StringTokenizer t(input, ", "); +// t.set_quote_chars("\""); +// while (t.GetNext()) { +// printf("%s\n", t.token().c_str()); +// } +// +// Output: +// +// no-cache="foo, bar" +// private +// +// +// EXAMPLE 3: +// +// bool next_is_option = false, next_is_value = false; +// std::string input = "text/html; charset=UTF-8; foo=bar"; +// StringTokenizer t(input, "; ="); +// t.set_options(StringTokenizer::RETURN_DELIMS); +// while (t.GetNext()) { +// if (t.token_is_delim()) { +// switch (*t.token_begin()) { +// case ';': +// next_is_option = true; +// break; +// case '=': +// next_is_value = true; +// break; +// } +// } else { +// const char* label; +// if (next_is_option) { +// label = "option-name"; +// next_is_option = false; +// } else if (next_is_value) { +// label = "option-value"; +// next_is_value = false; +// } else { +// label = "mime-type"; +// } +// printf("%s: %s\n", label, t.token().c_str()); +// } +// } +// +// +template <class str, class const_iterator> +class StringTokenizerT { + public: + typedef typename str::value_type char_type; + + // Options that may be pass to set_options() + enum { + // Specifies the delimiters should be returned as tokens + RETURN_DELIMS = 1 << 0, + }; + + // The string object must live longer than the tokenizer. (In particular this + // should not be constructed with a temporary.) + StringTokenizerT(const str& string, + const str& delims) { + Init(string.begin(), string.end(), delims); + } + + StringTokenizerT(const_iterator string_begin, + const_iterator string_end, + const str& delims) { + Init(string_begin, string_end, delims); + } + + // Set the options for this tokenizer. By default, this is 0. + void set_options(int options) { options_ = options; } + + // Set the characters to regard as quotes. By default, this is empty. When + // a quote char is encountered, the tokenizer will switch into a mode where + // it ignores delimiters that it finds. It switches out of this mode once it + // finds another instance of the quote char. If a backslash is encountered + // within a quoted string, then the next character is skipped. + void set_quote_chars(const str& quotes) { quotes_ = quotes; } + + // Call this method to advance the tokenizer to the next delimiter. This + // returns false if the tokenizer is complete. This method must be called + // before calling any of the token* methods. + bool GetNext() { + if (quotes_.empty() && options_ == 0) + return QuickGetNext(); + else + return FullGetNext(); + } + + // Start iterating through tokens from the beginning of the string. + void Reset() { + token_end_ = start_pos_; + } + + // Returns true if token is a delimiter. When the tokenizer is constructed + // with the RETURN_DELIMS option, this method can be used to check if the + // returned token is actually a delimiter. + bool token_is_delim() const { return token_is_delim_; } + + // If GetNext() returned true, then these methods may be used to read the + // value of the token. + const_iterator token_begin() const { return token_begin_; } + const_iterator token_end() const { return token_end_; } + str token() const { return str(token_begin_, token_end_); } + base::StringPiece token_piece() const { + return base::StringPiece(&*token_begin_, + std::distance(token_begin_, token_end_)); + } + + private: + void Init(const_iterator string_begin, + const_iterator string_end, + const str& delims) { + start_pos_ = string_begin; + token_begin_ = string_begin; + token_end_ = string_begin; + end_ = string_end; + delims_ = delims; + options_ = 0; + token_is_delim_ = false; + } + + // Implementation of GetNext() for when we have no quote characters. We have + // two separate implementations because AdvanceOne() is a hot spot in large + // text files with large tokens. + bool QuickGetNext() { + token_is_delim_ = false; + for (;;) { + token_begin_ = token_end_; + if (token_end_ == end_) + return false; + ++token_end_; + if (delims_.find(*token_begin_) == str::npos) + break; + // else skip over delimiter. + } + while (token_end_ != end_ && delims_.find(*token_end_) == str::npos) + ++token_end_; + return true; + } + + // Implementation of GetNext() for when we have to take quotes into account. + bool FullGetNext() { + AdvanceState state; + token_is_delim_ = false; + for (;;) { + token_begin_ = token_end_; + if (token_end_ == end_) + return false; + ++token_end_; + if (AdvanceOne(&state, *token_begin_)) + break; + if (options_ & RETURN_DELIMS) { + token_is_delim_ = true; + return true; + } + // else skip over delimiter. + } + while (token_end_ != end_ && AdvanceOne(&state, *token_end_)) + ++token_end_; + return true; + } + + bool IsDelim(char_type c) const { + return delims_.find(c) != str::npos; + } + + bool IsQuote(char_type c) const { + return quotes_.find(c) != str::npos; + } + + struct AdvanceState { + bool in_quote; + bool in_escape; + char_type quote_char; + AdvanceState() : in_quote(false), in_escape(false), quote_char('\0') {} + }; + + // Returns true if a delimiter was not hit. + bool AdvanceOne(AdvanceState* state, char_type c) { + if (state->in_quote) { + if (state->in_escape) { + state->in_escape = false; + } else if (c == '\\') { + state->in_escape = true; + } else if (c == state->quote_char) { + state->in_quote = false; + } + } else { + if (IsDelim(c)) + return false; + state->in_quote = IsQuote(state->quote_char = c); + } + return true; + } + + const_iterator start_pos_; + const_iterator token_begin_; + const_iterator token_end_; + const_iterator end_; + str delims_; + str quotes_; + int options_; + bool token_is_delim_; +}; + +typedef StringTokenizerT<std::string, std::string::const_iterator> + StringTokenizer; +typedef StringTokenizerT<std::wstring, std::wstring::const_iterator> + WStringTokenizer; +typedef StringTokenizerT<std::string, const char*> CStringTokenizer; + +#endif // BASE_STRING_TOKENIZER_H_
diff --git a/src/base/string_tokenizer_unittest.cc b/src/base/string_tokenizer_unittest.cc new file mode 100644 index 0000000..61841f2 --- /dev/null +++ b/src/base/string_tokenizer_unittest.cc
@@ -0,0 +1,230 @@ +// Copyright (c) 2006-2008 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/string_tokenizer.h" + +#include "testing/gtest/include/gtest/gtest.h" + +using std::string; + +namespace { + +TEST(StringTokenizerTest, Simple) { + string input = "this is a test"; + StringTokenizer t(input, " "); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("this"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("is"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("a"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("test"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, Reset) { + string input = "this is a test"; + StringTokenizer t(input, " "); + + for (int i = 0; i < 2; ++i) { + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("this"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("is"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("a"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("test"), t.token()); + + EXPECT_FALSE(t.GetNext()); + t.Reset(); + } +} + +TEST(StringTokenizerTest, RetDelims) { + string input = "this is a test"; + StringTokenizer t(input, " "); + t.set_options(StringTokenizer::RETURN_DELIMS); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("this"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("is"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("a"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("test"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ManyDelims) { + string input = "this: is, a-test"; + StringTokenizer t(input, ": ,-"); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("this"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("is"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("a"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("test"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ParseHeader) { + string input = "Content-Type: text/html ; charset=UTF-8"; + StringTokenizer t(input, ": ;="); + t.set_options(StringTokenizer::RETURN_DELIMS); + + EXPECT_TRUE(t.GetNext()); + EXPECT_FALSE(t.token_is_delim()); + EXPECT_EQ(string("Content-Type"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string(":"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_FALSE(t.token_is_delim()); + EXPECT_EQ(string("text/html"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string(";"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string(" "), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_FALSE(t.token_is_delim()); + EXPECT_EQ(string("charset"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_TRUE(t.token_is_delim()); + EXPECT_EQ(string("="), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_FALSE(t.token_is_delim()); + EXPECT_EQ(string("UTF-8"), t.token()); + + EXPECT_FALSE(t.GetNext()); + EXPECT_FALSE(t.token_is_delim()); +} + +TEST(StringTokenizerTest, ParseQuotedString) { + string input = "foo bar 'hello world' baz"; + StringTokenizer t(input, " "); + t.set_quote_chars("'"); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("foo"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("bar"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("'hello world'"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("baz"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ParseQuotedString_Malformed) { + string input = "bar 'hello wo"; + StringTokenizer t(input, " "); + t.set_quote_chars("'"); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("bar"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("'hello wo"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ParseQuotedString_Multiple) { + string input = "bar 'hel\"lo\" wo' baz\""; + StringTokenizer t(input, " "); + t.set_quote_chars("'\""); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("bar"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("'hel\"lo\" wo'"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("baz\""), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ParseQuotedString_EscapedQuotes) { + string input = "foo 'don\\'t do that'"; + StringTokenizer t(input, " "); + t.set_quote_chars("'"); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("foo"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("'don\\'t do that'"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +TEST(StringTokenizerTest, ParseQuotedString_EscapedQuotes2) { + string input = "foo='a, b', bar"; + StringTokenizer t(input, ", "); + t.set_quote_chars("'"); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("foo='a, b'"), t.token()); + + EXPECT_TRUE(t.GetNext()); + EXPECT_EQ(string("bar"), t.token()); + + EXPECT_FALSE(t.GetNext()); +} + +} // namespace
diff --git a/src/base/string_util.cc b/src/base/string_util.cc new file mode 100644 index 0000000..1acf1be --- /dev/null +++ b/src/base/string_util.cc
@@ -0,0 +1,1060 @@ +// 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/string_util.h" + +#include "build/build_config.h" + +#include <ctype.h> +#include <errno.h> +#include <math.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <wchar.h> +#include <wctype.h> + +#include <algorithm> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "base/utf_string_conversion_utils.h" +#include "base/utf_string_conversions.h" +#include "base/third_party/icu/icu_utf.h" + +namespace { + +// Force the singleton used by Empty[W]String[16] to be a unique type. This +// prevents other code that might accidentally use Singleton<string> from +// getting our internal one. +struct EmptyStrings { + EmptyStrings() {} + const std::string s; + const std::wstring ws; + const string16 s16; + + static EmptyStrings* GetInstance() { + return Singleton<EmptyStrings>::get(); + } +}; + +// Used by ReplaceStringPlaceholders to track the position in the string of +// replaced parameters. +struct ReplacementOffset { + ReplacementOffset(uintptr_t parameter, size_t offset) + : parameter(parameter), + offset(offset) {} + + // Index of the parameter. + uintptr_t parameter; + + // Starting position in the string. + size_t offset; +}; + +static bool CompareParameter(const ReplacementOffset& elem1, + const ReplacementOffset& elem2) { + return elem1.parameter < elem2.parameter; +} + +} // namespace + +namespace base { + +bool IsWprintfFormatPortable(const wchar_t* format) { + for (const wchar_t* position = format; *position != '\0'; ++position) { + if (*position == '%') { + bool in_specification = true; + bool modifier_l = false; + while (in_specification) { + // Eat up characters until reaching a known specifier. + if (*++position == '\0') { + // The format string ended in the middle of a specification. Call + // it portable because no unportable specifications were found. The + // string is equally broken on all platforms. + return true; + } + + if (*position == 'l') { + // 'l' is the only thing that can save the 's' and 'c' specifiers. + modifier_l = true; + } else if (((*position == 's' || *position == 'c') && !modifier_l) || + *position == 'S' || *position == 'C' || *position == 'F' || + *position == 'D' || *position == 'O' || *position == 'U') { + // Not portable. + return false; + } + + if (wcschr(L"diouxXeEfgGaAcspn%", *position)) { + // Portable, keep scanning the rest of the format string. + in_specification = false; + } + } + } + } + + return true; +} + +} // namespace base + + +const std::string& EmptyString() { + return EmptyStrings::GetInstance()->s; +} + +const std::wstring& EmptyWString() { + return EmptyStrings::GetInstance()->ws; +} + +const string16& EmptyString16() { + return EmptyStrings::GetInstance()->s16; +} + +#define WHITESPACE_UNICODE \ + 0x0009, /* <control-0009> to <control-000D> */ \ + 0x000A, \ + 0x000B, \ + 0x000C, \ + 0x000D, \ + 0x0020, /* Space */ \ + 0x0085, /* <control-0085> */ \ + 0x00A0, /* No-Break Space */ \ + 0x1680, /* Ogham Space Mark */ \ + 0x180E, /* Mongolian Vowel Separator */ \ + 0x2000, /* En Quad to Hair Space */ \ + 0x2001, \ + 0x2002, \ + 0x2003, \ + 0x2004, \ + 0x2005, \ + 0x2006, \ + 0x2007, \ + 0x2008, \ + 0x2009, \ + 0x200A, \ + 0x200C, /* Zero Width Non-Joiner */ \ + 0x2028, /* Line Separator */ \ + 0x2029, /* Paragraph Separator */ \ + 0x202F, /* Narrow No-Break Space */ \ + 0x205F, /* Medium Mathematical Space */ \ + 0x3000, /* Ideographic Space */ \ + 0 + +const wchar_t kWhitespaceWide[] = { + WHITESPACE_UNICODE +}; +const char16 kWhitespaceUTF16[] = { + WHITESPACE_UNICODE +}; +const char kWhitespaceASCII[] = { + 0x09, // <control-0009> to <control-000D> + 0x0A, + 0x0B, + 0x0C, + 0x0D, + 0x20, // Space + 0 +}; + +const char kUtf8ByteOrderMark[] = "\xEF\xBB\xBF"; + +template<typename STR> +bool ReplaceCharsT(const STR& input, + const typename STR::value_type replace_chars[], + const STR& replace_with, + STR* output) { + bool removed = false; + size_t replace_length = replace_with.length(); + + *output = input; + + size_t found = output->find_first_of(replace_chars); + while (found != STR::npos) { + removed = true; + output->replace(found, 1, replace_with); + found = output->find_first_of(replace_chars, found + replace_length); + } + + return removed; +} + +bool ReplaceChars(const string16& input, + const char16 replace_chars[], + const string16& replace_with, + string16* output) { + return ReplaceCharsT(input, replace_chars, replace_with, output); +} + +bool ReplaceChars(const std::string& input, + const char replace_chars[], + const std::string& replace_with, + std::string* output) { + return ReplaceCharsT(input, replace_chars, replace_with, output); +} + +bool RemoveChars(const string16& input, + const char16 remove_chars[], + string16* output) { + return ReplaceChars(input, remove_chars, string16(), output); +} + +bool RemoveChars(const std::string& input, + const char remove_chars[], + std::string* output) { + return ReplaceChars(input, remove_chars, std::string(), output); +} + +template<typename STR> +TrimPositions TrimStringT(const STR& input, + const typename STR::value_type trim_chars[], + TrimPositions positions, + STR* output) { + // Find the edges of leading/trailing whitespace as desired. + const typename STR::size_type last_char = input.length() - 1; + const typename STR::size_type first_good_char = (positions & TRIM_LEADING) ? + input.find_first_not_of(trim_chars) : 0; + const typename STR::size_type last_good_char = (positions & TRIM_TRAILING) ? + input.find_last_not_of(trim_chars) : last_char; + + // When the string was all whitespace, report that we stripped off whitespace + // from whichever position the caller was interested in. For empty input, we + // stripped no whitespace, but we still need to clear |output|. + if (input.empty() || + (first_good_char == STR::npos) || (last_good_char == STR::npos)) { + bool input_was_empty = input.empty(); // in case output == &input + output->clear(); + return input_was_empty ? TRIM_NONE : positions; + } + + // Trim the whitespace. + *output = + input.substr(first_good_char, last_good_char - first_good_char + 1); + + // Return where we trimmed from. + return static_cast<TrimPositions>( + ((first_good_char == 0) ? TRIM_NONE : TRIM_LEADING) | + ((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[], + std::string* output) { + return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE; +} + +void TruncateUTF8ToByteSize(const std::string& input, + const size_t byte_size, + std::string* output) { + DCHECK(output); + if (byte_size > input.length()) { + *output = input; + return; + } + DCHECK_LE(byte_size, static_cast<uint32>(kint32max)); + // Note: This cast is necessary because CBU8_NEXT uses int32s. + int32 truncation_length = static_cast<int32>(byte_size); + int32 char_index = truncation_length - 1; + const char* data = input.data(); + + // Using CBU8, we will move backwards from the truncation point + // to the beginning of the string looking for a valid UTF8 + // character. Once a full UTF8 character is found, we will + // truncate the string to the end of that character. + while (char_index >= 0) { + 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)) { + char_index = prev - 1; + } else { + break; + } + } + + if (char_index >= 0 ) + *output = input.substr(0, char_index); + else + output->clear(); +} + +TrimPositions TrimWhitespace(const string16& input, + TrimPositions positions, + string16* output) { + return TrimStringT(input, kWhitespaceUTF16, positions, output); +} + +TrimPositions TrimWhitespaceASCII(const std::string& input, + TrimPositions positions, + std::string* output) { + return TrimStringT(input, kWhitespaceASCII, positions, output); +} + +// This function is only for backward-compatibility. +// To be removed when all callers are updated. +TrimPositions TrimWhitespace(const std::string& input, + TrimPositions positions, + std::string* output) { + return TrimWhitespaceASCII(input, positions, output); +} + +template<typename STR> +STR CollapseWhitespaceT(const STR& text, + bool trim_sequences_with_line_breaks) { + STR result; + result.resize(text.size()); + + // Set flags to pretend we're already in a trimmed whitespace sequence, so we + // will trim any leading whitespace. + bool in_whitespace = true; + bool already_trimmed = true; + + int chars_written = 0; + for (typename STR::const_iterator i(text.begin()); i != text.end(); ++i) { + if (IsWhitespace(*i)) { + if (!in_whitespace) { + // Reduce all whitespace sequences to a single space. + in_whitespace = true; + result[chars_written++] = L' '; + } + if (trim_sequences_with_line_breaks && !already_trimmed && + ((*i == '\n') || (*i == '\r'))) { + // Whitespace sequences containing CR or LF are eliminated entirely. + already_trimmed = true; + --chars_written; + } + } else { + // Non-whitespace chracters are copied straight across. + in_whitespace = false; + already_trimmed = false; + result[chars_written++] = *i; + } + } + + if (in_whitespace && !already_trimmed) { + // Any trailing whitespace is eliminated. + --chars_written; + } + + result.resize(chars_written); + 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) { + return CollapseWhitespaceT(text, trim_sequences_with_line_breaks); +} + +bool ContainsOnlyWhitespaceASCII(const std::string& str) { + for (std::string::const_iterator i(str.begin()); i != str.end(); ++i) { + if (!IsAsciiWhitespace(*i)) + return false; + } + return true; +} + +bool ContainsOnlyWhitespace(const string16& str) { + return str.find_first_not_of(kWhitespaceUTF16) == string16::npos; +} + +template<typename STR> +static bool ContainsOnlyCharsT(const STR& input, const STR& characters) { + for (typename STR::const_iterator iter = input.begin(); + iter != input.end(); ++iter) { + if (characters.find(*iter) == STR::npos) + return false; + } + 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); +} + +std::string WideToASCII(const std::wstring& wide) { + DCHECK(IsStringASCII(wide)) << wide; + return std::string(wide.begin(), wide.end()); +} + +std::string UTF16ToASCII(const string16& utf16) { + DCHECK(IsStringASCII(utf16)) << 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++) { + typename ToUnsigned<typename STR::value_type>::Unsigned c = str[i]; + if (c > 0x7F) + return false; + } + return true; +} + +bool IsStringASCII(const std::wstring& str) { + return DoIsStringASCII(str); +} + +#if !defined(WCHAR_T_IS_UTF16) +bool IsStringASCII(const string16& str) { + return DoIsStringASCII(str); +} +#endif + +bool IsStringASCII(const base::StringPiece& str) { + return DoIsStringASCII(str); +} + +bool IsStringUTF8(const std::string& str) { + const char *src = str.data(); + int32 src_len = static_cast<int32>(str.length()); + int32 char_index = 0; + + while (char_index < src_len) { + int32 code_point; + CBU8_NEXT(src, char_index, src_len, code_point); + if (!base::IsValidCharacter(code_point)) + return false; + } + return true; +} + +template<typename Iter> +static inline bool DoLowerCaseEqualsASCII(Iter a_begin, + Iter a_end, + const char* b) { + for (Iter it = a_begin; it != a_end; ++it, ++b) { + if (!*b || base::ToLowerASCII(*it) != *b) + return false; + } + return *b == 0; +} + +// Front-ends for LowerCaseEqualsASCII. +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, + const char* b) { + 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) && !defined(__LB_ANDROID__) +bool LowerCaseEqualsASCII(const char* a_begin, + const char* a_end, + const char* b) { + 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) && !defined(__LB_ANDROID__) + +bool EqualsASCII(const string16& a, const base::StringPiece& b) { + if (a.length() != b.length()) + return false; + return std::equal(b.begin(), b.end(), a.begin()); +} + +bool StartsWithASCII(const std::string& str, + const std::string& search, + bool case_sensitive) { + if (case_sensitive) + return str.compare(0, search.length(), search) == 0; + else + return base::strncasecmp(str.c_str(), search.c_str(), search.length()) == 0; +} + +template <typename STR> +bool StartsWithT(const STR& str, const STR& search, bool case_sensitive) { + if (case_sensitive) { + return str.compare(0, search.length(), search) == 0; + } else { + if (search.size() > str.size()) + return false; + return std::equal(search.begin(), search.end(), str.begin(), + base::CaseInsensitiveCompare<typename STR::value_type>()); + } +} + +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) { + typename STR::size_type str_length = str.length(); + typename STR::size_type search_length = search.length(); + if (search_length > str_length) + return false; + if (case_sensitive) { + return str.compare(str_length - search_length, search_length, search) == 0; + } else { + return std::equal(search.begin(), search.end(), + str.begin() + (str_length - search_length), + base::CaseInsensitiveCompare<typename STR::value_type>()); + } +} + +bool EndsWith(const std::string& str, const std::string& search, + bool case_sensitive) { + 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", + " kB", + " MB", + " GB", + " TB", + " PB" +}; + +string16 FormatBytesUnlocalized(int64 bytes) { + double unit_amount = static_cast<double>(bytes); + size_t dimension = 0; + const int kKilo = 1024; + while (unit_amount >= kKilo && + dimension < arraysize(kByteStringsUnlocalized) - 1) { + unit_amount /= kKilo; + dimension++; + } + + char buf[64]; + if (bytes != 0 && dimension > 0 && unit_amount < 100) { + base::snprintf(buf, arraysize(buf), "%.1lf%s", unit_amount, + kByteStringsUnlocalized[dimension]); + } else { + base::snprintf(buf, arraysize(buf), "%.0lf%s", unit_amount, + kByteStringsUnlocalized[dimension]); + } + + return ASCIIToUTF16(buf); +} + +template<class StringType> +void DoReplaceSubstringsAfterOffset(StringType* str, + typename StringType::size_type start_offset, + const StringType& find_this, + const StringType& replace_with, + bool replace_all) { + if ((start_offset == StringType::npos) || (start_offset >= str->length())) + return; + + DCHECK(!find_this.empty()); + for (typename StringType::size_type offs(str->find(find_this, start_offset)); + offs != StringType::npos; offs = str->find(find_this, offs)) { + str->replace(offs, find_this.length(), replace_with); + offs += replace_with.length(); + + if (!replace_all) + break; + } +} + +void ReplaceFirstSubstringAfterOffset(string16* str, + string16::size_type start_offset, + const string16& find_this, + const string16& replace_with) { + DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, + false); // replace first instance +} + +void ReplaceFirstSubstringAfterOffset(std::string* str, + std::string::size_type start_offset, + const std::string& find_this, + const std::string& replace_with) { + DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, + false); // replace first instance +} + +void ReplaceSubstringsAfterOffset(string16* str, + string16::size_type start_offset, + const string16& find_this, + const string16& replace_with) { + DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, + true); // replace all instances +} + +void ReplaceSubstringsAfterOffset(std::string* str, + std::string::size_type start_offset, + const std::string& find_this, + const std::string& replace_with) { + DoReplaceSubstringsAfterOffset(str, start_offset, find_this, replace_with, + true); // replace all instances +} + + +template<typename STR> +static size_t TokenizeT(const STR& str, + const STR& delimiters, + std::vector<STR>* tokens) { + tokens->clear(); + + typename STR::size_type start = str.find_first_not_of(delimiters); + while (start != STR::npos) { + typename STR::size_type end = str.find_first_of(delimiters, start + 1); + if (end == STR::npos) { + tokens->push_back(str.substr(start)); + break; + } else { + tokens->push_back(str.substr(start, end - start)); + start = str.find_first_not_of(delimiters, end + 1); + } + } + + 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, + std::vector<std::string>* tokens) { + return TokenizeT(str, delimiters, tokens); +} + +size_t Tokenize(const base::StringPiece& str, + const base::StringPiece& delimiters, + std::vector<base::StringPiece>* tokens) { + return TokenizeT(str, delimiters, tokens); +} + +template<typename STR> +static STR JoinStringT(const std::vector<STR>& parts, const STR& sep) { + if (parts.empty()) + return STR(); + + STR result(parts[0]); + typename std::vector<STR>::const_iterator iter = parts.begin(); + ++iter; + + for (; iter != parts.end(); ++iter) { + result += sep; + result += *iter; + } + + return result; +} + +std::string JoinString(const std::vector<std::string>& parts, char sep) { + return JoinStringT(parts, std::string(1, sep)); +} + +string16 JoinString(const std::vector<string16>& parts, char16 sep) { + return JoinStringT(parts, string16(1, sep)); +} + +std::string JoinString(const std::vector<std::string>& parts, + const std::string& separator) { + return JoinStringT(parts, separator); +} + +string16 JoinString(const std::vector<string16>& parts, + const string16& separator) { + return JoinStringT(parts, separator); +} + +template<class FormatStringType, class OutStringType> +OutStringType DoReplaceStringPlaceholders(const FormatStringType& format_string, + const std::vector<OutStringType>& subst, std::vector<size_t>* offsets) { + size_t substitutions = subst.size(); + + size_t sub_length = 0; + for (typename std::vector<OutStringType>::const_iterator iter = subst.begin(); + iter != subst.end(); ++iter) { + sub_length += iter->length(); + } + + OutStringType formatted; + formatted.reserve(format_string.length() + sub_length); + + std::vector<ReplacementOffset> r_offsets; + for (typename FormatStringType::const_iterator i = format_string.begin(); + i != format_string.end(); ++i) { + if ('$' == *i) { + if (i + 1 != format_string.end()) { + ++i; + DCHECK('$' == *i || '1' <= *i) << "Invalid placeholder: " << *i; + if ('$' == *i) { + while (i != format_string.end() && '$' == *i) { + formatted.push_back('$'); + ++i; + } + --i; + } else { + uintptr_t index = 0; + while (i != format_string.end() && '0' <= *i && *i <= '9') { + index *= 10; + index += *i - '0'; + ++i; + } + --i; + index -= 1; + if (offsets) { + ReplacementOffset r_offset(index, + static_cast<int>(formatted.size())); + r_offsets.insert(std::lower_bound(r_offsets.begin(), + r_offsets.end(), + r_offset, + &CompareParameter), + r_offset); + } + if (index < substitutions) + formatted.append(subst.at(index)); + } + } + } else { + formatted.push_back(*i); + } + } + if (offsets) { + for (std::vector<ReplacementOffset>::const_iterator i = r_offsets.begin(); + i != r_offsets.end(); ++i) { + offsets->push_back(i->offset); + } + } + return formatted; +} + +string16 ReplaceStringPlaceholders(const string16& format_string, + const std::vector<string16>& subst, + std::vector<size_t>* offsets) { + return DoReplaceStringPlaceholders(format_string, subst, offsets); +} + +std::string ReplaceStringPlaceholders(const base::StringPiece& format_string, + const std::vector<std::string>& subst, + std::vector<size_t>* offsets) { + return DoReplaceStringPlaceholders(format_string, subst, offsets); +} + +string16 ReplaceStringPlaceholders(const string16& format_string, + const string16& a, + size_t* offset) { + std::vector<size_t> offsets; + std::vector<string16> subst; + subst.push_back(a); + string16 result = ReplaceStringPlaceholders(format_string, subst, &offsets); + + DCHECK(offsets.size() == 1); + if (offset) { + *offset = offsets[0]; + } + return result; +} + +static bool IsWildcard(base_icu::UChar32 character) { + return character == '*' || character == '?'; +} + +// Move the strings pointers to the point where they start to differ. +template <typename CHAR, typename NEXT> +static void EatSameChars(const CHAR** pattern, const CHAR* pattern_end, + const CHAR** string, const CHAR* string_end, + NEXT next) { + const CHAR* escape = NULL; + while (*pattern != pattern_end && *string != string_end) { + if (!escape && IsWildcard(**pattern)) { + // We don't want to match wildcard here, except if it's escaped. + return; + } + + // Check if the escapement char is found. If so, skip it and move to the + // next character. + if (!escape && **pattern == '\\') { + escape = *pattern; + next(pattern, pattern_end); + continue; + } + + // Check if the chars match, if so, increment the ptrs. + const CHAR* pattern_next = *pattern; + const CHAR* string_next = *string; + base_icu::UChar32 pattern_char = next(&pattern_next, pattern_end); + if (pattern_char == next(&string_next, string_end) && + pattern_char != (base_icu::UChar32) CBU_SENTINEL) { + *pattern = pattern_next; + *string = string_next; + } else { + // Uh ho, it did not match, we are done. If the last char was an + // escapement, that means that it was an error to advance the ptr here, + // let's put it back where it was. This also mean that the MatchPattern + // function will return false because if we can't match an escape char + // here, then no one will. + if (escape) { + *pattern = escape; + } + return; + } + + escape = NULL; + } +} + +template <typename CHAR, typename NEXT> +static void EatWildcard(const CHAR** pattern, const CHAR* end, NEXT next) { + while (*pattern != end) { + if (!IsWildcard(**pattern)) + return; + next(pattern, end); + } +} + +template <typename CHAR, typename NEXT> +static bool MatchPatternT(const CHAR* eval, const CHAR* eval_end, + const CHAR* pattern, const CHAR* pattern_end, + int depth, + NEXT next) { + const int kMaxDepth = 16; + if (depth > kMaxDepth) + return false; + + // Eat all the matching chars. + EatSameChars(&pattern, pattern_end, &eval, eval_end, next); + + // If the string is empty, then the pattern must be empty too, or contains + // only wildcards. + if (eval == eval_end) { + EatWildcard(&pattern, pattern_end, next); + return pattern == pattern_end; + } + + // Pattern is empty but not string, this is not a match. + if (pattern == pattern_end) + return false; + + // If this is a question mark, then we need to compare the rest with + // the current string or the string with one character eaten. + const CHAR* next_pattern = pattern; + next(&next_pattern, pattern_end); + if (pattern[0] == '?') { + if (MatchPatternT(eval, eval_end, next_pattern, pattern_end, + depth + 1, next)) + return true; + const CHAR* next_eval = eval; + next(&next_eval, eval_end); + if (MatchPatternT(next_eval, eval_end, next_pattern, pattern_end, + depth + 1, next)) + return true; + } + + // This is a *, try to match all the possible substrings with the remainder + // of the pattern. + if (pattern[0] == '*') { + // Collapse duplicate wild cards (********** into *) so that the + // method does not recurse unnecessarily. http://crbug.com/52839 + EatWildcard(&next_pattern, pattern_end, next); + + while (eval != eval_end) { + if (MatchPatternT(eval, eval_end, next_pattern, pattern_end, + depth + 1, next)) + return true; + eval++; + } + + // We reached the end of the string, let see if the pattern contains only + // wildcards. + if (eval == eval_end) { + EatWildcard(&pattern, pattern_end, next); + if (pattern != pattern_end) + return false; + return true; + } + } + + return false; +} + +struct NextCharUTF8 { + base_icu::UChar32 operator()(const char** p, const char* end) { + base_icu::UChar32 c; + int offset = 0; + CBU8_NEXT(*p, offset, end - *p, c); + *p += offset; + return c; + } +}; + +struct NextCharUTF16 { + base_icu::UChar32 operator()(const char16** p, const char16* end) { + base_icu::UChar32 c; + int offset = 0; + CBU16_NEXT(*p, offset, end - *p, c); + *p += offset; + return c; + } +}; + +bool MatchPattern(const base::StringPiece& eval, + const base::StringPiece& pattern) { + return MatchPatternT(eval.data(), eval.data() + eval.size(), + pattern.data(), pattern.data() + pattern.size(), + 0, NextCharUTF8()); +} + +bool MatchPattern(const string16& eval, const string16& pattern) { + return MatchPatternT(eval.c_str(), eval.c_str() + eval.size(), + pattern.c_str(), pattern.c_str() + pattern.size(), + 0, NextCharUTF16()); +} + +// The following code is compatible with the OpenBSD lcpy interface. See: +// http://www.gratisoft.us/todd/papers/strlcpy.html +// ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/{wcs,str}lcpy.c + +namespace { + +template <typename CHAR> +size_t lcpyT(CHAR* dst, const CHAR* src, size_t dst_size) { + for (size_t i = 0; i < dst_size; ++i) { + if ((dst[i] = src[i]) == 0) // We hit and copied the terminating NULL. + return i; + } + + // We were left off at dst_size. We over copied 1 byte. Null terminate. + if (dst_size != 0) + dst[dst_size - 1] = 0; + + // Count the rest of the |src|, and return it's length in characters. + while (src[dst_size]) ++dst_size; + return dst_size; +} + +} // namespace + +size_t base::strlcpy(char* dst, const char* src, size_t dst_size) { + return lcpyT<char>(dst, src, dst_size); +} +size_t base::wcslcpy(wchar_t* dst, const wchar_t* src, size_t dst_size) { + return lcpyT<wchar_t>(dst, src, dst_size); +}
diff --git a/src/base/string_util.h b/src/base/string_util.h new file mode 100644 index 0000000..f1160fe --- /dev/null +++ b/src/base/string_util.h
@@ -0,0 +1,578 @@ +// 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 working with strings. + +#ifndef BASE_STRING_UTIL_H_ +#define BASE_STRING_UTIL_H_ + +#include <ctype.h> +#include <stdarg.h> // va_list + +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/string16.h" +#include "base/string_piece.h" // For implicit conversions. + +// Safe standard library wrappers for all platforms. + +namespace base { + +// C standard-library functions like "strncasecmp" and "snprintf" that aren't +// cross-platform are provided as "base::strncasecmp", and their prototypes +// are listed below. These functions are then implemented as inline calls +// to the platform-specific equivalents in the platform-specific headers. + +// Compares the two strings s1 and s2 without regard to case using +// the current locale; returns 0 if they are equal, 1 if s1 > s2, and -1 if +// s2 > s1 according to a lexicographic comparison. +int strcasecmp(const char* s1, const char* s2); + +// Compares up to count characters of s1 and s2 without regard to case using +// the current locale; returns 0 if they are equal, 1 if s1 > s2, and -1 if +// s2 > s1 according to a lexicographic comparison. +int strncasecmp(const char* s1, const char* s2, size_t count); + +// Same as strncmp but for char16 strings. +int strncmp16(const char16* s1, const char16* s2, size_t count); + +// Wrapper for vsnprintf that always null-terminates and always returns the +// number of characters that would be in an untruncated formatted +// string, even when truncation occurs. +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 +// function just so the PRINTF_FORMAT works. +inline int snprintf(char* buffer, size_t size, const char* format, ...) + PRINTF_FORMAT(3, 4); +inline int snprintf(char* buffer, size_t size, const char* format, ...) { + va_list arguments; + va_start(arguments, format); + int result = base::vsnprintf(buffer, size, format, arguments); + va_end(arguments); + 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 = base::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 +// long as |dst_size| is not 0. Returns the length of |src| in characters. +// If the return value is >= dst_size, then the output was truncated. +// NOTE: All sizes are in number of characters, NOT in bytes. +BASE_EXPORT size_t strlcpy(char* dst, const char* src, size_t dst_size); +BASE_EXPORT size_t wcslcpy(wchar_t* dst, const wchar_t* src, size_t dst_size); + +// Scan a wprintf format string to determine whether it's portable across a +// variety of systems. This function only checks that the conversion +// specifiers used by the format string are supported and have the same meaning +// on a variety of systems. It doesn't check for other errors that might occur +// within a format string. +// +// Nonportable conversion specifiers for wprintf are: +// - 's' and 'c' without an 'l' length modifier. %s and %c operate on char +// data on all systems except Windows, which treat them as wchar_t data. +// Use %ls and %lc for wchar_t data instead. +// - 'S' and 'C', which operate on wchar_t data on all systems except Windows, +// which treat them as char data. Use %ls and %lc for wchar_t data +// instead. +// - 'F', which is not identified by Windows wprintf documentation. +// - 'D', 'O', and 'U', which are deprecated and not available on all systems. +// Use %ld, %lo, and %lu instead. +// +// Note that there is no portable conversion specifier for char data when +// working with wprintf. +// +// This function is intended to be called from base::vswprintf. +BASE_EXPORT bool IsWprintfFormatPortable(const wchar_t* format); + +// ASCII-specific tolower. The standard library's tolower is locale sensitive, +// so we don't want to use it here. +template <class Char> inline Char ToLowerASCII(Char c) { + return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c; +} + +// ASCII-specific toupper. The standard library's toupper is locale sensitive, +// so we don't want to use it here. +template <class Char> inline Char ToUpperASCII(Char c) { + return (c >= 'a' && c <= 'z') ? (c + ('A' - 'a')) : c; +} + +// Function objects to aid in comparing/searching strings. + +template<typename Char> struct CaseInsensitiveCompare { + public: + bool operator()(Char x, Char y) const { + // TODO(darin): Do we really want to do locale sensitive comparisons here? + // See http://crbug.com/24917 + return tolower(x) == tolower(y); + } +}; + +template<typename Char> struct CaseInsensitiveCompareASCII { + public: + bool operator()(Char x, Char y) const { + return ToLowerASCII(x) == ToLowerASCII(y); + } +}; + +} // namespace base + +#if defined(OS_WIN) +#include "base/string_util_win.h" +#elif defined(OS_POSIX) +#include "base/string_util_posix.h" +#elif defined(OS_STARBOARD) +#include "base/string_util_starboard.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. +BASE_EXPORT const std::string& EmptyString(); +BASE_EXPORT const std::wstring& EmptyWString(); +BASE_EXPORT const string16& EmptyString16(); + +BASE_EXPORT extern const wchar_t kWhitespaceWide[]; +BASE_EXPORT extern const char16 kWhitespaceUTF16[]; +BASE_EXPORT extern const char kWhitespaceASCII[]; + +BASE_EXPORT extern const char kUtf8ByteOrderMark[]; + +// Removes characters in |remove_chars| from anywhere in |input|. Returns true +// if any characters were removed. |remove_chars| must be null-terminated. +// NOTE: Safe to use the same variable for both |input| and |output|. +BASE_EXPORT bool RemoveChars(const string16& input, + const char16 remove_chars[], + string16* output); +BASE_EXPORT bool RemoveChars(const std::string& input, + const char remove_chars[], + std::string* output); + +// Replaces characters in |replace_chars| from anywhere in |input| with +// |replace_with|. Each character in |replace_chars| will be replaced with +// the |replace_with| string. Returns true if any characters were replaced. +// |replace_chars| must be null-terminated. +// NOTE: Safe to use the same variable for both |input| and |output|. +BASE_EXPORT bool ReplaceChars(const string16& input, + const char16 replace_chars[], + const string16& replace_with, + string16* output); +BASE_EXPORT bool ReplaceChars(const std::string& input, + const char replace_chars[], + const std::string& replace_with, + std::string* output); + +// 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); +BASE_EXPORT bool TrimString(const std::string& input, + const char trim_chars[], + std::string* output); + +// Truncates a string to the nearest UTF-8 character that will leave +// the string less than or equal to the specified byte size. +BASE_EXPORT void TruncateUTF8ToByteSize(const std::string& input, + const size_t byte_size, + std::string* output); + +// Trims any whitespace from either end of the input string. Returns where +// whitespace was found. +// The non-wide version has two functions: +// * TrimWhitespaceASCII() +// This function is for ASCII strings and only looks for ASCII whitespace; +// Please choose the best one according to your usage. +// NOTE: Safe to use the same variable for both input and output. +enum TrimPositions { + TRIM_NONE = 0, + TRIM_LEADING = 1 << 0, + TRIM_TRAILING = 1 << 1, + TRIM_ALL = TRIM_LEADING | TRIM_TRAILING, +}; +BASE_EXPORT TrimPositions TrimWhitespace(const string16& input, + TrimPositions positions, + string16* output); +BASE_EXPORT TrimPositions TrimWhitespaceASCII(const std::string& input, + TrimPositions positions, + std::string* output); + +// Deprecated. This function is only for backward compatibility and calls +// TrimWhitespaceASCII(). +BASE_EXPORT TrimPositions TrimWhitespace(const std::string& input, + TrimPositions positions, + std::string* output); + +// Searches for CR or LF characters. Removes all contiguous whitespace +// strings that contain them. This is useful when trying to deal with text +// copied from terminals. +// Returns |text|, with the following three transformations: +// (1) Leading and trailing whitespace is trimmed. +// (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, + bool trim_sequences_with_line_breaks); +BASE_EXPORT std::string CollapseWhitespaceASCII( + const std::string& text, + bool trim_sequences_with_line_breaks); + +// 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); + +// 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 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); + +// 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 +// first case) or characters that use only 8-bits and whose 8-bit +// representation looks like a UTF-8 string (the second case). +// +// Note that IsStringUTF8 checks not only if the input is structurally +// valid but also if it doesn't contain any non-character codepoint +// (e.g. U+FFFE). It's done on purpose because all the existing callers want +// to have the maximum 'discriminating' power from other encodings. If +// 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); + +// Converts the elements of the given string. This version uses a pointer to +// clearly differentiate it from the non-pointer variant. +template <class str> inline void StringToLowerASCII(str* s) { + for (typename str::iterator i = s->begin(); i != s->end(); ++i) + *i = base::ToLowerASCII(*i); +} + +template <class str> inline str StringToLowerASCII(const str& s) { + // for std::string and std::wstring + str output(s); + StringToLowerASCII(&output); + return output; +} + +// Converts the elements of the given string. This version uses a pointer to +// clearly differentiate it from the non-pointer variant. +template <class str> inline void StringToUpperASCII(str* s) { + for (typename str::iterator i = s->begin(); i != s->end(); ++i) + *i = base::ToUpperASCII(*i); +} + +template <class str> inline str StringToUpperASCII(const str& s) { + // for std::string and std::wstring + str output(s); + StringToUpperASCII(&output); + return output; +} + +// Compare the lower-case form of the given string against the given ASCII +// string. This is useful for doing checking if an input string matches some +// 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); + +// 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, + 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, + 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); + +// 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, + 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, + bool case_sensitive); + + +// Determines the type of ASCII character, independent of locale (the C +// library versions will change based on locale). +template <typename Char> +inline bool IsAsciiWhitespace(Char c) { + return c == ' ' || c == '\r' || c == '\n' || c == '\t'; +} +template <typename Char> +inline bool IsAsciiAlpha(Char c) { + return ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')); +} +template <typename Char> +inline bool IsAsciiDigit(Char c) { + return c >= '0' && c <= '9'; +} + +template <typename Char> +inline bool IsHexDigit(Char c) { + return (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f'); +} + +template <typename Char> +inline Char HexDigitToInt(Char c) { + DCHECK(IsHexDigit(c)); + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return 0; +} + +// Returns true if it's a whitespace character. +inline bool IsWhitespace(wchar_t c) { + return wcschr(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); + +// 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_EXPORT void ReplaceFirstSubstringAfterOffset( + std::string* str, + std::string::size_type start_offset, + const std::string& find_this, + const std::string& replace_with); + +// Starting at |start_offset| (usually 0), look through |str| and replace all +// instances of |find_this| with |replace_with|. +// +// This does entire substrings; use std::replace in <algorithm> for single +// 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_EXPORT void ReplaceSubstringsAfterOffset( + std::string* str, + std::string::size_type start_offset, + const std::string& find_this, + const std::string& replace_with); + +// Reserves enough memory in |str| to accommodate |length_with_null| characters, +// sets the size of |str| to |length_with_null - 1| characters, and returns a +// pointer to the underlying contiguous array of characters. This is typically +// used when calling a function that writes results into a character array, but +// the caller wants the data to be managed by a string-like object. It is +// convenient in that is can be used inline in the call, and fast in that it +// avoids copying the results of the call from a char* into a string. +// +// |length_with_null| must be at least 2, since otherwise the underlying string +// would have size 0, and trying to access &((*str)[0]) in that case can result +// in a number of problems. +// +// Internally, this takes linear time because the resize() call 0-fills the +// underlying array for potentially all +// (|length_with_null - 1| * sizeof(string_type::value_type)) bytes. Ideally we +// could avoid this aspect of the resize() call, as we expect the caller to +// immediately write over this memory, but there is no other way to set the size +// of the string, and not doing that will mean people who access |str| rather +// than str.c_str() will get back a string of whatever size |str| had on entry +// to this function (probably 0). +template <class string_type> +inline typename string_type::value_type* WriteInto(string_type* str, + size_t length_with_null) { + DCHECK_GT(length_with_null, 1u); + str->reserve(length_with_null); + str->resize(length_with_null - 1); + return &((*str)[0]); +} + +//----------------------------------------------------------------------------- + +// 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 std::string& str, + const std::string& delimiters, + std::vector<std::string>* tokens); +BASE_EXPORT size_t Tokenize(const base::StringPiece& str, + const base::StringPiece& delimiters, + std::vector<base::StringPiece>* tokens); + +// Does the opposite of SplitString(). +BASE_EXPORT string16 JoinString(const std::vector<string16>& parts, char16 s); +BASE_EXPORT std::string JoinString( + const std::vector<std::string>& parts, char s); + +// Join |parts| using |separator|. +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); + +// 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, + std::vector<size_t>* offsets); + +BASE_EXPORT std::string ReplaceStringPlaceholders( + const base::StringPiece& format_string, + const std::vector<std::string>& subst, + 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); + +// Returns true if the string passed in matches the pattern. The pattern +// string can contain wildcards like * and ? +// The backslash character (\) is an escape character for * and ? +// We limit the patterns to having a max of 16 * or ? characters. +// ? 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); + +// Hack to convert any char-like type to its unsigned counterpart. +// For example, it will convert char, signed char and unsigned char to unsigned +// char. +template<typename T> +struct ToUnsigned { + typedef T Unsigned; +}; + +template<> +struct ToUnsigned<char> { + typedef unsigned char Unsigned; +}; +template<> +struct ToUnsigned<signed char> { + typedef unsigned char Unsigned; +}; +template<> +struct ToUnsigned<wchar_t> { +#if defined(WCHAR_T_IS_UTF16) + typedef unsigned short Unsigned; +#elif defined(WCHAR_T_IS_UTF32) + typedef uint32 Unsigned; +#endif +}; +template<> +struct ToUnsigned<short> { + typedef unsigned short Unsigned; +}; + +#endif // BASE_STRING_UTIL_H_
diff --git a/src/base/string_util_posix.h b/src/base/string_util_posix.h new file mode 100644 index 0000000..9b776e6 --- /dev/null +++ b/src/base/string_util_posix.h
@@ -0,0 +1,53 @@ +// 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. + +#ifndef BASE_STRING_UTIL_POSIX_H_ +#define BASE_STRING_UTIL_POSIX_H_ + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <wchar.h> + +#include "base/logging.h" +#include "base/string_util.h" + +namespace base { + +// Chromium code style is to not use malloc'd strings; this is only for use +// for interaction with APIs that require it. +inline char* strdup(const char* str) { + return ::strdup(str); +} + +inline int strcasecmp(const char* string1, const char* string2) { + return ::strcasecmp(string1, string2); +} + +inline int strncasecmp(const char* string1, const char* string2, size_t count) { + return ::strncasecmp(string1, string2, count); +} + +inline int vsnprintf(char* buffer, size_t size, + const char* format, va_list arguments) { + return ::vsnprintf(buffer, size, format, arguments); +} + +inline int strncmp16(const char16* s1, const char16* s2, size_t count) { +#if defined(WCHAR_T_IS_UTF16) + return ::wcsncmp(s1, s2, count); +#elif defined(WCHAR_T_IS_UTF32) + return c16memcmp(s1, s2, count); +#endif +} + +inline int vswprintf(wchar_t* buffer, size_t size, + const wchar_t* format, va_list arguments) { + DCHECK(IsWprintfFormatPortable(format)); + return ::vswprintf(buffer, size, format, arguments); +} + +} // namespace base + +#endif // BASE_STRING_UTIL_POSIX_H_
diff --git a/src/base/string_util_starboard.h b/src/base/string_util_starboard.h new file mode 100644 index 0000000..533f493 --- /dev/null +++ b/src/base/string_util_starboard.h
@@ -0,0 +1,59 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BASE_STRING_UTIL_STARBOARD_H_ +#define BASE_STRING_UTIL_STARBOARD_H_ + +#include <stdarg.h> + +#include "base/logging.h" +#include "base/string_util.h" +#include "starboard/string.h" + +namespace base { + +inline char* strdup(const char* str) { + return SbStringDuplicate(str); +} + +inline int strcasecmp(const char* string1, const char* string2) { + return SbStringCompareNoCase(string1, string2); +} + +inline int strncasecmp(const char* string1, const char* string2, size_t count) { + return SbStringCompareNoCaseN(string1, string2, count); +} + +inline int vsnprintf(char* buffer, size_t size, + const char* format, va_list arguments) { + return SbStringFormat(buffer, size, format, arguments); +} + +inline int strncmp16(const char16* s1, const char16* s2, size_t count) { +#if defined(WCHAR_T_IS_UTF16) + return SbStringCompareWide(s1, s2, count); +#elif defined(WCHAR_T_IS_UTF32) + return c16memcmp(s1, s2, count); +#endif +} + +inline int vswprintf(wchar_t* buffer, size_t size, + const wchar_t* format, va_list arguments) { + DCHECK(IsWprintfFormatPortable(format)); + return SbStringFormatWide(buffer, size, format, arguments); +} + +} // namespace base + +#endif // BASE_STRING_UTIL_STARBOARD_H_
diff --git a/src/base/string_util_unittest.cc b/src/base/string_util_unittest.cc new file mode 100644 index 0000000..d36b955 --- /dev/null +++ b/src/base/string_util_unittest.cc
@@ -0,0 +1,1190 @@ +// 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 <math.h> +#include <stdarg.h> + +#include <limits> +#include <sstream> + +#include "base/basictypes.h" +#include "base/string16.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::ElementsAre; + +namespace base { + +static const struct trim_case { + const wchar_t* input; + const TrimPositions positions; + const wchar_t* output; + const TrimPositions return_value; +} trim_cases[] = { + {L" Google Video ", TRIM_LEADING, L"Google Video ", TRIM_LEADING}, + {L" Google Video ", TRIM_TRAILING, L" Google Video", TRIM_TRAILING}, + {L" Google Video ", TRIM_ALL, L"Google Video", TRIM_ALL}, + {L"Google Video", TRIM_ALL, L"Google Video", TRIM_NONE}, + {L"", TRIM_ALL, L"", TRIM_NONE}, + {L" ", TRIM_LEADING, L"", TRIM_LEADING}, + {L" ", TRIM_TRAILING, L"", TRIM_TRAILING}, + {L" ", TRIM_ALL, L"", TRIM_ALL}, + {L"\t\rTest String\n", TRIM_ALL, L"Test String", TRIM_ALL}, + {L"\x2002Test String\x00A0\x3000", TRIM_ALL, L"Test String", TRIM_ALL}, +}; + +static const struct trim_case_ascii { + const char* input; + const TrimPositions positions; + const char* output; + const TrimPositions return_value; +} trim_cases_ascii[] = { + {" Google Video ", TRIM_LEADING, "Google Video ", TRIM_LEADING}, + {" Google Video ", TRIM_TRAILING, " Google Video", TRIM_TRAILING}, + {" Google Video ", TRIM_ALL, "Google Video", TRIM_ALL}, + {"Google Video", TRIM_ALL, "Google Video", TRIM_NONE}, + {"", TRIM_ALL, "", TRIM_NONE}, + {" ", TRIM_LEADING, "", TRIM_LEADING}, + {" ", TRIM_TRAILING, "", TRIM_TRAILING}, + {" ", TRIM_ALL, "", TRIM_ALL}, + {"\t\rTest String\n", TRIM_ALL, "Test String", TRIM_ALL}, +}; + +namespace { + +// Helper used to test TruncateUTF8ToByteSize. +bool Truncated(const std::string& input, const size_t byte_size, + std::string* output) { + size_t prev = input.length(); + TruncateUTF8ToByteSize(input, byte_size, output); + return prev != output->length(); +} + +} // namespace + +TEST(StringUtilTest, TruncateUTF8ToByteSize) { + std::string output; + + // Empty strings and invalid byte_size arguments + EXPECT_FALSE(Truncated("", 0, &output)); + EXPECT_EQ(output, ""); + EXPECT_TRUE(Truncated("\xe1\x80\xbf", 0, &output)); + EXPECT_EQ(output, ""); + EXPECT_FALSE(Truncated("\xe1\x80\xbf", -1, &output)); + EXPECT_FALSE(Truncated("\xe1\x80\xbf", 4, &output)); + + // Testing the truncation of valid UTF8 correctly + EXPECT_TRUE(Truncated("abc", 2, &output)); + EXPECT_EQ(output, "ab"); + EXPECT_TRUE(Truncated("\xc2\x81\xc2\x81", 2, &output)); + EXPECT_EQ(output.compare("\xc2\x81"), 0); + EXPECT_TRUE(Truncated("\xc2\x81\xc2\x81", 3, &output)); + EXPECT_EQ(output.compare("\xc2\x81"), 0); + EXPECT_FALSE(Truncated("\xc2\x81\xc2\x81", 4, &output)); + EXPECT_EQ(output.compare("\xc2\x81\xc2\x81"), 0); + + { + const char array[] = "\x00\x00\xc2\x81\xc2\x81"; + const std::string array_string(array, arraysize(array)); + EXPECT_TRUE(Truncated(array_string, 4, &output)); + EXPECT_EQ(output.compare(std::string("\x00\x00\xc2\x81", 4)), 0); + } + + { + const char array[] = "\x00\xc2\x81\xc2\x81"; + const std::string array_string(array, arraysize(array)); + EXPECT_TRUE(Truncated(array_string, 4, &output)); + EXPECT_EQ(output.compare(std::string("\x00\xc2\x81", 3)), 0); + } + + // Testing invalid UTF8 + EXPECT_TRUE(Truncated("\xed\xa0\x80\xed\xbf\xbf", 6, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xed\xa0\x8f", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xed\xbf\xbf", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + + // Testing invalid UTF8 mixed with valid UTF8 + EXPECT_FALSE(Truncated("\xe1\x80\xbf", 3, &output)); + EXPECT_EQ(output.compare("\xe1\x80\xbf"), 0); + EXPECT_FALSE(Truncated("\xf1\x80\xa0\xbf", 4, &output)); + EXPECT_EQ(output.compare("\xf1\x80\xa0\xbf"), 0); + EXPECT_FALSE(Truncated("a\xc2\x81\xe1\x80\xbf\xf1\x80\xa0\xbf", + 10, &output)); + EXPECT_EQ(output.compare("a\xc2\x81\xe1\x80\xbf\xf1\x80\xa0\xbf"), 0); + EXPECT_TRUE(Truncated("a\xc2\x81\xe1\x80\xbf\xf1""a""\x80\xa0", + 10, &output)); + EXPECT_EQ(output.compare("a\xc2\x81\xe1\x80\xbf\xf1""a"), 0); + EXPECT_FALSE(Truncated("\xef\xbb\xbf" "abc", 6, &output)); + EXPECT_EQ(output.compare("\xef\xbb\xbf" "abc"), 0); + + // Overlong sequences + EXPECT_TRUE(Truncated("\xc0\x80", 2, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xc1\x80\xc1\x81", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xe0\x80\x80", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xe0\x82\x80", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xe0\x9f\xbf", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf0\x80\x80\x8D", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf0\x80\x82\x91", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf0\x80\xa0\x80", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf0\x8f\xbb\xbf", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf8\x80\x80\x80\xbf", 5, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xfc\x80\x80\x80\xa0\xa5", 6, &output)); + EXPECT_EQ(output.compare(""), 0); + + // Beyond U+10FFFF (the upper limit of Unicode codespace) + EXPECT_TRUE(Truncated("\xf4\x90\x80\x80", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf8\xa0\xbf\x80\xbf", 5, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xfc\x9c\xbf\x80\xbf\x80", 6, &output)); + EXPECT_EQ(output.compare(""), 0); + + // BOMs in UTF-16(BE|LE) and UTF-32(BE|LE) + EXPECT_TRUE(Truncated("\xfe\xff", 2, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xff\xfe", 2, &output)); + EXPECT_EQ(output.compare(""), 0); + + { + const char array[] = "\x00\x00\xfe\xff"; + const std::string array_string(array, arraysize(array)); + EXPECT_TRUE(Truncated(array_string, 4, &output)); + EXPECT_EQ(output.compare(std::string("\x00\x00", 2)), 0); + } + + // Variants on the previous test + { + const char array[] = "\xff\xfe\x00\x00"; + const std::string array_string(array, 4); + EXPECT_FALSE(Truncated(array_string, 4, &output)); + EXPECT_EQ(output.compare(std::string("\xff\xfe\x00\x00", 4)), 0); + } + { + const char array[] = "\xff\x00\x00\xfe"; + const std::string array_string(array, arraysize(array)); + EXPECT_TRUE(Truncated(array_string, 4, &output)); + EXPECT_EQ(output.compare(std::string("\xff\x00\x00", 3)), 0); + } + + // Non-characters : U+xxFFF[EF] where xx is 0x00 through 0x10 and <FDD0,FDEF> + EXPECT_TRUE(Truncated("\xef\xbf\xbe", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf0\x8f\xbf\xbe", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xf3\xbf\xbf\xbf", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xef\xb7\x90", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_TRUE(Truncated("\xef\xb7\xaf", 3, &output)); + EXPECT_EQ(output.compare(""), 0); + + // Strings in legacy encodings that are valid in UTF-8, but + // are invalid as UTF-8 in real data. + EXPECT_TRUE(Truncated("caf\xe9", 4, &output)); + EXPECT_EQ(output.compare("caf"), 0); + EXPECT_TRUE(Truncated("\xb0\xa1\xb0\xa2", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + EXPECT_FALSE(Truncated("\xa7\x41\xa6\x6e", 4, &output)); + EXPECT_EQ(output.compare("\xa7\x41\xa6\x6e"), 0); + EXPECT_TRUE(Truncated("\xa7\x41\xa6\x6e\xd9\xee\xe4\xee", 7, + &output)); + EXPECT_EQ(output.compare("\xa7\x41\xa6\x6e"), 0); + + // Testing using the same string as input and output. + EXPECT_FALSE(Truncated(output, 4, &output)); + EXPECT_EQ(output.compare("\xa7\x41\xa6\x6e"), 0); + EXPECT_TRUE(Truncated(output, 3, &output)); + EXPECT_EQ(output.compare("\xa7\x41"), 0); + + // "abc" with U+201[CD] in windows-125[0-8] + EXPECT_TRUE(Truncated("\x93" "abc\x94", 5, &output)); + EXPECT_EQ(output.compare("\x93" "abc"), 0); + + // U+0639 U+064E U+0644 U+064E in ISO-8859-6 + EXPECT_TRUE(Truncated("\xd9\xee\xe4\xee", 4, &output)); + EXPECT_EQ(output.compare(""), 0); + + // U+03B3 U+03B5 U+03B9 U+03AC in ISO-8859-7 + EXPECT_TRUE(Truncated("\xe3\xe5\xe9\xdC", 4, &output)); + EXPECT_EQ(output.compare(""), 0); +} + +TEST(StringUtilTest, TrimWhitespace) { + string16 output; // Allow contents to carry over to next testcase + for (size_t i = 0; i < arraysize(trim_cases); ++i) { + const trim_case& value = trim_cases[i]; + EXPECT_EQ(value.return_value, + TrimWhitespace(WideToUTF16(value.input), value.positions, + &output)); + EXPECT_EQ(WideToUTF16(value.output), output); + } + + // Test that TrimWhitespace() can take the same string for input and output + output = ASCIIToUTF16(" This is a test \r\n"); + EXPECT_EQ(TRIM_ALL, TrimWhitespace(output, TRIM_ALL, &output)); + EXPECT_EQ(ASCIIToUTF16("This is a test"), output); + + // Once more, but with a string of whitespace + output = ASCIIToUTF16(" \r\n"); + EXPECT_EQ(TRIM_ALL, TrimWhitespace(output, TRIM_ALL, &output)); + EXPECT_EQ(string16(), output); + + std::string output_ascii; + for (size_t i = 0; i < arraysize(trim_cases_ascii); ++i) { + const trim_case_ascii& value = trim_cases_ascii[i]; + EXPECT_EQ(value.return_value, + TrimWhitespace(value.input, value.positions, &output_ascii)); + EXPECT_EQ(value.output, output_ascii); + } +} + +static const struct collapse_case { + const wchar_t* input; + const bool trim; + const wchar_t* output; +} collapse_cases[] = { + {L" Google Video ", false, L"Google Video"}, + {L"Google Video", false, L"Google Video"}, + {L"", false, L""}, + {L" ", false, L""}, + {L"\t\rTest String\n", false, L"Test String"}, + {L"\x2002Test String\x00A0\x3000", false, L"Test String"}, + {L" Test \n \t String ", false, L"Test String"}, + {L"\x2002Test\x1680 \x2028 \tString\x00A0\x3000", false, L"Test String"}, + {L" Test String", false, L"Test String"}, + {L"Test String ", false, L"Test String"}, + {L"Test String", false, L"Test String"}, + {L"", true, L""}, + {L"\n", true, L""}, + {L" \r ", true, L""}, + {L"\nFoo", true, L"Foo"}, + {L"\r Foo ", true, L"Foo"}, + {L" Foo bar ", true, L"Foo bar"}, + {L" \tFoo bar \n", true, L"Foo bar"}, + {L" a \r b\n c \r\n d \t\re \t f \n ", true, L"abcde f"}, +}; + +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)); + } +} + +static const struct collapse_case_ascii { + const char* input; + const bool trim; + const char* output; +} collapse_cases_ascii[] = { + {" Google Video ", false, "Google Video"}, + {"Google Video", false, "Google Video"}, + {"", false, ""}, + {" ", false, ""}, + {"\t\rTest String\n", false, "Test String"}, + {" Test \n \t String ", false, "Test String"}, + {" Test String", false, "Test String"}, + {"Test String ", false, "Test String"}, + {"Test String", false, "Test String"}, + {"", true, ""}, + {"\n", true, ""}, + {" \r ", true, ""}, + {"\nFoo", true, "Foo"}, + {"\r Foo ", true, "Foo"}, + {" Foo bar ", true, "Foo bar"}, + {" \tFoo bar \n", true, "Foo bar"}, + {" a \r b\n c \r\n d \t\re \t f \n ", true, "abcde f"}, +}; + +TEST(StringUtilTest, CollapseWhitespaceASCII) { + for (size_t i = 0; i < arraysize(collapse_cases_ascii); ++i) { + const collapse_case_ascii& value = collapse_cases_ascii[i]; + EXPECT_EQ(value.output, CollapseWhitespaceASCII(value.input, value.trim)); + } +} + +TEST(StringUtilTest, ContainsOnlyWhitespaceASCII) { + EXPECT_TRUE(ContainsOnlyWhitespaceASCII("")); + EXPECT_TRUE(ContainsOnlyWhitespaceASCII(" ")); + EXPECT_TRUE(ContainsOnlyWhitespaceASCII("\t")); + EXPECT_TRUE(ContainsOnlyWhitespaceASCII("\t \r \n ")); + EXPECT_FALSE(ContainsOnlyWhitespaceASCII("a")); + EXPECT_FALSE(ContainsOnlyWhitespaceASCII("\thello\r \n ")); +} + +TEST(StringUtilTest, ContainsOnlyWhitespace) { + EXPECT_TRUE(ContainsOnlyWhitespace(string16())); + EXPECT_TRUE(ContainsOnlyWhitespace(ASCIIToUTF16(" "))); + EXPECT_TRUE(ContainsOnlyWhitespace(ASCIIToUTF16("\t"))); + EXPECT_TRUE(ContainsOnlyWhitespace(ASCIIToUTF16("\t \r \n "))); + EXPECT_FALSE(ContainsOnlyWhitespace(ASCIIToUTF16("a"))); + EXPECT_FALSE(ContainsOnlyWhitespace(ASCIIToUTF16("\thello\r \n "))); +} + +TEST(StringUtilTest, IsStringUTF8) { + EXPECT_TRUE(IsStringUTF8("abc")); + EXPECT_TRUE(IsStringUTF8("\xc2\x81")); + EXPECT_TRUE(IsStringUTF8("\xe1\x80\xbf")); + EXPECT_TRUE(IsStringUTF8("\xf1\x80\xa0\xbf")); + EXPECT_TRUE(IsStringUTF8("a\xc2\x81\xe1\x80\xbf\xf1\x80\xa0\xbf")); + EXPECT_TRUE(IsStringUTF8("\xef\xbb\xbf" "abc")); // UTF-8 BOM + + // surrogate code points + EXPECT_FALSE(IsStringUTF8("\xed\xa0\x80\xed\xbf\xbf")); + EXPECT_FALSE(IsStringUTF8("\xed\xa0\x8f")); + EXPECT_FALSE(IsStringUTF8("\xed\xbf\xbf")); + + // overlong sequences + EXPECT_FALSE(IsStringUTF8("\xc0\x80")); // U+0000 + EXPECT_FALSE(IsStringUTF8("\xc1\x80\xc1\x81")); // "AB" + EXPECT_FALSE(IsStringUTF8("\xe0\x80\x80")); // U+0000 + EXPECT_FALSE(IsStringUTF8("\xe0\x82\x80")); // U+0080 + EXPECT_FALSE(IsStringUTF8("\xe0\x9f\xbf")); // U+07ff + EXPECT_FALSE(IsStringUTF8("\xf0\x80\x80\x8D")); // U+000D + EXPECT_FALSE(IsStringUTF8("\xf0\x80\x82\x91")); // U+0091 + EXPECT_FALSE(IsStringUTF8("\xf0\x80\xa0\x80")); // U+0800 + EXPECT_FALSE(IsStringUTF8("\xf0\x8f\xbb\xbf")); // U+FEFF (BOM) + EXPECT_FALSE(IsStringUTF8("\xf8\x80\x80\x80\xbf")); // U+003F + EXPECT_FALSE(IsStringUTF8("\xfc\x80\x80\x80\xa0\xa5")); // U+00A5 + + // Beyond U+10FFFF (the upper limit of Unicode codespace) + EXPECT_FALSE(IsStringUTF8("\xf4\x90\x80\x80")); // U+110000 + EXPECT_FALSE(IsStringUTF8("\xf8\xa0\xbf\x80\xbf")); // 5 bytes + EXPECT_FALSE(IsStringUTF8("\xfc\x9c\xbf\x80\xbf\x80")); // 6 bytes + + // BOMs in UTF-16(BE|LE) and UTF-32(BE|LE) + EXPECT_FALSE(IsStringUTF8("\xfe\xff")); + EXPECT_FALSE(IsStringUTF8("\xff\xfe")); + EXPECT_FALSE(IsStringUTF8(std::string("\x00\x00\xfe\xff", 4))); + EXPECT_FALSE(IsStringUTF8("\xff\xfe\x00\x00")); + + // Non-characters : U+xxFFF[EF] where xx is 0x00 through 0x10 and <FDD0,FDEF> + EXPECT_FALSE(IsStringUTF8("\xef\xbf\xbe")); // U+FFFE) + EXPECT_FALSE(IsStringUTF8("\xf0\x8f\xbf\xbe")); // U+1FFFE + EXPECT_FALSE(IsStringUTF8("\xf3\xbf\xbf\xbf")); // U+10FFFF + EXPECT_FALSE(IsStringUTF8("\xef\xb7\x90")); // U+FDD0 + EXPECT_FALSE(IsStringUTF8("\xef\xb7\xaf")); // U+FDEF + // Strings in legacy encodings. We can certainly make up strings + // in a legacy encoding that are valid in UTF-8, but in real data, + // most of them are invalid as UTF-8. + EXPECT_FALSE(IsStringUTF8("caf\xe9")); // cafe with U+00E9 in ISO-8859-1 + EXPECT_FALSE(IsStringUTF8("\xb0\xa1\xb0\xa2")); // U+AC00, U+AC001 in EUC-KR + EXPECT_FALSE(IsStringUTF8("\xa7\x41\xa6\x6e")); // U+4F60 U+597D in Big5 + // "abc" with U+201[CD] in windows-125[0-8] + EXPECT_FALSE(IsStringUTF8("\x93" "abc\x94")); + // U+0639 U+064E U+0644 U+064E in ISO-8859-6 + EXPECT_FALSE(IsStringUTF8("\xd9\xee\xe4\xee")); + // U+03B3 U+03B5 U+03B9 U+03AC in ISO-8859-7 + EXPECT_FALSE(IsStringUTF8("\xe3\xe5\xe9\xdC")); + + // Check that we support Embedded Nulls. The first uses the canonical UTF-8 + // representation, and the second uses a 2-byte sequence. The second version + // is invalid UTF-8 since UTF-8 states that the shortest encoding for a + // given codepoint must be used. + static const char kEmbeddedNull[] = "embedded\0null"; + EXPECT_TRUE(IsStringUTF8( + std::string(kEmbeddedNull, sizeof(kEmbeddedNull)))); + EXPECT_FALSE(IsStringUTF8("embedded\xc0\x80U+0000")); +} + +TEST(StringUtilTest, ConvertASCII) { + static const char* char_cases[] = { + "Google Video", + "Hello, world\n", + "0123ABCDwxyz \a\b\t\r\n!+,.~" + }; + + static const wchar_t* const wchar_cases[] = { + L"Google Video", + L"Hello, world\n", + L"0123ABCDwxyz \a\b\t\r\n!+,.~" + }; + + for (size_t i = 0; i < arraysize(char_cases); ++i) { + EXPECT_TRUE(IsStringASCII(char_cases[i])); + 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; + std::string empty; + EXPECT_EQ(empty, WideToASCII(wempty)); + EXPECT_EQ(wempty, ASCIIToWide(empty)); + + // Convert strings with an embedded NUL character. + const char chars_with_nul[] = "test\0string"; + const int length_with_nul = arraysize(chars_with_nul) - 1; + std::string string_with_nul(chars_with_nul, length_with_nul); + std::wstring wide_with_nul = ASCIIToWide(string_with_nul); + EXPECT_EQ(static_cast<std::wstring::size_type>(length_with_nul), + wide_with_nul.length()); + std::string narrow_with_nul = WideToASCII(wide_with_nul); + EXPECT_EQ(static_cast<std::string::size_type>(length_with_nul), + narrow_with_nul.length()); + EXPECT_EQ(0, string_with_nul.compare(narrow_with_nul)); +} + +TEST(StringUtilTest, ToUpperASCII) { + EXPECT_EQ('C', ToUpperASCII('C')); + EXPECT_EQ('C', ToUpperASCII('c')); + EXPECT_EQ('2', ToUpperASCII('2')); + + EXPECT_EQ(L'C', ToUpperASCII(L'C')); + EXPECT_EQ(L'C', ToUpperASCII(L'c')); + EXPECT_EQ(L'2', ToUpperASCII(L'2')); + + std::string in_place_a("Cc2"); + StringToUpperASCII(&in_place_a); + EXPECT_EQ("CC2", in_place_a); + + std::wstring in_place_w(L"Cc2"); + StringToUpperASCII(&in_place_w); + EXPECT_EQ(L"CC2", in_place_w); + + std::string original_a("Cc2"); + std::string upper_a = StringToUpperASCII(original_a); + EXPECT_EQ("CC2", upper_a); + + std::wstring original_w(L"Cc2"); + std::wstring upper_w = StringToUpperASCII(original_w); + EXPECT_EQ(L"CC2", upper_w); +} + +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" }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(lowercase_cases); ++i) { + EXPECT_TRUE(LowerCaseEqualsASCII(lowercase_cases[i].src_w, + lowercase_cases[i].dst)); + EXPECT_TRUE(LowerCaseEqualsASCII(lowercase_cases[i].src_a, + lowercase_cases[i].dst)); + } +} + +TEST(StringUtilTest, FormatBytesUnlocalized) { + static const struct { + int64 bytes; + const char* expected; + } cases[] = { + // Expected behavior: we show one post-decimal digit when we have + // under two pre-decimal digits, except in cases where it makes no + // sense (zero or bytes). + // Since we switch units once we cross the 1000 mark, this keeps + // the display of file sizes or bytes consistently around three + // digits. + {0, "0 B"}, + {512, "512 B"}, + {1024*1024, "1.0 MB"}, + {1024*1024*1024, "1.0 GB"}, + {10LL*1024*1024*1024, "10.0 GB"}, + {99LL*1024*1024*1024, "99.0 GB"}, + {105LL*1024*1024*1024, "105 GB"}, + {105LL*1024*1024*1024 + 500LL*1024*1024, "105 GB"}, + {~(1LL<<63), "8192 PB"}, + + {99*1024 + 103, "99.1 kB"}, + {1024*1024 + 103, "1.0 MB"}, + {1024*1024 + 205 * 1024, "1.2 MB"}, + {1024*1024*1024 + (927 * 1024*1024), "1.9 GB"}, + {10LL*1024*1024*1024, "10.0 GB"}, + {100LL*1024*1024*1024, "100 GB"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + EXPECT_EQ(ASCIIToUTF16(cases[i].expected), + FormatBytesUnlocalized(cases[i].bytes)); + } +} +TEST(StringUtilTest, ReplaceSubstringsAfterOffset) { + static const struct { + const char* str; + string16::size_type start_offset; + const char* find_this; + const char* replace_with; + const char* expected; + } cases[] = { + {"aaa", 0, "a", "b", "bbb"}, + {"abb", 0, "ab", "a", "ab"}, + {"Removing some substrings inging", 0, "ing", "", "Remov some substrs "}, + {"Not found", 0, "x", "0", "Not found"}, + {"Not found again", 5, "x", "0", "Not found again"}, + {" Making it much longer ", 0, " ", "Four score and seven years ago", + "Four score and seven years agoMakingFour score and seven years agoit" + "Four score and seven years agomuchFour score and seven years agolonger" + "Four score and seven years ago"}, + {"Invalid offset", 9999, "t", "foobar", "Invalid offset"}, + {"Replace me only me once", 9, "me ", "", "Replace me only once"}, + {"abababab", 2, "ab", "c", "abccc"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) { + string16 str = ASCIIToUTF16(cases[i].str); + ReplaceSubstringsAfterOffset(&str, cases[i].start_offset, + ASCIIToUTF16(cases[i].find_this), + ASCIIToUTF16(cases[i].replace_with)); + EXPECT_EQ(ASCIIToUTF16(cases[i].expected), str); + } +} + +TEST(StringUtilTest, ReplaceFirstSubstringAfterOffset) { + static const struct { + const char* str; + string16::size_type start_offset; + const char* find_this; + const char* replace_with; + const char* expected; + } cases[] = { + {"aaa", 0, "a", "b", "baa"}, + {"abb", 0, "ab", "a", "ab"}, + {"Removing some substrings inging", 0, "ing", "", + "Remov some substrings inging"}, + {"Not found", 0, "x", "0", "Not found"}, + {"Not found again", 5, "x", "0", "Not found again"}, + {" Making it much longer ", 0, " ", "Four score and seven years ago", + "Four score and seven years agoMaking it much longer "}, + {"Invalid offset", 9999, "t", "foobar", "Invalid offset"}, + {"Replace me only me once", 4, "me ", "", "Replace only me once"}, + {"abababab", 2, "ab", "c", "abcabab"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) { + string16 str = ASCIIToUTF16(cases[i].str); + ReplaceFirstSubstringAfterOffset(&str, cases[i].start_offset, + ASCIIToUTF16(cases[i].find_this), + ASCIIToUTF16(cases[i].replace_with)); + EXPECT_EQ(ASCIIToUTF16(cases[i].expected), str); + } +} + +TEST(StringUtilTest, HexDigitToInt) { + EXPECT_EQ(0, HexDigitToInt('0')); + EXPECT_EQ(1, HexDigitToInt('1')); + EXPECT_EQ(2, HexDigitToInt('2')); + EXPECT_EQ(3, HexDigitToInt('3')); + EXPECT_EQ(4, HexDigitToInt('4')); + EXPECT_EQ(5, HexDigitToInt('5')); + EXPECT_EQ(6, HexDigitToInt('6')); + EXPECT_EQ(7, HexDigitToInt('7')); + EXPECT_EQ(8, HexDigitToInt('8')); + EXPECT_EQ(9, HexDigitToInt('9')); + EXPECT_EQ(10, HexDigitToInt('A')); + EXPECT_EQ(11, HexDigitToInt('B')); + EXPECT_EQ(12, HexDigitToInt('C')); + EXPECT_EQ(13, HexDigitToInt('D')); + EXPECT_EQ(14, HexDigitToInt('E')); + EXPECT_EQ(15, HexDigitToInt('F')); + + // Verify the lower case as well. + EXPECT_EQ(10, HexDigitToInt('a')); + EXPECT_EQ(11, HexDigitToInt('b')); + EXPECT_EQ(12, HexDigitToInt('c')); + EXPECT_EQ(13, HexDigitToInt('d')); + EXPECT_EQ(14, HexDigitToInt('e')); + EXPECT_EQ(15, HexDigitToInt('f')); +} + +// This checks where we can use the assignment operator for a va_list. We need +// a way to do this since Visual C doesn't support va_copy, but assignment on +// va_list is not guaranteed to be a copy. See StringAppendVT which uses this +// capability. +static void VariableArgsFunc(const char* format, ...) { + va_list org; + va_start(org, format); + + va_list dup; + GG_VA_COPY(dup, org); + int i1 = va_arg(org, int); + int j1 = va_arg(org, int); + char* s1 = va_arg(org, char*); + double d1 = va_arg(org, double); + va_end(org); + + int i2 = va_arg(dup, int); + int j2 = va_arg(dup, int); + char* s2 = va_arg(dup, char*); + double d2 = va_arg(dup, double); + + EXPECT_EQ(i1, i2); + EXPECT_EQ(j1, j2); + EXPECT_STREQ(s1, s2); + EXPECT_EQ(d1, d2); + + va_end(dup); +} + +TEST(StringUtilTest, VAList) { + VariableArgsFunc("%d %d %s %lf", 45, 92, "This is interesting", 9.21); +} + +// Test for Tokenize +template <typename STR> +void TokenizeTest() { + std::vector<STR> r; + size_t size; + + size = Tokenize(STR("This is a string"), STR(" "), &r); + EXPECT_EQ(4U, size); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], STR("This")); + EXPECT_EQ(r[1], STR("is")); + EXPECT_EQ(r[2], STR("a")); + EXPECT_EQ(r[3], STR("string")); + r.clear(); + + size = Tokenize(STR("one,two,three"), STR(","), &r); + EXPECT_EQ(3U, size); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR("two")); + EXPECT_EQ(r[2], STR("three")); + r.clear(); + + size = Tokenize(STR("one,two:three;four"), STR(",:"), &r); + EXPECT_EQ(3U, size); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR("two")); + EXPECT_EQ(r[2], STR("three;four")); + r.clear(); + + size = Tokenize(STR("one,two:three;four"), STR(";,:"), &r); + EXPECT_EQ(4U, size); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR("two")); + EXPECT_EQ(r[2], STR("three")); + EXPECT_EQ(r[3], STR("four")); + r.clear(); + + size = Tokenize(STR("one, two, three"), STR(","), &r); + EXPECT_EQ(3U, size); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR(" two")); + EXPECT_EQ(r[2], STR(" three")); + r.clear(); + + size = Tokenize(STR("one, two, three, "), STR(","), &r); + EXPECT_EQ(4U, size); + ASSERT_EQ(4U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR(" two")); + EXPECT_EQ(r[2], STR(" three")); + EXPECT_EQ(r[3], STR(" ")); + r.clear(); + + size = Tokenize(STR("one, two, three,"), STR(","), &r); + EXPECT_EQ(3U, size); + ASSERT_EQ(3U, r.size()); + EXPECT_EQ(r[0], STR("one")); + EXPECT_EQ(r[1], STR(" two")); + EXPECT_EQ(r[2], STR(" three")); + r.clear(); + + size = Tokenize(STR(""), STR(","), &r); + EXPECT_EQ(0U, size); + ASSERT_EQ(0U, r.size()); + r.clear(); + + size = Tokenize(STR(","), STR(","), &r); + EXPECT_EQ(0U, size); + ASSERT_EQ(0U, r.size()); + r.clear(); + + size = Tokenize(STR(",;:."), STR(".:;,"), &r); + EXPECT_EQ(0U, size); + ASSERT_EQ(0U, r.size()); + r.clear(); + + size = Tokenize(STR("\t\ta\t"), STR("\t"), &r); + EXPECT_EQ(1U, size); + ASSERT_EQ(1U, r.size()); + EXPECT_EQ(r[0], STR("a")); + r.clear(); + + size = Tokenize(STR("\ta\t\nb\tcc"), STR("\n"), &r); + EXPECT_EQ(2U, size); + ASSERT_EQ(2U, r.size()); + EXPECT_EQ(r[0], STR("\ta\t")); + EXPECT_EQ(r[1], STR("b\tcc")); + r.clear(); +} + +TEST(StringUtilTest, TokenizeStdString) { + TokenizeTest<std::string>(); +} + +TEST(StringUtilTest, TokenizeStringPiece) { + TokenizeTest<base::StringPiece>(); +} + +// Test for JoinString +TEST(StringUtilTest, JoinString) { + std::vector<std::string> in; + EXPECT_EQ("", JoinString(in, ',')); + + in.push_back("a"); + EXPECT_EQ("a", JoinString(in, ',')); + + in.push_back("b"); + in.push_back("c"); + EXPECT_EQ("a,b,c", JoinString(in, ',')); + + in.push_back(""); + EXPECT_EQ("a,b,c,", JoinString(in, ',')); + in.push_back(" "); + EXPECT_EQ("a|b|c|| ", JoinString(in, '|')); +} + +// Test for JoinString overloaded with std::string separator +TEST(StringUtilTest, JoinStringWithString) { + std::string separator(", "); + std::vector<std::string> parts; + EXPECT_EQ(std::string(), JoinString(parts, separator)); + + parts.push_back("a"); + EXPECT_EQ("a", JoinString(parts, separator)); + + parts.push_back("b"); + parts.push_back("c"); + EXPECT_EQ("a, b, c", JoinString(parts, separator)); + + parts.push_back(""); + EXPECT_EQ("a, b, c, ", JoinString(parts, separator)); + parts.push_back(" "); + EXPECT_EQ("a|b|c|| ", JoinString(parts, "|")); +} + +// Test for JoinString overloaded with string16 separator +TEST(StringUtilTest, JoinStringWithString16) { + string16 separator = ASCIIToUTF16(", "); + std::vector<string16> parts; + EXPECT_EQ(string16(), JoinString(parts, separator)); + + parts.push_back(ASCIIToUTF16("a")); + EXPECT_EQ(ASCIIToUTF16("a"), JoinString(parts, separator)); + + parts.push_back(ASCIIToUTF16("b")); + parts.push_back(ASCIIToUTF16("c")); + EXPECT_EQ(ASCIIToUTF16("a, b, c"), JoinString(parts, separator)); + + parts.push_back(ASCIIToUTF16("")); + EXPECT_EQ(ASCIIToUTF16("a, b, c, "), JoinString(parts, separator)); + parts.push_back(ASCIIToUTF16(" ")); + EXPECT_EQ(ASCIIToUTF16("a|b|c|| "), JoinString(parts, ASCIIToUTF16("|"))); +} + +TEST(StringUtilTest, StartsWith) { + EXPECT_TRUE(StartsWithASCII("javascript:url", "javascript", true)); + EXPECT_FALSE(StartsWithASCII("JavaScript:url", "javascript", true)); + EXPECT_TRUE(StartsWithASCII("javascript:url", "javascript", false)); + EXPECT_TRUE(StartsWithASCII("JavaScript:url", "javascript", false)); + EXPECT_FALSE(StartsWithASCII("java", "javascript", true)); + EXPECT_FALSE(StartsWithASCII("java", "javascript", false)); + EXPECT_FALSE(StartsWithASCII("", "javascript", false)); + EXPECT_FALSE(StartsWithASCII("", "javascript", true)); + EXPECT_TRUE(StartsWithASCII("java", "", false)); + EXPECT_TRUE(StartsWithASCII("java", "", 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(L"", L"javascript", false)); + EXPECT_FALSE(StartsWith(L"", L"javascript", true)); + EXPECT_TRUE(StartsWith(L"java", L"", false)); + EXPECT_TRUE(StartsWith(L"java", L"", 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(L"", L".plugin", false)); + EXPECT_FALSE(EndsWith(L"", L".plugin", true)); + EXPECT_TRUE(EndsWith(L"Foo.plugin", L"", false)); + EXPECT_TRUE(EndsWith(L"Foo.plugin", L"", true)); + EXPECT_TRUE(EndsWith(L".plugin", L".plugin", false)); + EXPECT_TRUE(EndsWith(L".plugin", L".plugin", true)); + EXPECT_TRUE(EndsWith(L"", L"", false)); + EXPECT_TRUE(EndsWith(L"", L"", true)); +} + +TEST(StringUtilTest, GetStringFWithOffsets) { + std::vector<string16> subst; + subst.push_back(ASCIIToUTF16("1")); + subst.push_back(ASCIIToUTF16("2")); + std::vector<size_t> offsets; + + ReplaceStringPlaceholders(ASCIIToUTF16("Hello, $1. Your number is $2."), + subst, + &offsets); + EXPECT_EQ(2U, offsets.size()); + EXPECT_EQ(7U, offsets[0]); + EXPECT_EQ(25U, offsets[1]); + offsets.clear(); + + ReplaceStringPlaceholders(ASCIIToUTF16("Hello, $2. Your number is $1."), + subst, + &offsets); + EXPECT_EQ(2U, offsets.size()); + EXPECT_EQ(25U, offsets[0]); + EXPECT_EQ(7U, offsets[1]); + offsets.clear(); +} + +TEST(StringUtilTest, ReplaceStringPlaceholdersTooFew) { + // Test whether replacestringplaceholders works as expected when there + // are fewer inputs than outputs. + std::vector<string16> subst; + subst.push_back(ASCIIToUTF16("9a")); + subst.push_back(ASCIIToUTF16("8b")); + subst.push_back(ASCIIToUTF16("7c")); + + string16 formatted = + ReplaceStringPlaceholders( + ASCIIToUTF16("$1a,$2b,$3c,$4d,$5e,$6f,$1g,$2h,$3i"), subst, NULL); + + EXPECT_EQ(formatted, ASCIIToUTF16("9aa,8bb,7cc,d,e,f,9ag,8bh,7ci")); +} + +TEST(StringUtilTest, ReplaceStringPlaceholders) { + std::vector<string16> subst; + subst.push_back(ASCIIToUTF16("9a")); + subst.push_back(ASCIIToUTF16("8b")); + subst.push_back(ASCIIToUTF16("7c")); + subst.push_back(ASCIIToUTF16("6d")); + subst.push_back(ASCIIToUTF16("5e")); + subst.push_back(ASCIIToUTF16("4f")); + subst.push_back(ASCIIToUTF16("3g")); + subst.push_back(ASCIIToUTF16("2h")); + subst.push_back(ASCIIToUTF16("1i")); + + string16 formatted = + ReplaceStringPlaceholders( + ASCIIToUTF16("$1a,$2b,$3c,$4d,$5e,$6f,$7g,$8h,$9i"), subst, NULL); + + EXPECT_EQ(formatted, ASCIIToUTF16("9aa,8bb,7cc,6dd,5ee,4ff,3gg,2hh,1ii")); +} + +TEST(StringUtilTest, ReplaceStringPlaceholdersMoreThan9Replacements) { + std::vector<string16> subst; + subst.push_back(ASCIIToUTF16("9a")); + subst.push_back(ASCIIToUTF16("8b")); + subst.push_back(ASCIIToUTF16("7c")); + subst.push_back(ASCIIToUTF16("6d")); + subst.push_back(ASCIIToUTF16("5e")); + subst.push_back(ASCIIToUTF16("4f")); + subst.push_back(ASCIIToUTF16("3g")); + subst.push_back(ASCIIToUTF16("2h")); + subst.push_back(ASCIIToUTF16("1i")); + subst.push_back(ASCIIToUTF16("0j")); + subst.push_back(ASCIIToUTF16("-1k")); + subst.push_back(ASCIIToUTF16("-2l")); + subst.push_back(ASCIIToUTF16("-3m")); + subst.push_back(ASCIIToUTF16("-4n")); + + string16 formatted = + ReplaceStringPlaceholders( + ASCIIToUTF16("$1a,$2b,$3c,$4d,$5e,$6f,$7g,$8h,$9i," + "$10j,$11k,$12l,$13m,$14n,$1"), subst, NULL); + + EXPECT_EQ(formatted, ASCIIToUTF16("9aa,8bb,7cc,6dd,5ee,4ff,3gg,2hh," + "1ii,0jj,-1kk,-2ll,-3mm,-4nn,9a")); +} + +TEST(StringUtilTest, StdStringReplaceStringPlaceholders) { + std::vector<std::string> subst; + subst.push_back("9a"); + subst.push_back("8b"); + subst.push_back("7c"); + subst.push_back("6d"); + subst.push_back("5e"); + subst.push_back("4f"); + subst.push_back("3g"); + subst.push_back("2h"); + subst.push_back("1i"); + + std::string formatted = + ReplaceStringPlaceholders( + "$1a,$2b,$3c,$4d,$5e,$6f,$7g,$8h,$9i", subst, NULL); + + EXPECT_EQ(formatted, "9aa,8bb,7cc,6dd,5ee,4ff,3gg,2hh,1ii"); +} + +TEST(StringUtilTest, ReplaceStringPlaceholdersConsecutiveDollarSigns) { + std::vector<std::string> subst; + subst.push_back("a"); + subst.push_back("b"); + subst.push_back("c"); + EXPECT_EQ(ReplaceStringPlaceholders("$$1 $$$2 $$$$3", subst, NULL), + "$1 $$2 $$$3"); +} + +TEST(StringUtilTest, MatchPatternTest) { + EXPECT_TRUE(MatchPattern("www.google.com", "*.com")); + EXPECT_TRUE(MatchPattern("www.google.com", "*")); + EXPECT_FALSE(MatchPattern("www.google.com", "www*.g*.org")); + EXPECT_TRUE(MatchPattern("Hello", "H?l?o")); + EXPECT_FALSE(MatchPattern("www.google.com", "http://*)")); + EXPECT_FALSE(MatchPattern("www.msn.com", "*.COM")); + EXPECT_TRUE(MatchPattern("Hello*1234", "He??o\\*1*")); + EXPECT_FALSE(MatchPattern("", "*.*")); + EXPECT_TRUE(MatchPattern("", "*")); + EXPECT_TRUE(MatchPattern("", "?")); + EXPECT_TRUE(MatchPattern("", "")); + EXPECT_FALSE(MatchPattern("Hello", "")); + EXPECT_TRUE(MatchPattern("Hello*", "Hello*")); + // Stop after a certain recursion depth. + EXPECT_FALSE(MatchPattern("123456789012345678", "?????????????????*")); + + // Test UTF8 matching. + EXPECT_TRUE(MatchPattern("heart: \xe2\x99\xa0", "*\xe2\x99\xa0")); + EXPECT_TRUE(MatchPattern("heart: \xe2\x99\xa0.", "heart: ?.")); + EXPECT_TRUE(MatchPattern("hearts: \xe2\x99\xa0\xe2\x99\xa0", "*")); + // Invalid sequences should be handled as a single invalid character. + EXPECT_TRUE(MatchPattern("invalid: \xef\xbf\xbe", "invalid: ?")); + // If the pattern has invalid characters, it shouldn't match anything. + EXPECT_FALSE(MatchPattern("\xf4\x90\x80\x80", "\xf4\x90\x80\x80")); + + // Test UTF16 character matching. + EXPECT_TRUE(MatchPattern(UTF8ToUTF16("www.google.com"), + UTF8ToUTF16("*.com"))); + EXPECT_TRUE(MatchPattern(UTF8ToUTF16("Hello*1234"), + UTF8ToUTF16("He??o\\*1*"))); + + // This test verifies that consecutive wild cards are collapsed into 1 + // wildcard (when this doesn't occur, MatchPattern reaches it's maximum + // recursion depth). + EXPECT_TRUE(MatchPattern(UTF8ToUTF16("Hello"), + UTF8ToUTF16("He********************************o"))); +} + +TEST(StringUtilTest, LcpyTest) { + // Test the normal case where we fit in our buffer. + { + char dst[10]; + wchar_t wdst[10]; + EXPECT_EQ(7U, base::strlcpy(dst, "abcdefg", arraysize(dst))); + EXPECT_EQ(0, memcmp(dst, "abcdefg", 8)); + EXPECT_EQ(7U, base::wcslcpy(wdst, L"abcdefg", arraysize(wdst))); + EXPECT_EQ(0, memcmp(wdst, L"abcdefg", sizeof(wchar_t) * 8)); + } + + // Test dst_size == 0, nothing should be written to |dst| and we should + // have the equivalent of strlen(src). + { + char dst[2] = {1, 2}; + wchar_t wdst[2] = {1, 2}; + EXPECT_EQ(7U, base::strlcpy(dst, "abcdefg", 0)); + EXPECT_EQ(1, dst[0]); + EXPECT_EQ(2, dst[1]); + EXPECT_EQ(7U, base::wcslcpy(wdst, L"abcdefg", 0)); +#if defined(WCHAR_T_IS_UNSIGNED) + EXPECT_EQ(1U, wdst[0]); + EXPECT_EQ(2U, wdst[1]); +#else + EXPECT_EQ(1, wdst[0]); + EXPECT_EQ(2, wdst[1]); +#endif + } + + // Test the case were we _just_ competely fit including the null. + { + char dst[8]; + wchar_t wdst[8]; + EXPECT_EQ(7U, base::strlcpy(dst, "abcdefg", arraysize(dst))); + EXPECT_EQ(0, memcmp(dst, "abcdefg", 8)); + EXPECT_EQ(7U, base::wcslcpy(wdst, L"abcdefg", arraysize(wdst))); + EXPECT_EQ(0, memcmp(wdst, L"abcdefg", sizeof(wchar_t) * 8)); + } + + // Test the case were we we are one smaller, so we can't fit the null. + { + char dst[7]; + wchar_t wdst[7]; + EXPECT_EQ(7U, base::strlcpy(dst, "abcdefg", arraysize(dst))); + EXPECT_EQ(0, memcmp(dst, "abcdef", 7)); + EXPECT_EQ(7U, base::wcslcpy(wdst, L"abcdefg", arraysize(wdst))); + EXPECT_EQ(0, memcmp(wdst, L"abcdef", sizeof(wchar_t) * 7)); + } + + // Test the case were we are just too small. + { + char dst[3]; + wchar_t wdst[3]; + EXPECT_EQ(7U, base::strlcpy(dst, "abcdefg", arraysize(dst))); + EXPECT_EQ(0, memcmp(dst, "ab", 3)); + EXPECT_EQ(7U, base::wcslcpy(wdst, L"abcdefg", arraysize(wdst))); + EXPECT_EQ(0, memcmp(wdst, L"ab", sizeof(wchar_t) * 3)); + } +} + +TEST(StringUtilTest, WprintfFormatPortabilityTest) { + static const struct { + const wchar_t* input; + bool portable; + } cases[] = { + { L"%ls", true }, + { L"%s", false }, + { L"%S", false }, + { L"%lS", false }, + { L"Hello, %s", false }, + { L"%lc", true }, + { L"%c", false }, + { L"%C", false }, + { L"%lC", false }, + { L"%ls %s", false }, + { L"%s %ls", false }, + { L"%s %ls %s", false }, + { L"%f", true }, + { L"%f %F", false }, + { L"%d %D", false }, + { L"%o %O", false }, + { L"%u %U", false }, + { L"%f %d %o %u", true }, + { L"%-8d (%02.1f%)", true }, + { L"% 10s", false }, + { L"% 10ls", true } + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) + EXPECT_EQ(cases[i].portable, base::IsWprintfFormatPortable(cases[i].input)); +} + +TEST(StringUtilTest, RemoveChars) { + const char* kRemoveChars = "-/+*"; + std::string input = "A-+bc/d!*"; + EXPECT_TRUE(RemoveChars(input, kRemoveChars, &input)); + EXPECT_EQ("Abcd!", input); + + // No characters match kRemoveChars. + EXPECT_FALSE(RemoveChars(input, kRemoveChars, &input)); + EXPECT_EQ("Abcd!", input); + + // Empty string. + input.clear(); + EXPECT_FALSE(RemoveChars(input, kRemoveChars, &input)); + EXPECT_EQ(std::string(), input); +} + +TEST(StringUtilTest, ReplaceChars) { + struct TestData { + const char* input; + const char* replace_chars; + const char* replace_with; + const char* output; + bool result; + } cases[] = { + { "", "", "", "", false }, + { "test", "", "", "test", false }, + { "test", "", "!", "test", false }, + { "test", "z", "!", "test", false }, + { "test", "e", "!", "t!st", true }, + { "test", "e", "!?", "t!?st", true }, + { "test", "ez", "!", "t!st", true }, + { "test", "zed", "!?", "t!?st", true }, + { "test", "t", "!?", "!?es!?", true }, + { "test", "et", "!>", "!>!>s!>", true }, + { "test", "zest", "!", "!!!!", true }, + { "test", "szt", "!", "!e!!", true }, + { "test", "t", "test", "testestest", true }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + std::string output; + bool result = ReplaceChars(cases[i].input, + cases[i].replace_chars, + cases[i].replace_with, + &output); + EXPECT_EQ(cases[i].result, result); + EXPECT_EQ(cases[i].output, output); + } +} + +TEST(StringUtilTest, ContainsOnlyChars) { + // Providing an empty list of characters should return false but for the empty + // string. + EXPECT_TRUE(ContainsOnlyChars("", "")); + EXPECT_FALSE(ContainsOnlyChars("Hello", "")); + + EXPECT_TRUE(ContainsOnlyChars("", "1234")); + EXPECT_TRUE(ContainsOnlyChars("1", "1234")); + EXPECT_TRUE(ContainsOnlyChars("1", "4321")); + EXPECT_TRUE(ContainsOnlyChars("123", "4321")); + EXPECT_FALSE(ContainsOnlyChars("123a", "4321")); +} + +class WriteIntoTest : public testing::Test { + protected: + static void WritesCorrectly(size_t num_chars) { + std::string buffer; + char kOriginal[] = "supercali"; + strncpy(WriteInto(&buffer, num_chars + 1), kOriginal, num_chars); + // Using std::string(buffer.c_str()) instead of |buffer| truncates the + // string at the first \0. + EXPECT_EQ(std::string(kOriginal, + std::min(num_chars, arraysize(kOriginal) - 1)), + std::string(buffer.c_str())); + EXPECT_EQ(num_chars, buffer.size()); + } +}; + +TEST_F(WriteIntoTest, WriteInto) { + // Validate that WriteInto reserves enough space and + // sizes a string correctly. + WritesCorrectly(1); + WritesCorrectly(2); + WritesCorrectly(5000); + + // Validate that WriteInto doesn't modify other strings + // when using a Copy-on-Write implementation. + const char kLive[] = "live"; + const char kDead[] = "dead"; + const std::string live = kLive; + std::string dead = live; + strncpy(WriteInto(&dead, 5), kDead, 4); + EXPECT_EQ(kDead, dead); + EXPECT_EQ(4u, dead.size()); + EXPECT_EQ(kLive, live); + EXPECT_EQ(4u, live.size()); +} + +} // namespace base
diff --git a/src/base/string_util_win.h b/src/base/string_util_win.h new file mode 100644 index 0000000..2a4d3fd --- /dev/null +++ b/src/base/string_util_win.h
@@ -0,0 +1,61 @@ +// 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_STRING_UTIL_WIN_H_ +#define BASE_STRING_UTIL_WIN_H_ + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <wchar.h> + +#include "base/logging.h" + +namespace base { + +// Chromium code style is to not use malloc'd strings; this is only for use +// for interaction with APIs that require it. +inline char* strdup(const char* str) { + return _strdup(str); +} + +inline int strcasecmp(const char* s1, const char* s2) { + return _stricmp(s1, s2); +} + +inline int strncasecmp(const char* s1, const char* s2, size_t count) { + return _strnicmp(s1, s2, count); +} + +inline int strncmp16(const char16* s1, const char16* s2, size_t count) { + return ::wcsncmp(s1, s2, count); +} + +inline int vsnprintf(char* buffer, size_t size, + const char* format, va_list arguments) { + int length = _vsprintf_p(buffer, size, format, arguments); + if (length < 0) { + if (size > 0) + buffer[0] = 0; + return _vscprintf_p(format, arguments); + } + return length; +} + +inline int vswprintf(wchar_t* buffer, size_t size, + const wchar_t* format, va_list arguments) { + DCHECK(IsWprintfFormatPortable(format)); + + int length = _vswprintf_p(buffer, size, format, arguments); + if (length < 0) { + if (size > 0) + buffer[0] = 0; + return _vscwprintf_p(format, arguments); + } + return length; +} + +} // namespace base + +#endif // BASE_STRING_UTIL_WIN_H_
diff --git a/src/base/stringize_macros.h b/src/base/stringize_macros.h new file mode 100644 index 0000000..1d53e48 --- /dev/null +++ b/src/base/stringize_macros.h
@@ -0,0 +1,56 @@ +// Copyright (c) 2010 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 preprocessor macros for stringizing preprocessor +// symbols (or their output) and manipulating preprocessor symbols +// that define strings. + +#ifndef BASE_STRINGIZE_MACROS_H_ +#define BASE_STRINGIZE_MACROS_H_ + +#include "build/build_config.h" + +// This is not very useful as it does not expand defined symbols if +// called directly. Use its counterpart without the _NO_EXPANSION +// suffix, below. +#define STRINGIZE_NO_EXPANSION(x) #x + +// Use this to quote the provided parameter, first expanding it if it +// is a preprocessor symbol. +// +// For example, if: +// #define A FOO +// #define B(x) myobj->FunctionCall(x) +// +// Then: +// STRINGIZE(A) produces "FOO" +// STRINGIZE(B(y)) produces "myobj->FunctionCall(y)" +#define STRINGIZE(x) STRINGIZE_NO_EXPANSION(x) + +// The following are defined only on Windows (for use when interacting +// with Windows APIs) as wide strings are otherwise deprecated. +#if defined(OS_WIN) + +// Second-level utility macros to let us expand symbols. +#define LSTRINGIZE_NO_EXPANSION(x) L ## #x +#define TO_L_STRING_NO_EXPANSION(x) L ## x + +// L version of STRINGIZE(). For examples above, +// LSTRINGIZE(A) produces L"FOO" +// LSTRINGIZE(B(y)) produces L"myobj->FunctionCall(y)" +#define LSTRINGIZE(x) LSTRINGIZE_NO_EXPANSION(x) + +// Adds an L in front of an existing ASCII string constant (after +// expanding symbols). Does not do any quoting. +// +// For example, if: +// #define C "foo" +// +// Then: +// TO_L_STRING(C) produces L"foo" +#define TO_L_STRING(x) TO_L_STRING_NO_EXPANSION(x) + +#endif // defined(OS_WIN) + +#endif // BASE_STRINGIZE_MACROS_H_
diff --git a/src/base/stringize_macros_unittest.cc b/src/base/stringize_macros_unittest.cc new file mode 100644 index 0000000..8d92d53 --- /dev/null +++ b/src/base/stringize_macros_unittest.cc
@@ -0,0 +1,59 @@ +// Copyright (c) 2010 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. +// +// Unit tests for stringize_macros.h + +#include "base/stringize_macros.h" + +#include "testing/gtest/include/gtest/gtest.h" + +// Macros as per documentation in header file. +#define PREPROCESSOR_UTIL_UNITTEST_A FOO +#define PREPROCESSOR_UTIL_UNITTEST_B(x) myobj->FunctionCall(x) +#define PREPROCESSOR_UTIL_UNITTEST_C "foo" + +TEST(StringizeTest, Ansi) { + EXPECT_STREQ( + "PREPROCESSOR_UTIL_UNITTEST_A", + STRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_A)); + EXPECT_STREQ( + "PREPROCESSOR_UTIL_UNITTEST_B(y)", + STRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_B(y))); + EXPECT_STREQ( + "PREPROCESSOR_UTIL_UNITTEST_C", + STRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_C)); + + EXPECT_STREQ("FOO", STRINGIZE(PREPROCESSOR_UTIL_UNITTEST_A)); + EXPECT_STREQ("myobj->FunctionCall(y)", + STRINGIZE(PREPROCESSOR_UTIL_UNITTEST_B(y))); + EXPECT_STREQ("\"foo\"", STRINGIZE(PREPROCESSOR_UTIL_UNITTEST_C)); +} + +#if defined(OS_WIN) + +TEST(StringizeTest, Wide) { + EXPECT_STREQ( + L"PREPROCESSOR_UTIL_UNITTEST_A", + LSTRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_A)); + EXPECT_STREQ( + L"PREPROCESSOR_UTIL_UNITTEST_B(y)", + LSTRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_B(y))); + EXPECT_STREQ( + L"PREPROCESSOR_UTIL_UNITTEST_C", + LSTRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_C)); + + EXPECT_STREQ(L"FOO", LSTRINGIZE(PREPROCESSOR_UTIL_UNITTEST_A)); + EXPECT_STREQ(L"myobj->FunctionCall(y)", + LSTRINGIZE(PREPROCESSOR_UTIL_UNITTEST_B(y))); + EXPECT_STREQ(L"\"foo\"", LSTRINGIZE(PREPROCESSOR_UTIL_UNITTEST_C)); +} + +TEST(ToLStringTest, Main) { + EXPECT_STREQ(L"blat", TO_L_STRING_NO_EXPANSION("blat")); + + EXPECT_STREQ(L"foo", TO_L_STRING(PREPROCESSOR_UTIL_UNITTEST_C)); + EXPECT_STREQ(L"blat", TO_L_STRING("blat")); +} + +#endif // defined(OS_WIN)
diff --git a/src/base/stringprintf.cc b/src/base/stringprintf.cc new file mode 100644 index 0000000..76a6dbd --- /dev/null +++ b/src/base/stringprintf.cc
@@ -0,0 +1,195 @@ +// 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/stringprintf.h" + +#include <errno.h> + +#include "base/string_util.h" +#include "base/utf_string_conversions.h" + +namespace base { + +namespace { + +#if defined(__LB_SHELL__) || defined(COBALT) +// Emperically derived lowest number required to pass unit tests. +const int kMaxStringPrintfBufferSize = 8 * 1024; // 8kB +#else +const int kMaxStringPrintfBufferSize = 32 * 1024 * 1024; // 32MB +#endif + +// Overloaded wrappers around vsnprintf and vswprintf. The buf_size parameter +// is the size of the buffer. These return the number of characters in the +// formatted string excluding the NUL terminator. If the buffer is not +// large enough to accommodate the formatted string without truncation, they +// return the number of characters that would be in the fully-formatted string +// (vsnprintf, and vswprintf on Windows), or -1 (vswprintf on POSIX platforms). +inline int vsnprintfT(char* buffer, + size_t buf_size, + const char* format, + va_list argptr) { + return base::vsnprintf(buffer, buf_size, format, argptr); +} + +#if !defined(OS_ANDROID) +inline int vsnprintfT(wchar_t* buffer, + size_t buf_size, + const wchar_t* format, + va_list argptr) { + return base::vswprintf(buffer, buf_size, format, argptr); +} +#endif + +// Templatized backend for StringPrintF/StringAppendF. This does not finalize +// the va_list, the caller is expected to do that. +template <class StringType> +static void StringAppendVT(StringType* dst, + const typename StringType::value_type* format, + va_list ap) { + // First try with a small fixed size buffer. + // This buffer size should be kept in sync with StringUtilTest.GrowBoundary + // and StringUtilTest.StringPrintfBounds. + typename StringType::value_type stack_buf[1024]; + + va_list ap_copy; + GG_VA_COPY(ap_copy, ap); + +#if !defined(OS_WIN) + errno = 0; +#endif + int result = vsnprintfT(stack_buf, arraysize(stack_buf), format, ap_copy); + va_end(ap_copy); + + if (result >= 0 && result < static_cast<int>(arraysize(stack_buf))) { + // It fit. + dst->append(stack_buf, result); + return; + } + + // Repeatedly increase buffer size until it fits. + int mem_length = arraysize(stack_buf); + while (true) { + if (result < 0) { +#if defined(__LB_XB1__) || defined(__LB_XB360__) + // On XB1, we get a -1 with ERANGE when the buffer is not big enough. + if (result != -1 || (errno != 0 && errno != ERANGE)) +#elif !defined(OS_WIN) + // On Windows, vsnprintfT always returns the number of characters in a + // fully-formatted string, so if we reach this point, something else is + // wrong and no amount of buffer-doubling is going to fix it. + if (errno != 0 && errno != EOVERFLOW) +#endif + { + // If an error other than overflow occurred, it's never going to work. + DLOG(WARNING) << "Unable to printf the requested string due to error."; + return; + } + // Try doubling the buffer size. + mem_length *= 2; + } else { + // We need exactly "result + 1" characters. + mem_length = result + 1; + } + + if (mem_length > kMaxStringPrintfBufferSize) { + // That should be plenty, don't try anything larger. This protects + // against huge allocations when using vsnprintfT implementations that + // return -1 for reasons other than overflow without setting errno. + DLOG(WARNING) << "Unable to printf the requested string due to size."; + return; + } + + std::vector<typename StringType::value_type> mem_buf(mem_length); + + // NOTE: You can only use a va_list once. Since we're in a while loop, we + // need to make a new copy each time so we don't use up the original. + GG_VA_COPY(ap_copy, ap); + result = vsnprintfT(&mem_buf[0], mem_length, format, ap_copy); + va_end(ap_copy); + + if ((result >= 0) && (result < mem_length)) { + // It fit. + dst->append(&mem_buf[0], result); + return; + } + } +} + +} // namespace + +std::string StringPrintf(const char* format, ...) { + va_list ap; + va_start(ap, format); + std::string result; + StringAppendV(&result, format, ap); + va_end(ap); + return result; +} + +#if !defined(OS_ANDROID) +std::wstring StringPrintf(const wchar_t* format, ...) { + va_list ap; + va_start(ap, format); + std::wstring result; + StringAppendV(&result, format, ap); + va_end(ap); + return result; +} +#endif + +std::string StringPrintV(const char* format, va_list ap) { + std::string result; + StringAppendV(&result, format, ap); + return result; +} + +const std::string& SStringPrintf(std::string* dst, const char* format, ...) { + va_list ap; + va_start(ap, format); + dst->clear(); + StringAppendV(dst, format, ap); + va_end(ap); + return *dst; +} + +#if !defined(OS_ANDROID) +const std::wstring& SStringPrintf(std::wstring* dst, + const wchar_t* format, ...) { + va_list ap; + va_start(ap, format); + dst->clear(); + StringAppendV(dst, format, ap); + va_end(ap); + return *dst; +} +#endif + +void StringAppendF(std::string* dst, const char* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(dst, format, ap); + va_end(ap); +} + +#if !defined(OS_ANDROID) +void StringAppendF(std::wstring* dst, const wchar_t* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(dst, format, ap); + va_end(ap); +} +#endif + +void StringAppendV(std::string* dst, const char* format, va_list ap) { + StringAppendVT(dst, format, ap); +} + +#if !defined(OS_ANDROID) +void StringAppendV(std::wstring* dst, const wchar_t* format, va_list ap) { + StringAppendVT(dst, format, ap); +} +#endif + +} // namespace base
diff --git a/src/base/stringprintf.h b/src/base/stringprintf.h new file mode 100644 index 0000000..9a99237 --- /dev/null +++ b/src/base/stringprintf.h
@@ -0,0 +1,68 @@ +// 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_STRINGPRINTF_H_ +#define BASE_STRINGPRINTF_H_ + +#include <stdarg.h> // va_list + +#include <string> + +#include "base/base_export.h" +#include "base/compiler_specific.h" + +namespace base { + +// Return a C++ string given printf-like input. +BASE_EXPORT std::string StringPrintf(const char* format, ...) + PRINTF_FORMAT(1, 2); +// OS_ANDROID's libc does not support wchar_t, so several overloads are omitted. +#if !defined(OS_ANDROID) +BASE_EXPORT std::wstring StringPrintf(const wchar_t* format, ...) + WPRINTF_FORMAT(1, 2); +#endif + +// Return a C++ string given vprintf-like input. +BASE_EXPORT std::string StringPrintV(const char* format, va_list ap) + PRINTF_FORMAT(1, 0); + +// Store result into a supplied string and return it. +BASE_EXPORT const std::string& SStringPrintf(std::string* dst, + const char* format, ...) + PRINTF_FORMAT(2, 3); +#if !defined(OS_ANDROID) +BASE_EXPORT const std::wstring& SStringPrintf(std::wstring* dst, + const wchar_t* format, ...) + WPRINTF_FORMAT(2, 3); +#endif + +// Append result to a supplied string. +BASE_EXPORT void StringAppendF(std::string* dst, const char* format, ...) + PRINTF_FORMAT(2, 3); +#if !defined(OS_ANDROID) +// TODO(evanm): this is only used in a few places in the code; +// replace with string16 version. +BASE_EXPORT void StringAppendF(std::wstring* dst, const wchar_t* format, ...) + WPRINTF_FORMAT(2, 3); +#endif + +// Lower-level routine that takes a va_list and appends to a specified +// string. All other routines are just convenience wrappers around it. +BASE_EXPORT void StringAppendV(std::string* dst, const char* format, va_list ap) + PRINTF_FORMAT(2, 0); +#if !defined(OS_ANDROID) +BASE_EXPORT void StringAppendV(std::wstring* dst, + const wchar_t* format, va_list ap) + WPRINTF_FORMAT(2, 0); +#endif + +} // namespace base + +// Don't require the namespace for legacy code. New code should use "base::" or +// have its own using decl. +// +// TODO(brettw) remove these when calling code is converted. +using base::StringPrintf; + +#endif // BASE_STRINGPRINTF_H_
diff --git a/src/base/stringprintf_unittest.cc b/src/base/stringprintf_unittest.cc new file mode 100644 index 0000000..d8fdfee --- /dev/null +++ b/src/base/stringprintf_unittest.cc
@@ -0,0 +1,198 @@ +// 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/basictypes.h" +#include "base/stringprintf.h" +#include "testing/gtest/include/gtest/gtest.h" +#if defined(OS_STARBOARD) +#include "starboard/string.h" +#endif + +namespace base { + +namespace { + +// A helper for the StringAppendV test that follows. +// +// Just forwards its args to StringAppendV. +static void StringAppendVTestHelper(std::string* out, const char* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(out, format, ap); + va_end(ap); +} + +#if defined(OS_STARBOARD) +// Same deal for SbStringFormat +static void SbStringFormatHelper(char *out_buffer, + size_t buffer_size, + const char *format, + ...) { + va_list ap; + va_start(ap, format); + SbStringFormat(out_buffer, buffer_size, format, ap); + va_end(ap); +} +#endif + +} // namespace + +TEST(StringPrintfTest, StringPrintfEmpty) { + EXPECT_EQ("", StringPrintf("%s", "")); +} + +TEST(StringPrintfTest, StringPrintfMisc) { + EXPECT_EQ("123hello w", StringPrintf("%3d%2s %1c", 123, "hello", 'w')); +#if !defined(OS_ANDROID) && !defined(__LB_ANDROID__) + EXPECT_EQ(L"123hello w", StringPrintf(L"%3d%2ls %1lc", 123, L"hello", 'w')); +#endif +} + +TEST(StringPrintfTest, StringAppendfEmptyString) { + std::string value("Hello"); + StringAppendF(&value, "%s", ""); + EXPECT_EQ("Hello", value); + +#if !defined(OS_ANDROID) && !defined(__LB_ANDROID__) + std::wstring valuew(L"Hello"); + StringAppendF(&valuew, L"%ls", L""); + EXPECT_EQ(L"Hello", valuew); +#endif +} + +TEST(StringPrintfTest, StringAppendfString) { + std::string value("Hello"); + StringAppendF(&value, " %s", "World"); + EXPECT_EQ("Hello World", value); + +#if !defined(OS_ANDROID) && !defined(__LB_ANDROID__) + std::wstring valuew(L"Hello"); + StringAppendF(&valuew, L" %ls", L"World"); + EXPECT_EQ(L"Hello World", valuew); +#endif +} + +TEST(StringPrintfTest, StringAppendfInt) { + std::string value("Hello"); + StringAppendF(&value, " %d", 123); + EXPECT_EQ("Hello 123", value); + +#if !defined(OS_ANDROID) && !defined(__LB_ANDROID__) + std::wstring valuew(L"Hello"); + StringAppendF(&valuew, L" %d", 123); + EXPECT_EQ(L"Hello 123", valuew); +#endif +} + +// Make sure that lengths exactly around the initial buffer size are handled +// correctly. +TEST(StringPrintfTest, StringPrintfBounds) { + const int kSrcLen = 1026; + char src[kSrcLen]; + for (size_t i = 0; i < arraysize(src); i++) + src[i] = 'A'; + + wchar_t srcw[kSrcLen]; + for (size_t i = 0; i < arraysize(srcw); i++) + srcw[i] = 'A'; + + for (int i = 1; i < 3; i++) { + src[kSrcLen - i] = 0; + std::string out; + SStringPrintf(&out, "%s", src); + EXPECT_STREQ(src, out.c_str()); + +#if !defined(OS_ANDROID) && !defined(__LB_ANDROID__) + srcw[kSrcLen - i] = 0; + std::wstring outw; + SStringPrintf(&outw, L"%ls", srcw); + EXPECT_STREQ(srcw, outw.c_str()); +#endif + } +} + +// Test very large sprintfs that will cause the buffer to grow. +TEST(StringPrintfTest, Grow) { + char src[1026]; + for (size_t i = 0; i < arraysize(src); i++) + src[i] = 'A'; + src[1025] = 0; + + const char* fmt = "%sB%sB%sB%sB%sB%sB%s"; + + std::string out; + SStringPrintf(&out, fmt, src, src, src, src, src, src, src); + + const int kRefSize = 320000; + char* ref = new char[kRefSize]; +#if defined(OS_WIN) + sprintf_s(ref, kRefSize, fmt, src, src, src, src, src, src, src); +#elif defined(OS_POSIX) + snprintf(ref, kRefSize, fmt, src, src, src, src, src, src, src); +#elif defined(OS_STARBOARD) + SbStringFormatHelper(ref, kRefSize, fmt, src, src, src, src, src, src, src); +#endif + + EXPECT_STREQ(ref, out.c_str()); + delete[] ref; +} + +TEST(StringPrintfTest, StringAppendV) { + std::string out; + StringAppendVTestHelper(&out, "%d foo %s", 1, "bar"); + EXPECT_EQ("1 foo bar", out); +} + +// Test the boundary condition for the size of the string_util's +// internal buffer. +TEST(StringPrintfTest, GrowBoundary) { + const int string_util_buf_len = 1024; + // Our buffer should be one larger than the size of StringAppendVT's stack + // buffer. + const int buf_len = string_util_buf_len + 1; + char src[buf_len + 1]; // Need extra one for NULL-terminator. + for (int i = 0; i < buf_len; ++i) + src[i] = 'a'; + src[buf_len] = 0; + + std::string out; + SStringPrintf(&out, "%s", src); + + EXPECT_STREQ(src, out.c_str()); +} + +// TODO(evanm): what's the proper cross-platform test here? +#if defined(OS_WIN) || defined(__LB_XB1__) || defined(__LB_XB360__) +// sprintf in Visual Studio fails when given U+FFFF. This tests that the +// failure case is gracefuly handled. +TEST(StringPrintfTest, Invalid) { + wchar_t invalid[2]; + invalid[0] = 0xffff; + invalid[1] = 0; + + std::wstring out; + SStringPrintf(&out, L"%ls", invalid); + EXPECT_STREQ(L"", out.c_str()); +} +#endif + +// lbshell platforms do not support positional parameters, +// and lbshell does not use the few parts of chromium that +// leverage positional parameter support in the OS. +#if !defined(__LB_SHELL__) +// Test that the positional parameters work. +TEST(StringPrintfTest, PositionalParameters) { + std::string out; + SStringPrintf(&out, "%1$s %1$s", "test"); + EXPECT_STREQ("test test", out.c_str()); + +#if defined(OS_WIN) + std::wstring wout; + SStringPrintf(&wout, L"%1$ls %1$ls", L"test"); + EXPECT_STREQ(L"test test", wout.c_str()); +#endif +} +#endif + +} // namespace base
diff --git a/src/base/supports_user_data.cc b/src/base/supports_user_data.cc new file mode 100644 index 0000000..2a0263e --- /dev/null +++ b/src/base/supports_user_data.cc
@@ -0,0 +1,40 @@ +// 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/supports_user_data.h" + +namespace base { + +SupportsUserData::SupportsUserData() { + // Harmless to construct on a different thread to subsequent usage. + thread_checker_.DetachFromThread(); +} + +SupportsUserData::Data* SupportsUserData::GetUserData(const void* key) const { + DCHECK(thread_checker_.CalledOnValidThread()); + DataMap::const_iterator found = user_data_.find(key); + if (found != user_data_.end()) + return found->second.get(); + return NULL; +} + +void SupportsUserData::SetUserData(const void* key, Data* data) { + DCHECK(thread_checker_.CalledOnValidThread()); + user_data_[key] = linked_ptr<Data>(data); +} + +void SupportsUserData::RemoveUserData(const void* key) { + DCHECK(thread_checker_.CalledOnValidThread()); + user_data_.erase(key); +} + +void SupportsUserData::DetachUserDataThread() { + thread_checker_.DetachFromThread(); +} + +SupportsUserData::~SupportsUserData() { + DCHECK(thread_checker_.CalledOnValidThread() || user_data_.empty()); +} + +} // namespace base
diff --git a/src/base/supports_user_data.h b/src/base/supports_user_data.h new file mode 100644 index 0000000..7736755 --- /dev/null +++ b/src/base/supports_user_data.h
@@ -0,0 +1,81 @@ +// 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_SUPPORTS_USER_DATA_H_ +#define BASE_SUPPORTS_USER_DATA_H_ + +#include <map> + +#include "base/base_export.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/ref_counted.h" +#include "base/threading/thread_checker.h" + +namespace base { + +// This is a helper for classes that want to allow users to stash random data by +// key. At destruction all the objects will be destructed. +class BASE_EXPORT SupportsUserData { + public: + SupportsUserData(); + + // Derive from this class and add your own data members to associate extra + // information with this object. Alternatively, add this as a public base + // class to any class with a virtual destructor. + class BASE_EXPORT Data { + public: + virtual ~Data() {} + }; + + // The user data allows the clients to associate data with this object. + // Multiple user data values can be stored under different keys. + // This object will TAKE OWNERSHIP of the given data pointer, and will + // delete the object if it is changed or the object is destroyed. + Data* GetUserData(const void* key) const; + void SetUserData(const void* key, Data* data); + void RemoveUserData(const void* key); + + // SupportsUserData is not thread-safe, and on debug build will assert it is + // only used on one thread. Calling this method allows the caller to hand + // the SupportsUserData instance across threads. Use only if you are taking + // full control of the synchronization of that hand over. + void DetachUserDataThread(); + + protected: + virtual ~SupportsUserData(); + + private: + typedef std::map<const void*, linked_ptr<Data> > DataMap; + + // Externally-defined data accessible by key. + DataMap user_data_; + // Guards usage of |user_data_| + ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(SupportsUserData); +}; + +// Adapter class that releases a refcounted object when the +// SupportsUserData::Data object is deleted. +template <typename T> +class UserDataAdapter : public base::SupportsUserData::Data { + public: + static T* Get(SupportsUserData* supports_user_data, const char* key) { + UserDataAdapter* data = + static_cast<UserDataAdapter*>(supports_user_data->GetUserData(key)); + return data ? static_cast<T*>(data->object_.get()) : NULL; + } + + UserDataAdapter(T* object) : object_(object) {} + T* release() { return object_.release(); } + + private: + scoped_refptr<T> object_; + + DISALLOW_COPY_AND_ASSIGN(UserDataAdapter); +}; + +} // namespace base + +#endif // BASE_SUPPORTS_USER_DATA_H_
diff --git a/src/base/sync_socket.h b/src/base/sync_socket.h new file mode 100644 index 0000000..eb3eeb5 --- /dev/null +++ b/src/base/sync_socket.h
@@ -0,0 +1,124 @@ +// 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_SYNC_SOCKET_H_ +#define BASE_SYNC_SOCKET_H_ + +// A socket abstraction used for sending and receiving plain +// data. Because the receiving is blocking, they can be used to perform +// rudimentary cross-process synchronization with low latency. + +#include "base/basictypes.h" +#if defined(OS_WIN) +#include <windows.h> +#endif +#include <sys/types.h> + +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/synchronization/waitable_event.h" + +namespace base { + +class BASE_EXPORT SyncSocket { + public: +#if defined(OS_WIN) + typedef HANDLE Handle; +#else + typedef int Handle; +#endif + static const Handle kInvalidHandle; + + SyncSocket(); + + // Creates a SyncSocket from a Handle. Used in transport. + explicit SyncSocket(Handle handle) : handle_(handle) {} + virtual ~SyncSocket(); + + // Initializes and connects a pair of sockets. + // |socket_a| and |socket_b| must not hold a valid handle. Upon successful + // return, the sockets will both be valid and connected. + static bool CreatePair(SyncSocket* socket_a, SyncSocket* socket_b); + + // Closes the SyncSocket. Returns true on success, false on failure. + virtual bool Close(); + + // Sends the message to the remote peer of the SyncSocket. + // Note it is not safe to send messages from the same socket handle by + // multiple threads simultaneously. + // buffer is a pointer to the data to send. + // length is the length of the data to send (must be non-zero). + // Returns the number of bytes sent, or 0 upon failure. + virtual size_t Send(const void* buffer, size_t length); + + // Receives a message from an SyncSocket. + // buffer is a pointer to the buffer to receive data. + // length is the number of bytes of data to receive (must be non-zero). + // Returns the number of bytes received, or 0 upon failure. + virtual size_t Receive(void* buffer, size_t length); + + // 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 + // size as a promise and simply test against zero. + size_t Peek(); + + // Extracts the contained handle. Used for transferring between + // processes. + Handle handle() const { return handle_; } + + protected: + Handle handle_; + + private: + DISALLOW_COPY_AND_ASSIGN(SyncSocket); +}; + +// Derives from SyncSocket and adds support for shutting down the socket from +// another thread while a blocking Receive or Send is being done from the +// thread that owns the socket. +class BASE_EXPORT CancelableSyncSocket : public SyncSocket { + public: + CancelableSyncSocket(); + explicit CancelableSyncSocket(Handle handle); + virtual ~CancelableSyncSocket() {} + + // Initializes a pair of cancelable sockets. See documentation for + // SyncSocket::CreatePair for more details. + static bool CreatePair(CancelableSyncSocket* socket_a, + CancelableSyncSocket* socket_b); + + // A way to shut down a socket even if another thread is currently performing + // a blocking Receive or Send. + bool Shutdown(); + +#if defined(OS_WIN) + // Since the Linux and Mac implementations actually use a socket, shutting + // them down from another thread is pretty simple - we can just call + // shutdown(). However, the Windows implementation relies on named pipes + // and there isn't a way to cancel a blocking synchronous Read that is + // supported on <Vista. So, for Windows only, we override these + // SyncSocket methods in order to support shutting down the 'socket'. + virtual bool Close() OVERRIDE; + virtual size_t Receive(void* buffer, size_t length) OVERRIDE; +#endif + + // Send() is overridden to catch cases where the remote end is not responding + // and we fill the local socket buffer. When the buffer is full, this + // implementation of Send() will not block indefinitely as + // SyncSocket::Send will, but instead return 0, as no bytes could be sent. + // Note that the socket will not be closed in this case. + virtual size_t Send(const void* buffer, size_t length) OVERRIDE; + + private: +#if defined(OS_WIN) + WaitableEvent shutdown_event_; + WaitableEvent file_operation_; +#endif + DISALLOW_COPY_AND_ASSIGN(CancelableSyncSocket); +}; + +} // namespace base + +#endif // BASE_SYNC_SOCKET_H_
diff --git a/src/base/sync_socket_nacl.cc b/src/base/sync_socket_nacl.cc new file mode 100644 index 0000000..6d6405d --- /dev/null +++ b/src/base/sync_socket_nacl.cc
@@ -0,0 +1,74 @@ +// 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/sync_socket.h" + +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <sys/types.h> + +#include "base/file_util.h" +#include "base/logging.h" + + +namespace base { + +const SyncSocket::Handle SyncSocket::kInvalidHandle = -1; + +SyncSocket::SyncSocket() : handle_(kInvalidHandle) { +} + +SyncSocket::~SyncSocket() { +} + +// static +bool SyncSocket::CreatePair(SyncSocket* socket_a, SyncSocket* socket_b) { + return false; +} + +bool SyncSocket::Close() { + if (handle_ != kInvalidHandle) { + if (close(handle_) < 0) + DPLOG(ERROR) << "close"; + handle_ = -1; + } + 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; +} + +size_t SyncSocket::Receive(void* buffer, size_t length) { + return read(handle_, buffer, length); +} + +size_t SyncSocket::Peek() { + return -1; +} + +CancelableSyncSocket::CancelableSyncSocket() { +} + +CancelableSyncSocket::CancelableSyncSocket(Handle handle) + : SyncSocket(handle) { +} + +size_t CancelableSyncSocket::Send(const void* buffer, size_t length) { + return -1; +} + +bool CancelableSyncSocket::Shutdown() { + return false; +} + +// static +bool CancelableSyncSocket::CreatePair(CancelableSyncSocket* socket_a, + CancelableSyncSocket* socket_b) { + return SyncSocket::CreatePair(socket_a, socket_b); +} + +} // namespace base
diff --git a/src/base/sync_socket_posix.cc b/src/base/sync_socket_posix.cc new file mode 100644 index 0000000..257916d --- /dev/null +++ b/src/base/sync_socket_posix.cc
@@ -0,0 +1,154 @@ +// 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/sync_socket.h" + +#include <errno.h> +#include <limits.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/types.h> + +#if defined(OS_SOLARIS) +#include <sys/filio.h> +#endif + +#include "base/file_util.h" +#include "base/logging.h" + + +namespace base { + +namespace { +// To avoid users sending negative message lengths to Send/Receive +// we clamp message lengths, which are size_t, to no more than INT_MAX. +const size_t kMaxMessageLength = static_cast<size_t>(INT_MAX); + +} // namespace + +const SyncSocket::Handle SyncSocket::kInvalidHandle = -1; + +SyncSocket::SyncSocket() : handle_(kInvalidHandle) {} + +SyncSocket::~SyncSocket() { + Close(); +} + +// 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); + +#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 defined(OS_MACOSX) + // On OSX an attempt to read or write to a closed socket may generate a + // SIGPIPE rather than returning -1. setsockopt will shut this off. + if (0 != setsockopt(handles[0], SOL_SOCKET, SO_NOSIGPIPE, + &nosigpipe, sizeof nosigpipe) || + 0 != setsockopt(handles[1], SOL_SOCKET, SO_NOSIGPIPE, + &nosigpipe, sizeof nosigpipe)) { + goto cleanup; + } +#endif + + // Copy the handles out for successful return. + socket_a->handle_ = handles[0]; + 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"; + handle_ = kInvalidHandle; + return (retval == 0); +} + +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); +} + +size_t SyncSocket::Receive(void* buffer, size_t length) { + DCHECK_LE(length, kMaxMessageLength); + char* charbuffer = static_cast<char*>(buffer); + if (file_util::ReadFromFD(handle_, charbuffer, length)) + return length; + return 0; +} + +size_t SyncSocket::Peek() { + int number_chars; + if (-1 == ioctl(handle_, FIONREAD, &number_chars)) { + // If there is an error in ioctl, signal that the channel would block. + return 0; + } + return (size_t) number_chars; +} + +CancelableSyncSocket::CancelableSyncSocket() {} +CancelableSyncSocket::CancelableSyncSocket(Handle handle) + : SyncSocket(handle) { +} + +bool CancelableSyncSocket::Shutdown() { + 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); + 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); + + if (flags != -1 && (flags & O_NONBLOCK) == 0) { + // Restore the original flags. + fcntl(handle_, F_SETFL, flags); + } + + return len; +} + +// static +bool CancelableSyncSocket::CreatePair(CancelableSyncSocket* socket_a, + CancelableSyncSocket* socket_b) { + return SyncSocket::CreatePair(socket_a, socket_b); +} + +} // namespace base
diff --git a/src/base/sync_socket_win.cc b/src/base/sync_socket_win.cc new file mode 100644 index 0000000..4fcd572 --- /dev/null +++ b/src/base/sync_socket_win.cc
@@ -0,0 +1,271 @@ +// 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/sync_socket.h" + +#include "base/logging.h" +#include "base/win/scoped_handle.h" + +namespace base { + +using win::ScopedHandle; + +namespace { +// IMPORTANT: do not change how this name is generated because it will break +// in sandboxed scenarios as we might have by-name policies that allow pipe +// creation. Also keep the secure random number generation. +const wchar_t kPipeNameFormat[] = L"\\\\.\\pipe\\chrome.sync.%u.%u.%lu"; +const size_t kPipePathMax = arraysize(kPipeNameFormat) + (3 * 10) + 1; + +// To avoid users sending negative message lengths to Send/Receive +// we clamp message lengths, which are size_t, to no more than INT_MAX. +const size_t kMaxMessageLength = static_cast<size_t>(INT_MAX); + +const int kOutBufferSize = 4096; +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); + + wchar_t name[kPipePathMax]; + ScopedHandle handle_a; + DWORD flags = PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE; + if (overlapped) + flags |= FILE_FLAG_OVERLAPPED; + + do { + unsigned int rnd_name; + if (rand_s(&rnd_name) != 0) + return false; + + swprintf(name, kPipePathMax, + kPipeNameFormat, + GetCurrentProcessId(), + GetCurrentThreadId(), + rnd_name); + + handle_a.Set(CreateNamedPipeW( + name, + flags, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, + 1, + kOutBufferSize, + kInBufferSize, + kDefaultTimeoutMilliSeconds, + NULL)); + } while (!handle_a.IsValid() && + (GetLastError() == ERROR_PIPE_BUSY)); + + if (!handle_a.IsValid()) { + NOTREACHED(); + return false; + } + + // The SECURITY_ANONYMOUS flag means that the server side (handle_a) cannot + // impersonate the client (handle_b). This allows us not to care which side + // ends up in which side of a privilege boundary. + flags = SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS; + if (overlapped) + flags |= FILE_FLAG_OVERLAPPED; + + ScopedHandle handle_b(CreateFileW(name, + GENERIC_READ | GENERIC_WRITE, + 0, // no sharing. + NULL, // default security attributes. + OPEN_EXISTING, // opens existing pipe. + flags, + NULL)); // no template file. + if (!handle_b.IsValid()) { + DPLOG(ERROR) << "CreateFileW failed"; + return false; + } + + if (!ConnectNamedPipe(handle_a, NULL)) { + DWORD error = GetLastError(); + if (error != ERROR_PIPE_CONNECTED) { + DPLOG(ERROR) << "ConnectNamedPipe failed"; + return false; + } + } + + *socket_a = handle_a.Take(); + *socket_b = handle_b.Take(); + + return true; +} + +// Inline helper to avoid having the cast everywhere. +DWORD GetNextChunkSize(size_t current_pos, size_t max_size) { + // The following statement is for 64 bit portability. + return static_cast<DWORD>(((max_size - current_pos) <= UINT_MAX) ? + (max_size - current_pos) : UINT_MAX); +} + +// Template function that supports calling ReadFile or WriteFile in an +// overlapped fashion and waits for IO completion. The function also waits +// 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, + CancelableSyncSocket* socket, + DWORD timeout_in_ms) { + // 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_LE(length, kMaxMessageLength); + + OVERLAPPED ol = {0}; + ol.hEvent = io_event->handle(); + size_t count = 0; + while (count < length) { + 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) { + if (::GetLastError() == ERROR_IO_PENDING) { + HANDLE events[] = { io_event->handle(), cancel_event->handle() }; + int wait_result = WaitForMultipleObjects( + arraysize(events), events, FALSE, timeout_in_ms); + 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."; + CancelIo(file); + socket->Close(); + count = 0; + break; + } else { + // Timeout happened. + DCHECK_EQ(WAIT_TIMEOUT, wait_result); + if (!CancelIo(file)){ + DLOG(WARNING) << "CancelIo() failed"; + } + break; + } + } else { + break; + } + } + + count += len; + + // Quit the operation if we can't write/read anymore. + if (len != chunk) + break; + } + + return (count > 0) ? count : 0; +} + +} // namespace + +const SyncSocket::Handle SyncSocket::kInvalidHandle = INVALID_HANDLE_VALUE; + +SyncSocket::SyncSocket() : handle_(kInvalidHandle) {} + +SyncSocket::~SyncSocket() { + Close(); +} + +// static +bool SyncSocket::CreatePair(SyncSocket* socket_a, SyncSocket* socket_b) { + return CreatePairImpl(&socket_a->handle_, &socket_b->handle_, false); +} + +bool SyncSocket::Close() { + if (handle_ == kInvalidHandle) + return false; + + BOOL retval = CloseHandle(handle_); + handle_ = kInvalidHandle; + return retval ? true : false; +} + +size_t SyncSocket::Send(const void* buffer, size_t length) { + DCHECK_LE(length, kMaxMessageLength); + 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; + } + count += len; + } + return count; +} + +size_t SyncSocket::Receive(void* buffer, size_t length) { + DCHECK_LE(length, kMaxMessageLength); + 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; + } + count += len; + } + return count; +} + +size_t SyncSocket::Peek() { + DWORD available = 0; + PeekNamedPipe(handle_, NULL, 0, NULL, &available, NULL); + return available; +} + +CancelableSyncSocket::CancelableSyncSocket() + : shutdown_event_(true, false), file_operation_(true, false) { +} + +CancelableSyncSocket::CancelableSyncSocket(Handle handle) + : SyncSocket(handle), shutdown_event_(true, false), + file_operation_(true, false) { +} + +bool CancelableSyncSocket::Shutdown() { + // This doesn't shut down the pipe immediately, but subsequent Receive or Send + // methods will fail straight away. + shutdown_event_.Signal(); + return true; +} + +bool CancelableSyncSocket::Close() { + bool ret = SyncSocket::Close(); + shutdown_event_.Reset(); + return ret; +} + +size_t CancelableSyncSocket::Send(const void* buffer, size_t length) { + static const DWORD kWaitTimeOutInMs = 500; + return CancelableFileOperation( + &WriteFile, handle_, reinterpret_cast<const char*>(buffer), + length, &file_operation_, &shutdown_event_, this, kWaitTimeOutInMs); +} + +size_t CancelableSyncSocket::Receive(void* buffer, size_t length) { + return CancelableFileOperation(&ReadFile, handle_, + reinterpret_cast<char*>(buffer), length, &file_operation_, + &shutdown_event_, this, INFINITE); +} + +// static +bool CancelableSyncSocket::CreatePair(CancelableSyncSocket* socket_a, + CancelableSyncSocket* socket_b) { + return CreatePairImpl(&socket_a->handle_, &socket_b->handle_, true); +} + + +} // namespace base
diff --git a/src/base/synchronization/cancellation_flag.cc b/src/base/synchronization/cancellation_flag.cc new file mode 100644 index 0000000..ad3b551 --- /dev/null +++ b/src/base/synchronization/cancellation_flag.cc
@@ -0,0 +1,22 @@ +// 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/synchronization/cancellation_flag.h" + +#include "base/logging.h" + +namespace base { + +void CancellationFlag::Set() { +#if !defined(NDEBUG) + DCHECK_EQ(set_on_, PlatformThread::CurrentId()); +#endif + base::subtle::Release_Store(&flag_, 1); +} + +bool CancellationFlag::IsSet() const { + return base::subtle::Acquire_Load(&flag_) != 0; +} + +} // namespace base
diff --git a/src/base/synchronization/cancellation_flag.h b/src/base/synchronization/cancellation_flag.h new file mode 100644 index 0000000..51a4def --- /dev/null +++ b/src/base/synchronization/cancellation_flag.h
@@ -0,0 +1,43 @@ +// 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. + +#ifndef BASE_SYNCHRONIZATION_CANCELLATION_FLAG_H_ +#define BASE_SYNCHRONIZATION_CANCELLATION_FLAG_H_ + +#include "base/base_export.h" +#include "base/atomicops.h" +#include "base/threading/platform_thread.h" + +namespace base { + +// CancellationFlag allows one thread to cancel jobs executed on some worker +// thread. Calling Set() from one thread and IsSet() from a number of threads +// is thread-safe. +// +// This class IS NOT intended for synchronization between threads. +class BASE_EXPORT CancellationFlag { + public: + CancellationFlag() : flag_(false) { +#if !defined(NDEBUG) + set_on_ = PlatformThread::CurrentId(); +#endif + } + ~CancellationFlag() {} + + // Set the flag. May only be called on the thread which owns the object. + void Set(); + bool IsSet() const; // Returns true iff the flag was set. + + private: + base::subtle::Atomic32 flag_; +#if !defined(NDEBUG) + PlatformThreadId set_on_; +#endif + + DISALLOW_COPY_AND_ASSIGN(CancellationFlag); +}; + +} // namespace base + +#endif // BASE_SYNCHRONIZATION_CANCELLATION_FLAG_H_
diff --git a/src/base/synchronization/cancellation_flag_unittest.cc b/src/base/synchronization/cancellation_flag_unittest.cc new file mode 100644 index 0000000..02a31b5 --- /dev/null +++ b/src/base/synchronization/cancellation_flag_unittest.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. + +// Tests of CancellationFlag class. + +#include "base/synchronization/cancellation_flag.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/synchronization/spin_wait.h" +#include "base/time.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +namespace base { + +namespace { + +//------------------------------------------------------------------------------ +// Define our test class. +//------------------------------------------------------------------------------ + +void CancelHelper(CancellationFlag* flag) { +#if GTEST_HAS_DEATH_TEST + ASSERT_DEBUG_DEATH(flag->Set(), ""); +#endif +} + +TEST(CancellationFlagTest, SimpleSingleThreadedTest) { + CancellationFlag flag; + ASSERT_FALSE(flag.IsSet()); + flag.Set(); + ASSERT_TRUE(flag.IsSet()); +} + +TEST(CancellationFlagTest, DoubleSetTest) { + CancellationFlag flag; + ASSERT_FALSE(flag.IsSet()); + flag.Set(); + ASSERT_TRUE(flag.IsSet()); + flag.Set(); + ASSERT_TRUE(flag.IsSet()); +} + +TEST(CancellationFlagTest, SetOnDifferentThreadDeathTest) { + // Checks that Set() can't be called from any other thread. + // CancellationFlag should die on a DCHECK if Set() is called from + // other thread. + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + Thread t("CancellationFlagTest.SetOnDifferentThreadDeathTest"); + ASSERT_TRUE(t.Start()); + ASSERT_TRUE(t.message_loop()); + ASSERT_TRUE(t.IsRunning()); + + CancellationFlag flag; + t.message_loop()->PostTask(FROM_HERE, base::Bind(&CancelHelper, &flag)); +} + +} // namespace + +} // namespace base
diff --git a/src/base/synchronization/condition_variable.h b/src/base/synchronization/condition_variable.h new file mode 100644 index 0000000..fb42d2e --- /dev/null +++ b/src/base/synchronization/condition_variable.h
@@ -0,0 +1,129 @@ +// 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. + +// ConditionVariable wraps pthreads condition variable synchronization or, on +// Windows, simulates it. This functionality is very helpful for having +// several threads wait for an event, as is common with a thread pool managed +// by a master. The meaning of such an event in the (worker) thread pool +// scenario is that additional tasks are now available for processing. It is +// used in Chrome in the DNS prefetching system to notify worker threads that +// a queue now has items (tasks) which need to be tended to. A related use +// would have a pool manager waiting on a ConditionVariable, waiting for a +// thread in the pool to announce (signal) that there is now more room in a +// (bounded size) communications queue for the manager to deposit tasks, or, +// as a second example, that the queue of tasks is completely empty and all +// workers are waiting. +// +// USAGE NOTE 1: spurious signal events are possible with this and +// most implementations of condition variables. As a result, be +// *sure* to retest your condition before proceeding. The following +// is a good example of doing this correctly: +// +// while (!work_to_be_done()) Wait(...); +// +// In contrast do NOT do the following: +// +// if (!work_to_be_done()) Wait(...); // Don't do this. +// +// Especially avoid the above if you are relying on some other thread only +// issuing a signal up *if* there is work-to-do. There can/will +// be spurious signals. Recheck state on waiting thread before +// assuming the signal was intentional. Caveat caller ;-). +// +// USAGE NOTE 2: Broadcast() frees up all waiting threads at once, +// which leads to contention for the locks they all held when they +// called Wait(). This results in POOR performance. A much better +// approach to getting a lot of threads out of Wait() is to have each +// thread (upon exiting Wait()) call Signal() to free up another +// Wait'ing thread. Look at condition_variable_unittest.cc for +// both examples. +// +// Broadcast() can be used nicely during teardown, as it gets the job +// done, and leaves no sleeping threads... and performance is less +// critical at that point. +// +// The semantics of Broadcast() are carefully crafted so that *all* +// threads that were waiting when the request was made will indeed +// get signaled. Some implementations mess up, and don't signal them +// all, while others allow the wait to be effectively turned off (for +// a while while waiting threads come around). This implementation +// appears correct, as it will not "lose" any signals, and will guarantee +// that all threads get signaled by Broadcast(). +// +// This implementation offers support for "performance" in its selection of +// which thread to revive. Performance, in direct contrast with "fairness," +// assures that the thread that most recently began to Wait() is selected by +// Signal to revive. Fairness would (if publicly supported) assure that the +// thread that has Wait()ed the longest is selected. The default policy +// may improve performance, as the selected thread may have a greater chance of +// having some of its stack data in various CPU caches. +// +// For a discussion of the many very subtle implementation details, see the FAQ +// at the end of condition_variable_win.cc. + +#ifndef BASE_SYNCHRONIZATION_CONDITION_VARIABLE_H_ +#define BASE_SYNCHRONIZATION_CONDITION_VARIABLE_H_ + +#include "build/build_config.h" + +#if defined(OS_STARBOARD) +#include "starboard/condition_variable.h" +#include "starboard/mutex.h" +#elif defined(__LB_SHELL__) +#include "lb_mutex.h" +#elif defined(OS_POSIX) +#include <pthread.h> +#endif + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/synchronization/lock.h" + +namespace base { + +class ConditionVarImpl; +class TimeDelta; + +class BASE_EXPORT ConditionVariable { + public: + // Construct a cv for use with ONLY one user lock. + explicit ConditionVariable(Lock* user_lock); + + ~ConditionVariable(); + + // Wait() releases the caller's critical section atomically as it starts to + // sleep, and the reacquires it when it is signaled. + void Wait(); + void TimedWait(const TimeDelta& max_time); + + // Broadcast() revives all waiting threads. + void Broadcast(); + // Signal() revives one waiting thread. + void Signal(); + + private: + +#if defined(OS_WIN) + ConditionVarImpl* impl_; +#elif defined(OS_STARBOARD) + SbConditionVariable condition_; + SbMutex* user_mutex_; +#elif defined(__LB_SHELL__) + lb_shell_cond_t condition_; + lb_shell_mutex_t* user_mutex_; +#elif defined(OS_POSIX) + pthread_cond_t condition_; + pthread_mutex_t* user_mutex_; +#endif + +#if !defined(NDEBUG) && !defined(OS_WIN) + base::Lock* user_lock_; // Needed to adjust shadow lock state on wait. +#endif + + DISALLOW_COPY_AND_ASSIGN(ConditionVariable); +}; + +} // namespace base + +#endif // BASE_SYNCHRONIZATION_CONDITION_VARIABLE_H_
diff --git a/src/base/synchronization/condition_variable_posix.cc b/src/base/synchronization/condition_variable_posix.cc new file mode 100644 index 0000000..da2bb31 --- /dev/null +++ b/src/base/synchronization/condition_variable_posix.cc
@@ -0,0 +1,80 @@ +// 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/synchronization/condition_variable.h" + +#include <errno.h> +#include <sys/time.h> + +#include "base/logging.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_restrictions.h" +#include "base/time.h" + +namespace base { + +ConditionVariable::ConditionVariable(Lock* user_lock) + : user_mutex_(user_lock->lock_.os_lock()) +#if !defined(NDEBUG) + , user_lock_(user_lock) +#endif +{ + int rv = pthread_cond_init(&condition_, NULL); + DCHECK_EQ(0, rv); +} + +ConditionVariable::~ConditionVariable() { + int rv = pthread_cond_destroy(&condition_); + DCHECK_EQ(0, rv); +} + +void ConditionVariable::Wait() { + base::ThreadRestrictions::AssertWaitAllowed(); +#if !defined(NDEBUG) + user_lock_->CheckHeldAndUnmark(); +#endif + int rv = pthread_cond_wait(&condition_, user_mutex_); + DCHECK_EQ(0, rv); +#if !defined(NDEBUG) + user_lock_->CheckUnheldAndMark(); +#endif +} + +void ConditionVariable::TimedWait(const TimeDelta& max_time) { + base::ThreadRestrictions::AssertWaitAllowed(); + int64 usecs = max_time.InMicroseconds(); + + // The timeout argument to pthread_cond_timedwait is in absolute time. + struct timeval now; + gettimeofday(&now, NULL); + + 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 + +#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(); +#endif +} + +void ConditionVariable::Broadcast() { + int rv = pthread_cond_broadcast(&condition_); + DCHECK_EQ(0, rv); +} + +void ConditionVariable::Signal() { + int rv = pthread_cond_signal(&condition_); + DCHECK_EQ(0, rv); +} + +} // namespace base
diff --git a/src/base/synchronization/condition_variable_shell.cc b/src/base/synchronization/condition_variable_shell.cc new file mode 100644 index 0000000..57c63af --- /dev/null +++ b/src/base/synchronization/condition_variable_shell.cc
@@ -0,0 +1,76 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/synchronization/condition_variable.h" + +#include <errno.h> + +#include "base/logging.h" +#include "base/synchronization/lock.h" +#include "base/time.h" + +namespace base { + +ConditionVariable::ConditionVariable(Lock* user_lock) + : user_mutex_(user_lock->lock_.os_lock()) +#if !defined(NDEBUG) + , user_lock_(user_lock) +#endif +{ + int rv = lb_shell_cond_init(&condition_, user_mutex_); + DCHECK_EQ(0, rv); +} + +ConditionVariable::~ConditionVariable() { + int rv = lb_shell_cond_destroy(&condition_); + DCHECK_EQ(0, rv); +} + +void ConditionVariable::Wait() { +#if !defined(NDEBUG) + user_lock_->CheckHeldAndUnmark(); +#endif + int rv = lb_shell_cond_wait(&condition_, user_mutex_); + DCHECK_EQ(0, rv); +#if !defined(NDEBUG) + user_lock_->CheckUnheldAndMark(); +#endif +} + +void ConditionVariable::TimedWait(const TimeDelta& max_time) { + int64 usecs = max_time.InMicroseconds(); + +#if !defined(NDEBUG) + user_lock_->CheckHeldAndUnmark(); +#endif + int rv = lb_shell_cond_timedwait(&condition_, user_mutex_, usecs); + DCHECK(rv == 0 || rv == ETIMEDOUT); +#if !defined(NDEBUG) + user_lock_->CheckUnheldAndMark(); +#endif +} + +void ConditionVariable::Broadcast() { + int rv = lb_shell_cond_broadcast(&condition_); + DCHECK_EQ(0, rv); +} + +void ConditionVariable::Signal() { + int rv = lb_shell_cond_signal(&condition_); + DCHECK_EQ(0, rv); +} + +} // namespace base
diff --git a/src/base/synchronization/condition_variable_starboard.cc b/src/base/synchronization/condition_variable_starboard.cc new file mode 100644 index 0000000..816a2f4 --- /dev/null +++ b/src/base/synchronization/condition_variable_starboard.cc
@@ -0,0 +1,76 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/synchronization/condition_variable.h" + +#include "base/logging.h" +#include "base/synchronization/lock.h" +#include "base/time.h" +#include "starboard/condition_variable.h" +#include "starboard/mutex.h" + +namespace base { + +ConditionVariable::ConditionVariable(Lock* user_lock) + : user_mutex_(user_lock->lock_.os_lock()) +#if !defined(NDEBUG) + , user_lock_(user_lock) +#endif +{ + bool result = SbConditionVariableCreate(&condition_, user_mutex_); + DCHECK(result); +} + +ConditionVariable::~ConditionVariable() { + bool result = SbConditionVariableDestroy(&condition_); + DCHECK(result); +} + +void ConditionVariable::Wait() { +#if !defined(NDEBUG) + user_lock_->CheckHeldAndUnmark(); +#endif + SbConditionVariableResult result = + SbConditionVariableWait(&condition_, user_mutex_); + DCHECK(SbConditionVariableIsSignaled(result)); +#if !defined(NDEBUG) + user_lock_->CheckUnheldAndMark(); +#endif +} + +void ConditionVariable::TimedWait(const TimeDelta& max_time) { + SbTime duration = max_time.ToSbTime(); + +#if !defined(NDEBUG) + user_lock_->CheckHeldAndUnmark(); +#endif + SbConditionVariableResult result = + SbConditionVariableWaitTimed(&condition_, user_mutex_, duration); + DCHECK_NE(kSbConditionVariableFailed, result); +#if !defined(NDEBUG) + user_lock_->CheckUnheldAndMark(); +#endif +} + +void ConditionVariable::Broadcast() { + bool result = SbConditionVariableBroadcast(&condition_); + DCHECK(result); +} + +void ConditionVariable::Signal() { + bool result = SbConditionVariableSignal(&condition_); + DCHECK(result); +} + +} // namespace base
diff --git a/src/base/synchronization/condition_variable_unittest.cc b/src/base/synchronization/condition_variable_unittest.cc new file mode 100644 index 0000000..bc8201f --- /dev/null +++ b/src/base/synchronization/condition_variable_unittest.cc
@@ -0,0 +1,727 @@ +// 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. + +// Multi-threaded tests of ConditionVariable class. + +#include <time.h> +#include <algorithm> +#include <vector> + +#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_collision_warner.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +#if defined(OS_STARBOARD) +#include "starboard/configuration.h" +#endif + +namespace base { + +namespace { +//------------------------------------------------------------------------------ +// Define our test class, with several common variables. +//------------------------------------------------------------------------------ + +class ConditionVariableTest : public PlatformTest { + public: + const TimeDelta kZeroMs; + const TimeDelta kTenMs; + const TimeDelta kThirtyMs; + const TimeDelta kFortyFiveMs; + const TimeDelta kSixtyMs; + const TimeDelta kOneHundredMs; + + explicit ConditionVariableTest() + : kZeroMs(TimeDelta::FromMilliseconds(0)), + kTenMs(TimeDelta::FromMilliseconds(10)), + kThirtyMs(TimeDelta::FromMilliseconds(30)), + kFortyFiveMs(TimeDelta::FromMilliseconds(45)), + kSixtyMs(TimeDelta::FromMilliseconds(60)), + kOneHundredMs(TimeDelta::FromMilliseconds(100)) { + } +}; + +//------------------------------------------------------------------------------ +// Define a class that will control activities an several multi-threaded tests. +// The general structure of multi-threaded tests is that a test case will +// construct an instance of a WorkQueue. The WorkQueue will spin up some +// threads and control them throughout their lifetime, as well as maintaining +// a central repository of the work thread's activity. Finally, the WorkQueue +// will command the the worker threads to terminate. At that point, the test +// cases will validate that the WorkQueue has records showing that the desired +// activities were performed. +//------------------------------------------------------------------------------ + +// Callers are responsible for synchronizing access to the following class. +// The WorkQueue::lock_, as accessed via WorkQueue::lock(), should be used for +// all synchronized access. +class WorkQueue : public PlatformThread::Delegate { + public: + explicit WorkQueue(int thread_count); + virtual ~WorkQueue(); + + // PlatformThread::Delegate interface. + virtual void ThreadMain() OVERRIDE; + + //---------------------------------------------------------------------------- + // Worker threads only call the following methods. + // They should use the lock to get exclusive access. + int GetThreadId(); // Get an ID assigned to a thread.. + bool EveryIdWasAllocated() const; // Indicates that all IDs were handed out. + TimeDelta GetAnAssignment(int thread_id); // Get a work task duration. + void WorkIsCompleted(int thread_id); + + int task_count() const; + bool allow_help_requests() const; // Workers can signal more workers. + bool shutdown() const; // Check if shutdown has been requested. + + void thread_shutting_down(); + + + //---------------------------------------------------------------------------- + // Worker threads can call them but not needed to acquire a lock. + Lock* lock(); + + ConditionVariable* work_is_available(); + ConditionVariable* all_threads_have_ids(); + ConditionVariable* no_more_tasks(); + + //---------------------------------------------------------------------------- + // The rest of the methods are for use by the controlling master thread (the + // test case code). + void ResetHistory(); + int GetMinCompletionsByWorkerThread() const; + int GetMaxCompletionsByWorkerThread() const; + int GetNumThreadsTakingAssignments() const; + int GetNumThreadsCompletingTasks() const; + int GetNumberOfCompletedTasks() const; + TimeDelta GetWorkTime() const; + + void SetWorkTime(TimeDelta delay); + void SetTaskCount(int count); + void SetAllowHelp(bool allow); + + // The following must be called without locking, and will spin wait until the + // threads are all in a wait state. + void SpinUntilAllThreadsAreWaiting(); + void SpinUntilTaskCountLessThan(int task_count); + + // Caller must acquire lock before calling. + void SetShutdown(); + + // Compares the |shutdown_task_count_| to the |thread_count| and returns true + // if they are equal. This check will acquire the |lock_| so the caller + // should not hold the lock when calling this method. + bool ThreadSafeCheckShutdown(int thread_count); + + private: + // Both worker threads and controller use the following to synchronize. + Lock lock_; + ConditionVariable work_is_available_; // To tell threads there is work. + + // Conditions to notify the controlling process (if it is interested). + ConditionVariable all_threads_have_ids_; // All threads are running. + ConditionVariable no_more_tasks_; // Task count is zero. + + const int thread_count_; + int waiting_thread_count_; + scoped_array<PlatformThreadHandle> thread_handles_; + std::vector<int> assignment_history_; // Number of assignment per worker. + std::vector<int> completion_history_; // Number of completions per worker. + int thread_started_counter_; // Used to issue unique id to workers. + int shutdown_task_count_; // Number of tasks told to shutdown + int task_count_; // Number of assignment tasks waiting to be processed. + TimeDelta worker_delay_; // Time each task takes to complete. + bool allow_help_requests_; // Workers can signal more workers. + bool shutdown_; // Set when threads need to terminate. + + DFAKE_MUTEX(locked_methods_); +}; + +//------------------------------------------------------------------------------ +// The next section contains the actual tests. +//------------------------------------------------------------------------------ + +TEST_F(ConditionVariableTest, StartupShutdownTest) { + Lock lock; + + // First try trivial startup/shutdown. + { + ConditionVariable cv1(&lock); + } // Call for cv1 destruction. + + // Exercise with at least a few waits. + ConditionVariable cv(&lock); + + lock.Acquire(); + cv.TimedWait(kTenMs); // Wait for 10 ms. + cv.TimedWait(kTenMs); // Wait for 10 ms. + lock.Release(); + + lock.Acquire(); + cv.TimedWait(kTenMs); // Wait for 10 ms. + cv.TimedWait(kTenMs); // Wait for 10 ms. + cv.TimedWait(kTenMs); // Wait for 10 ms. + lock.Release(); +} // Call for cv destruction. + +TEST_F(ConditionVariableTest, TimeoutTest) { + Lock lock; + ConditionVariable cv(&lock); + lock.Acquire(); + + TimeTicks start = TimeTicks::Now(); + const TimeDelta WAIT_TIME = TimeDelta::FromMilliseconds(300); + // Allow for clocking rate granularity. + const TimeDelta FUDGE_TIME = TimeDelta::FromMilliseconds(50); + + cv.TimedWait(WAIT_TIME + FUDGE_TIME); + TimeDelta duration = TimeTicks::Now() - start; + // We can't use EXPECT_GE here as the TimeDelta class does not support the + // required stream conversion. + EXPECT_TRUE(duration >= WAIT_TIME); + + lock.Release(); +} + + +// Suddenly got flaky on Win, see http://crbug.com/10607 (starting at +// comment #15) +#if defined(OS_WIN) +#define MAYBE_MultiThreadConsumerTest DISABLED_MultiThreadConsumerTest +#else +#define MAYBE_MultiThreadConsumerTest MultiThreadConsumerTest +#endif +// Test serial task servicing, as well as two parallel task servicing methods. +TEST_F(ConditionVariableTest, MAYBE_MultiThreadConsumerTest) { + const int kThreadCount = 10; + WorkQueue queue(kThreadCount); // Start the threads. + + const int kTaskCount = 10; // Number of tasks in each mini-test here. + + Time start_time; // Used to time task processing. + + { + base::AutoLock auto_lock(*queue.lock()); + while (!queue.EveryIdWasAllocated()) + queue.all_threads_have_ids()->Wait(); + } + + // If threads aren't in a wait state, they may start to gobble up tasks in + // parallel, short-circuiting (breaking) this test. + queue.SpinUntilAllThreadsAreWaiting(); + + { + // Since we have no tasks yet, all threads should be waiting by now. + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(0, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(0, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(0, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetNumberOfCompletedTasks()); + + // Set up to make each task include getting help from another worker, so + // so that the work gets done in paralell. + queue.ResetHistory(); + queue.SetTaskCount(kTaskCount); + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(true); + + start_time = Time::Now(); + } + + queue.work_is_available()->Signal(); // But each worker can signal another. + // Wait till we at least start to handle tasks (and we're not all waiting). + queue.SpinUntilTaskCountLessThan(kTaskCount); + // Wait to allow the all workers to get done. + queue.SpinUntilAllThreadsAreWaiting(); + + { + // Wait until all work tasks have at least been assigned. + base::AutoLock auto_lock(*queue.lock()); + while (queue.task_count()) + queue.no_more_tasks()->Wait(); + + // To avoid racy assumptions, we'll just assert that at least 2 threads + // did work. We know that the first worker should have gone to sleep, and + // hence a second worker should have gotten an assignment. + EXPECT_LE(2, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(kTaskCount, queue.GetNumberOfCompletedTasks()); + + // Try to ask all workers to help, and only a few will do the work. + queue.ResetHistory(); + queue.SetTaskCount(3); + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(false); + } + queue.work_is_available()->Broadcast(); // Make them all try. + // Wait till we at least start to handle tasks (and we're not all waiting). + queue.SpinUntilTaskCountLessThan(3); + // Wait to allow the 3 workers to get done. + queue.SpinUntilAllThreadsAreWaiting(); + + { + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(3, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(3, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(1, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread()); + EXPECT_EQ(3, queue.GetNumberOfCompletedTasks()); + + // Set up to make each task get help from another worker. + queue.ResetHistory(); + queue.SetTaskCount(3); + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(true); // Allow (unnecessary) help requests. + } + queue.work_is_available()->Broadcast(); // Signal all threads. + // Wait till we at least start to handle tasks (and we're not all waiting). + queue.SpinUntilTaskCountLessThan(3); + // Wait to allow the 3 workers to get done. + queue.SpinUntilAllThreadsAreWaiting(); + + { + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(3, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(3, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(1, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread()); + EXPECT_EQ(3, queue.GetNumberOfCompletedTasks()); + + // Set up to make each task get help from another worker. + queue.ResetHistory(); + queue.SetTaskCount(20); // 2 tasks per thread. + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(true); + } + queue.work_is_available()->Signal(); // But each worker can signal another. + // Wait till we at least start to handle tasks (and we're not all waiting). + queue.SpinUntilTaskCountLessThan(20); + // Wait to allow the 10 workers to get done. + queue.SpinUntilAllThreadsAreWaiting(); // Should take about 60 ms. + + { + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(10, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(10, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(20, queue.GetNumberOfCompletedTasks()); + + // Same as last test, but with Broadcast(). + queue.ResetHistory(); + queue.SetTaskCount(20); // 2 tasks per thread. + queue.SetWorkTime(kThirtyMs); + queue.SetAllowHelp(true); + } + queue.work_is_available()->Broadcast(); + // Wait till we at least start to handle tasks (and we're not all waiting). + queue.SpinUntilTaskCountLessThan(20); + // Wait to allow the 10 workers to get done. + queue.SpinUntilAllThreadsAreWaiting(); // Should take about 60 ms. + + { + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(10, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(10, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(20, queue.GetNumberOfCompletedTasks()); + + queue.SetShutdown(); + } + queue.work_is_available()->Broadcast(); // Force check for shutdown. + + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(1), + queue.ThreadSafeCheckShutdown(kThreadCount)); +} + +TEST_F(ConditionVariableTest, LargeFastTaskTest) { +#if defined(__LB_SHELL__) && !defined(__LB_LINUX__) + // Game consoles don't support that many threads. We have a max threads + // macro in pthread.h. Leave a few to system and test with the rest. + const int kThreadCount = SHELL_MAX_THREADS - 8; +#elif defined(OS_STARBOARD) + const int kThreadCount = SB_MAX_THREADS - 8; +#else + const int kThreadCount = 200; +#endif + WorkQueue queue(kThreadCount); // Start the threads. + + Lock private_lock; // Used locally for master to wait. + base::AutoLock private_held_lock(private_lock); + ConditionVariable private_cv(&private_lock); + + { + base::AutoLock auto_lock(*queue.lock()); + while (!queue.EveryIdWasAllocated()) + queue.all_threads_have_ids()->Wait(); + } + + // Wait a bit more to allow threads to reach their wait state. + queue.SpinUntilAllThreadsAreWaiting(); + + { + // Since we have no tasks, all threads should be waiting by now. + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(0, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(0, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_EQ(0, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread()); + EXPECT_EQ(0, queue.GetNumberOfCompletedTasks()); + + // Set up to make all workers do (an average of) 20 tasks. + queue.ResetHistory(); + queue.SetTaskCount(20 * kThreadCount); + queue.SetWorkTime(kFortyFiveMs); + queue.SetAllowHelp(false); + } + queue.work_is_available()->Broadcast(); // Start up all threads. + // Wait until we've handed out all tasks. + { + base::AutoLock auto_lock(*queue.lock()); + while (queue.task_count() != 0) + queue.no_more_tasks()->Wait(); + } + + // Wait till the last of the tasks complete. + queue.SpinUntilAllThreadsAreWaiting(); + + { + // With Broadcast(), every thread should have participated. + // but with racing.. they may not all have done equal numbers of tasks. + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(kThreadCount, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(kThreadCount, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_LE(20, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(20 * kThreadCount, queue.GetNumberOfCompletedTasks()); + + // Set up to make all workers do (an average of) 4 tasks. + queue.ResetHistory(); + queue.SetTaskCount(kThreadCount * 4); + queue.SetWorkTime(kFortyFiveMs); + queue.SetAllowHelp(true); // Might outperform Broadcast(). + } + queue.work_is_available()->Signal(); // Start up one thread. + + // Wait until we've handed out all tasks + { + base::AutoLock auto_lock(*queue.lock()); + while (queue.task_count() != 0) + queue.no_more_tasks()->Wait(); + } + + // Wait till the last of the tasks complete. + queue.SpinUntilAllThreadsAreWaiting(); + + { + // With Signal(), every thread should have participated. + // but with racing.. they may not all have done four tasks. + base::AutoLock auto_lock(*queue.lock()); + EXPECT_EQ(kThreadCount, queue.GetNumThreadsTakingAssignments()); + EXPECT_EQ(kThreadCount, queue.GetNumThreadsCompletingTasks()); + EXPECT_EQ(0, queue.task_count()); + EXPECT_LE(4, queue.GetMaxCompletionsByWorkerThread()); + EXPECT_EQ(4 * kThreadCount, queue.GetNumberOfCompletedTasks()); + + queue.SetShutdown(); + } + queue.work_is_available()->Broadcast(); // Force check for shutdown. + + // Wait for shutdowns to complete. + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(1), + queue.ThreadSafeCheckShutdown(kThreadCount)); +} + +//------------------------------------------------------------------------------ +// Finally we provide the implementation for the methods in the WorkQueue class. +//------------------------------------------------------------------------------ + +WorkQueue::WorkQueue(int thread_count) + : lock_(), + work_is_available_(&lock_), + all_threads_have_ids_(&lock_), + no_more_tasks_(&lock_), + thread_count_(thread_count), + waiting_thread_count_(0), + thread_handles_(new PlatformThreadHandle[thread_count]), + assignment_history_(thread_count), + completion_history_(thread_count), + thread_started_counter_(0), + shutdown_task_count_(0), + task_count_(0), + allow_help_requests_(false), + shutdown_(false) { + EXPECT_GE(thread_count_, 1); + ResetHistory(); + SetTaskCount(0); + SetWorkTime(TimeDelta::FromMilliseconds(30)); + + for (int i = 0; i < thread_count_; ++i) { + PlatformThreadHandle pth; + EXPECT_TRUE(PlatformThread::Create(0, this, &pth)); + thread_handles_[i] = pth; + } +} + +WorkQueue::~WorkQueue() { + { + base::AutoLock auto_lock(lock_); + SetShutdown(); + } + work_is_available_.Broadcast(); // Tell them all to terminate. + + for (int i = 0; i < thread_count_; ++i) { + PlatformThread::Join(thread_handles_[i]); + } + EXPECT_EQ(0, waiting_thread_count_); +} + +int WorkQueue::GetThreadId() { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + DCHECK(!EveryIdWasAllocated()); + return thread_started_counter_++; // Give out Unique IDs. +} + +bool WorkQueue::EveryIdWasAllocated() const { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + return thread_count_ == thread_started_counter_; +} + +TimeDelta WorkQueue::GetAnAssignment(int thread_id) { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + DCHECK_LT(0, task_count_); + assignment_history_[thread_id]++; + if (0 == --task_count_) { + no_more_tasks_.Signal(); + } + return worker_delay_; +} + +void WorkQueue::WorkIsCompleted(int thread_id) { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + completion_history_[thread_id]++; +} + +int WorkQueue::task_count() const { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + return task_count_; +} + +bool WorkQueue::allow_help_requests() const { + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + return allow_help_requests_; +} + +bool WorkQueue::shutdown() const { + lock_.AssertAcquired(); + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + return shutdown_; +} + +// Because this method is called from the test's main thread we need to actually +// take the lock. Threads will call the thread_shutting_down() method with the +// lock already acquired. +bool WorkQueue::ThreadSafeCheckShutdown(int thread_count) { + bool all_shutdown; + base::AutoLock auto_lock(lock_); + { + // Declare in scope so DFAKE is guranteed to be destroyed before AutoLock. + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + all_shutdown = (shutdown_task_count_ == thread_count); + } + return all_shutdown; +} + +void WorkQueue::thread_shutting_down() { + lock_.AssertAcquired(); + DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_); + shutdown_task_count_++; +} + +Lock* WorkQueue::lock() { + return &lock_; +} + +ConditionVariable* WorkQueue::work_is_available() { + return &work_is_available_; +} + +ConditionVariable* WorkQueue::all_threads_have_ids() { + return &all_threads_have_ids_; +} + +ConditionVariable* WorkQueue::no_more_tasks() { + return &no_more_tasks_; +} + +void WorkQueue::ResetHistory() { + for (int i = 0; i < thread_count_; ++i) { + assignment_history_[i] = 0; + completion_history_[i] = 0; + } +} + +int WorkQueue::GetMinCompletionsByWorkerThread() const { + int minumum = completion_history_[0]; + for (int i = 0; i < thread_count_; ++i) + minumum = std::min(minumum, completion_history_[i]); + return minumum; +} + +int WorkQueue::GetMaxCompletionsByWorkerThread() const { + int maximum = completion_history_[0]; + for (int i = 0; i < thread_count_; ++i) + maximum = std::max(maximum, completion_history_[i]); + return maximum; +} + +int WorkQueue::GetNumThreadsTakingAssignments() const { + int count = 0; + for (int i = 0; i < thread_count_; ++i) + if (assignment_history_[i]) + count++; + return count; +} + +int WorkQueue::GetNumThreadsCompletingTasks() const { + int count = 0; + for (int i = 0; i < thread_count_; ++i) + if (completion_history_[i]) + count++; + return count; +} + +int WorkQueue::GetNumberOfCompletedTasks() const { + int total = 0; + for (int i = 0; i < thread_count_; ++i) + total += completion_history_[i]; + return total; +} + +TimeDelta WorkQueue::GetWorkTime() const { + return worker_delay_; +} + +void WorkQueue::SetWorkTime(TimeDelta delay) { + worker_delay_ = delay; +} + +void WorkQueue::SetTaskCount(int count) { + task_count_ = count; +} + +void WorkQueue::SetAllowHelp(bool allow) { + allow_help_requests_ = allow; +} + +void WorkQueue::SetShutdown() { + lock_.AssertAcquired(); + shutdown_ = true; +} + +void WorkQueue::SpinUntilAllThreadsAreWaiting() { + while (true) { + { + base::AutoLock auto_lock(lock_); + if (waiting_thread_count_ == thread_count_) + break; + } + PlatformThread::Sleep(TimeDelta::FromMilliseconds(30)); + } +} + +void WorkQueue::SpinUntilTaskCountLessThan(int task_count) { + while (true) { + { + base::AutoLock auto_lock(lock_); + if (task_count_ < task_count) + break; + } + PlatformThread::Sleep(TimeDelta::FromMilliseconds(30)); + } +} + + +//------------------------------------------------------------------------------ +// Define the standard worker task. Several tests will spin out many of these +// threads. +//------------------------------------------------------------------------------ + +// The multithread tests involve several threads with a task to perform as +// directed by an instance of the class WorkQueue. +// The task is to: +// a) Check to see if there are more tasks (there is a task counter). +// a1) Wait on condition variable if there are no tasks currently. +// b) Call a function to see what should be done. +// c) Do some computation based on the number of milliseconds returned in (b). +// d) go back to (a). + +// WorkQueue::ThreadMain() implements the above task for all threads. +// It calls the controlling object to tell the creator about progress, and to +// ask about tasks. + +void WorkQueue::ThreadMain() { + int thread_id; + { + base::AutoLock auto_lock(lock_); + thread_id = GetThreadId(); + if (EveryIdWasAllocated()) + all_threads_have_ids()->Signal(); // Tell creator we're ready. + } + + Lock private_lock; // Used to waste time on "our work". + while (1) { // This is the main consumer loop. + TimeDelta work_time; + bool could_use_help; + { + base::AutoLock auto_lock(lock_); + while (0 == task_count() && !shutdown()) { + ++waiting_thread_count_; + work_is_available()->Wait(); + --waiting_thread_count_; + } + if (shutdown()) { + // Ack the notification of a shutdown message back to the controller. + thread_shutting_down(); + return; // Terminate. + } + // Get our task duration from the queue. + work_time = GetAnAssignment(thread_id); + could_use_help = (task_count() > 0) && allow_help_requests(); + } // Release lock + + // Do work (outside of locked region. + if (could_use_help) + work_is_available()->Signal(); // Get help from other threads. + + if (work_time > TimeDelta::FromMilliseconds(0)) { + // We could just sleep(), but we'll instead further exercise the + // condition variable class, and do a timed wait. + base::AutoLock auto_lock(private_lock); + ConditionVariable private_cv(&private_lock); + private_cv.TimedWait(work_time); // Unsynchronized waiting. + } + + { + base::AutoLock auto_lock(lock_); + // Send notification that we completed our "work." + WorkIsCompleted(thread_id); + } + } +} + +} // namespace + +} // namespace base
diff --git a/src/base/synchronization/condition_variable_win.cc b/src/base/synchronization/condition_variable_win.cc new file mode 100644 index 0000000..f32778f --- /dev/null +++ b/src/base/synchronization/condition_variable_win.cc
@@ -0,0 +1,669 @@ +// 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/synchronization/condition_variable.h" + +#include <windows.h> +#include <stack> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_restrictions.h" +#include "base/time.h" + +namespace { +// We can't use the linker supported delay-load for kernel32 so all this +// cruft here is to manually late-bind the needed functions. +typedef void (WINAPI *InitializeConditionVariableFn)(PCONDITION_VARIABLE); +typedef BOOL (WINAPI *SleepConditionVariableCSFn)(PCONDITION_VARIABLE, + PCRITICAL_SECTION, DWORD); +typedef void (WINAPI *WakeConditionVariableFn)(PCONDITION_VARIABLE); +typedef void (WINAPI *WakeAllConditionVariableFn)(PCONDITION_VARIABLE); + +InitializeConditionVariableFn initialize_condition_variable_fn; +SleepConditionVariableCSFn sleep_condition_variable_fn; +WakeConditionVariableFn wake_condition_variable_fn; +WakeAllConditionVariableFn wake_all_condition_variable_fn; + +bool BindVistaCondVarFunctions() { + HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); + initialize_condition_variable_fn = + reinterpret_cast<InitializeConditionVariableFn>( + GetProcAddress(kernel32, "InitializeConditionVariable")); + if (!initialize_condition_variable_fn) + return false; + sleep_condition_variable_fn = + reinterpret_cast<SleepConditionVariableCSFn>( + GetProcAddress(kernel32, "SleepConditionVariableCS")); + if (!sleep_condition_variable_fn) + return false; + wake_condition_variable_fn = + reinterpret_cast<WakeConditionVariableFn>( + GetProcAddress(kernel32, "WakeConditionVariable")); + if (!wake_condition_variable_fn) + return false; + wake_all_condition_variable_fn = + reinterpret_cast<WakeAllConditionVariableFn>( + GetProcAddress(kernel32, "WakeAllConditionVariable")); + if (!wake_all_condition_variable_fn) + return false; + return true; +} + +} // namespace. + +namespace base { +// Abstract base class of the pimpl idiom. +class ConditionVarImpl { + public: + virtual ~ConditionVarImpl() {}; + virtual void Wait() = 0; + virtual void TimedWait(const TimeDelta& max_time) = 0; + virtual void Broadcast() = 0; + virtual void Signal() = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Windows Vista and Win7 implementation. +/////////////////////////////////////////////////////////////////////////////// + +class WinVistaCondVar: public ConditionVarImpl { + public: + WinVistaCondVar(Lock* user_lock); + ~WinVistaCondVar() {}; + // Overridden from ConditionVarImpl. + virtual void Wait() OVERRIDE; + virtual void TimedWait(const TimeDelta& max_time) OVERRIDE; + virtual void Broadcast() OVERRIDE; + virtual void Signal() OVERRIDE; + + private: + base::Lock& user_lock_; + CONDITION_VARIABLE cv_; +}; + +WinVistaCondVar::WinVistaCondVar(Lock* user_lock) + : user_lock_(*user_lock) { + initialize_condition_variable_fn(&cv_); + DCHECK(user_lock); +} + +void WinVistaCondVar::Wait() { + TimedWait(TimeDelta::FromMilliseconds(INFINITE)); +} + +void WinVistaCondVar::TimedWait(const TimeDelta& max_time) { + base::ThreadRestrictions::AssertWaitAllowed(); + DWORD timeout = static_cast<DWORD>(max_time.InMilliseconds()); + CRITICAL_SECTION* cs = user_lock_.lock_.os_lock(); + +#if !defined(NDEBUG) + user_lock_.CheckHeldAndUnmark(); +#endif + + if (FALSE == sleep_condition_variable_fn(&cv_, cs, timeout)) { + DCHECK(GetLastError() != WAIT_TIMEOUT); + } + +#if !defined(NDEBUG) + user_lock_.CheckUnheldAndMark(); +#endif +} + +void WinVistaCondVar::Broadcast() { + wake_all_condition_variable_fn(&cv_); +} + +void WinVistaCondVar::Signal() { + wake_condition_variable_fn(&cv_); +} + +/////////////////////////////////////////////////////////////////////////////// +// Windows XP implementation. +/////////////////////////////////////////////////////////////////////////////// + +class WinXPCondVar : public ConditionVarImpl { + public: + WinXPCondVar(Lock* user_lock); + ~WinXPCondVar(); + // Overridden from ConditionVarImpl. + virtual void Wait() OVERRIDE; + virtual void TimedWait(const TimeDelta& max_time) OVERRIDE; + virtual void Broadcast() OVERRIDE; + virtual void Signal() OVERRIDE; + + // Define Event class that is used to form circularly linked lists. + // The list container is an element with NULL as its handle_ value. + // The actual list elements have a non-zero handle_ value. + // All calls to methods MUST be done under protection of a lock so that links + // can be validated. Without the lock, some links might asynchronously + // change, and the assertions would fail (as would list change operations). + class Event { + public: + // Default constructor with no arguments creates a list container. + Event(); + ~Event(); + + // InitListElement transitions an instance from a container, to an element. + void InitListElement(); + + // Methods for use on lists. + bool IsEmpty() const; + void PushBack(Event* other); + Event* PopFront(); + Event* PopBack(); + + // Methods for use on list elements. + // Accessor method. + HANDLE handle() const; + // Pull an element from a list (if it's in one). + Event* Extract(); + + // Method for use on a list element or on a list. + bool IsSingleton() const; + + private: + // Provide pre/post conditions to validate correct manipulations. + bool ValidateAsDistinct(Event* other) const; + bool ValidateAsItem() const; + bool ValidateAsList() const; + bool ValidateLinks() const; + + HANDLE handle_; + Event* next_; + Event* prev_; + DISALLOW_COPY_AND_ASSIGN(Event); + }; + + // Note that RUNNING is an unlikely number to have in RAM by accident. + // This helps with defensive destructor coding in the face of user error. + enum RunState { SHUTDOWN = 0, RUNNING = 64213 }; + + // Internal implementation methods supporting Wait(). + Event* GetEventForWaiting(); + void RecycleEvent(Event* used_event); + + RunState run_state_; + + // Private critical section for access to member data. + base::Lock internal_lock_; + + // Lock that is acquired before calling Wait(). + base::Lock& user_lock_; + + // Events that threads are blocked on. + Event waiting_list_; + + // Free list for old events. + Event recycling_list_; + int recycling_list_size_; + + // The number of allocated, but not yet deleted events. + int allocation_counter_; +}; + +WinXPCondVar::WinXPCondVar(Lock* user_lock) + : user_lock_(*user_lock), + run_state_(RUNNING), + allocation_counter_(0), + recycling_list_size_(0) { + DCHECK(user_lock); +} + +WinXPCondVar::~WinXPCondVar() { + AutoLock auto_lock(internal_lock_); + run_state_ = SHUTDOWN; // Prevent any more waiting. + + DCHECK_EQ(recycling_list_size_, allocation_counter_); + if (recycling_list_size_ != allocation_counter_) { // Rare shutdown problem. + // There are threads of execution still in this->TimedWait() and yet the + // caller has instigated the destruction of this instance :-/. + // A common reason for such "overly hasty" destruction is that the caller + // was not willing to wait for all the threads to terminate. Such hasty + // actions are a violation of our usage contract, but we'll give the + // waiting thread(s) one last chance to exit gracefully (prior to our + // destruction). + // Note: waiting_list_ *might* be empty, but recycling is still pending. + AutoUnlock auto_unlock(internal_lock_); + Broadcast(); // Make sure all waiting threads have been signaled. + Sleep(10); // Give threads a chance to grab internal_lock_. + // All contained threads should be blocked on user_lock_ by now :-). + } // Reacquire internal_lock_. + + DCHECK_EQ(recycling_list_size_, allocation_counter_); +} + +void WinXPCondVar::Wait() { + // Default to "wait forever" timing, which means have to get a Signal() + // or Broadcast() to come out of this wait state. + TimedWait(TimeDelta::FromMilliseconds(INFINITE)); +} + +void WinXPCondVar::TimedWait(const TimeDelta& max_time) { + base::ThreadRestrictions::AssertWaitAllowed(); + Event* waiting_event; + HANDLE handle; + { + AutoLock auto_lock(internal_lock_); + if (RUNNING != run_state_) return; // Destruction in progress. + waiting_event = GetEventForWaiting(); + handle = waiting_event->handle(); + DCHECK(handle); + } // Release internal_lock. + + { + AutoUnlock unlock(user_lock_); // Release caller's lock + WaitForSingleObject(handle, static_cast<DWORD>(max_time.InMilliseconds())); + // Minimize spurious signal creation window by recycling asap. + AutoLock auto_lock(internal_lock_); + RecycleEvent(waiting_event); + // Release internal_lock_ + } // Reacquire callers lock to depth at entry. +} + +// Broadcast() is guaranteed to signal all threads that were waiting (i.e., had +// a cv_event internally allocated for them) before Broadcast() was called. +void WinXPCondVar::Broadcast() { + std::stack<HANDLE> handles; // See FAQ-question-10. + { + AutoLock auto_lock(internal_lock_); + if (waiting_list_.IsEmpty()) + return; + while (!waiting_list_.IsEmpty()) + // This is not a leak from waiting_list_. See FAQ-question 12. + handles.push(waiting_list_.PopBack()->handle()); + } // Release internal_lock_. + while (!handles.empty()) { + SetEvent(handles.top()); + handles.pop(); + } +} + +// Signal() will select one of the waiting threads, and signal it (signal its +// cv_event). For better performance we signal the thread that went to sleep +// most recently (LIFO). If we want fairness, then we wake the thread that has +// been sleeping the longest (FIFO). +void WinXPCondVar::Signal() { + HANDLE handle; + { + AutoLock auto_lock(internal_lock_); + if (waiting_list_.IsEmpty()) + return; // No one to signal. + // Only performance option should be used. + // This is not a leak from waiting_list. See FAQ-question 12. + handle = waiting_list_.PopBack()->handle(); // LIFO. + } // Release internal_lock_. + SetEvent(handle); +} + +// GetEventForWaiting() provides a unique cv_event for any caller that needs to +// wait. This means that (worst case) we may over time create as many cv_event +// objects as there are threads simultaneously using this instance's Wait() +// functionality. +WinXPCondVar::Event* WinXPCondVar::GetEventForWaiting() { + // We hold internal_lock, courtesy of Wait(). + Event* cv_event; + if (0 == recycling_list_size_) { + DCHECK(recycling_list_.IsEmpty()); + cv_event = new Event(); + cv_event->InitListElement(); + allocation_counter_++; + DCHECK(cv_event->handle()); + } else { + cv_event = recycling_list_.PopFront(); + recycling_list_size_--; + } + waiting_list_.PushBack(cv_event); + return cv_event; +} + +// RecycleEvent() takes a cv_event that was previously used for Wait()ing, and +// recycles it for use in future Wait() calls for this or other threads. +// Note that there is a tiny chance that the cv_event is still signaled when we +// obtain it, and that can cause spurious signals (if/when we re-use the +// cv_event), but such is quite rare (see FAQ-question-5). +void WinXPCondVar::RecycleEvent(Event* used_event) { + // We hold internal_lock, courtesy of Wait(). + // If the cv_event timed out, then it is necessary to remove it from + // waiting_list_. If it was selected by Broadcast() or Signal(), then it is + // already gone. + used_event->Extract(); // Possibly redundant + recycling_list_.PushBack(used_event); + recycling_list_size_++; +} +//------------------------------------------------------------------------------ +// The next section provides the implementation for the private Event class. +//------------------------------------------------------------------------------ + +// Event provides a doubly-linked-list of events for use exclusively by the +// ConditionVariable class. + +// This custom container was crafted because no simple combination of STL +// classes appeared to support the functionality required. The specific +// unusual requirement for a linked-list-class is support for the Extract() +// method, which can remove an element from a list, potentially for insertion +// into a second list. Most critically, the Extract() method is idempotent, +// turning the indicated element into an extracted singleton whether it was +// contained in a list or not. This functionality allows one (or more) of +// threads to do the extraction. The iterator that identifies this extractable +// element (in this case, a pointer to the list element) can be used after +// arbitrary manipulation of the (possibly) enclosing list container. In +// general, STL containers do not provide iterators that can be used across +// modifications (insertions/extractions) of the enclosing containers, and +// certainly don't provide iterators that can be used if the identified +// element is *deleted* (removed) from the container. + +// It is possible to use multiple redundant containers, such as an STL list, +// and an STL map, to achieve similar container semantics. This container has +// only O(1) methods, while the corresponding (multiple) STL container approach +// would have more complex O(log(N)) methods (yeah... N isn't that large). +// Multiple containers also makes correctness more difficult to assert, as +// data is redundantly stored and maintained, which is generally evil. + +WinXPCondVar::Event::Event() : handle_(0) { + next_ = prev_ = this; // Self referencing circular. +} + +WinXPCondVar::Event::~Event() { + if (0 == handle_) { + // This is the list holder + while (!IsEmpty()) { + Event* cv_event = PopFront(); + DCHECK(cv_event->ValidateAsItem()); + delete cv_event; + } + } + DCHECK(IsSingleton()); + if (0 != handle_) { + int ret_val = CloseHandle(handle_); + DCHECK(ret_val); + } +} + +// Change a container instance permanently into an element of a list. +void WinXPCondVar::Event::InitListElement() { + DCHECK(!handle_); + handle_ = CreateEvent(NULL, false, false, NULL); + DCHECK(handle_); +} + +// Methods for use on lists. +bool WinXPCondVar::Event::IsEmpty() const { + DCHECK(ValidateAsList()); + return IsSingleton(); +} + +void WinXPCondVar::Event::PushBack(Event* other) { + DCHECK(ValidateAsList()); + DCHECK(other->ValidateAsItem()); + DCHECK(other->IsSingleton()); + // Prepare other for insertion. + other->prev_ = prev_; + other->next_ = this; + // Cut into list. + prev_->next_ = other; + prev_ = other; + DCHECK(ValidateAsDistinct(other)); +} + +WinXPCondVar::Event* WinXPCondVar::Event::PopFront() { + DCHECK(ValidateAsList()); + DCHECK(!IsSingleton()); + return next_->Extract(); +} + +WinXPCondVar::Event* WinXPCondVar::Event::PopBack() { + DCHECK(ValidateAsList()); + DCHECK(!IsSingleton()); + return prev_->Extract(); +} + +// Methods for use on list elements. +// Accessor method. +HANDLE WinXPCondVar::Event::handle() const { + DCHECK(ValidateAsItem()); + return handle_; +} + +// Pull an element from a list (if it's in one). +WinXPCondVar::Event* WinXPCondVar::Event::Extract() { + DCHECK(ValidateAsItem()); + if (!IsSingleton()) { + // Stitch neighbors together. + next_->prev_ = prev_; + prev_->next_ = next_; + // Make extractee into a singleton. + prev_ = next_ = this; + } + DCHECK(IsSingleton()); + return this; +} + +// Method for use on a list element or on a list. +bool WinXPCondVar::Event::IsSingleton() const { + DCHECK(ValidateLinks()); + return next_ == this; +} + +// Provide pre/post conditions to validate correct manipulations. +bool WinXPCondVar::Event::ValidateAsDistinct(Event* other) const { + return ValidateLinks() && other->ValidateLinks() && (this != other); +} + +bool WinXPCondVar::Event::ValidateAsItem() const { + return (0 != handle_) && ValidateLinks(); +} + +bool WinXPCondVar::Event::ValidateAsList() const { + return (0 == handle_) && ValidateLinks(); +} + +bool WinXPCondVar::Event::ValidateLinks() const { + // Make sure both of our neighbors have links that point back to us. + // We don't do the O(n) check and traverse the whole loop, and instead only + // do a local check to (and returning from) our immediate neighbors. + return (next_->prev_ == this) && (prev_->next_ == this); +} + + +/* +FAQ On WinXPCondVar subtle implementation details: + +1) What makes this problem subtle? Please take a look at "Strategies +for Implementing POSIX Condition Variables on Win32" by Douglas +C. Schmidt and Irfan Pyarali. +http://www.cs.wustl.edu/~schmidt/win32-cv-1.html It includes +discussions of numerous flawed strategies for implementing this +functionality. I'm not convinced that even the final proposed +implementation has semantics that are as nice as this implementation +(especially with regard to Broadcast() and the impact on threads that +try to Wait() after a Broadcast() has been called, but before all the +original waiting threads have been signaled). + +2) Why can't you use a single wait_event for all threads that call +Wait()? See FAQ-question-1, or consider the following: If a single +event were used, then numerous threads calling Wait() could release +their cs locks, and be preempted just before calling +WaitForSingleObject(). If a call to Broadcast() was then presented on +a second thread, it would be impossible to actually signal all +waiting(?) threads. Some number of SetEvent() calls *could* be made, +but there could be no guarantee that those led to to more than one +signaled thread (SetEvent()'s may be discarded after the first!), and +there could be no guarantee that the SetEvent() calls didn't just +awaken "other" threads that hadn't even started waiting yet (oops). +Without any limit on the number of requisite SetEvent() calls, the +system would be forced to do many such calls, allowing many new waits +to receive spurious signals. + +3) How does this implementation cause spurious signal events? The +cause in this implementation involves a race between a signal via +time-out and a signal via Signal() or Broadcast(). The series of +actions leading to this are: + +a) Timer fires, and a waiting thread exits the line of code: + + WaitForSingleObject(waiting_event, max_time.InMilliseconds()); + +b) That thread (in (a)) is randomly pre-empted after the above line, +leaving the waiting_event reset (unsignaled) and still in the +waiting_list_. + +c) A call to Signal() (or Broadcast()) on a second thread proceeds, and +selects the waiting cv_event (identified in step (b)) as the event to revive +via a call to SetEvent(). + +d) The Signal() method (step c) calls SetEvent() on waiting_event (step b). + +e) The waiting cv_event (step b) is now signaled, but no thread is +waiting on it. + +f) When that waiting_event (step b) is reused, it will immediately +be signaled (spuriously). + + +4) Why do you recycle events, and cause spurious signals? First off, +the spurious events are very rare. They can only (I think) appear +when the race described in FAQ-question-3 takes place. This should be +very rare. Most(?) uses will involve only timer expiration, or only +Signal/Broadcast() actions. When both are used, it will be rare that +the race will appear, and it would require MANY Wait() and signaling +activities. If this implementation did not recycle events, then it +would have to create and destroy events for every call to Wait(). +That allocation/deallocation and associated construction/destruction +would be costly (per wait), and would only be a rare benefit (when the +race was "lost" and a spurious signal took place). That would be bad +(IMO) optimization trade-off. Finally, such spurious events are +allowed by the specification of condition variables (such as +implemented in Vista), and hence it is better if any user accommodates +such spurious events (see usage note in condition_variable.h). + +5) Why don't you reset events when you are about to recycle them, or +about to reuse them, so that the spurious signals don't take place? +The thread described in FAQ-question-3 step c may be pre-empted for an +arbitrary length of time before proceeding to step d. As a result, +the wait_event may actually be re-used *before* step (e) is reached. +As a result, calling reset would not help significantly. + +6) How is it that the callers lock is released atomically with the +entry into a wait state? We commit to the wait activity when we +allocate the wait_event for use in a given call to Wait(). This +allocation takes place before the caller's lock is released (and +actually before our internal_lock_ is released). That allocation is +the defining moment when "the wait state has been entered," as that +thread *can* now be signaled by a call to Broadcast() or Signal(). +Hence we actually "commit to wait" before releasing the lock, making +the pair effectively atomic. + +8) Why do you need to lock your data structures during waiting, as the +caller is already in possession of a lock? We need to Acquire() and +Release() our internal lock during Signal() and Broadcast(). If we tried +to use a callers lock for this purpose, we might conflict with their +external use of the lock. For example, the caller may use to consistently +hold a lock on one thread while calling Signal() on another, and that would +block Signal(). + +9) Couldn't a more efficient implementation be provided if you +preclude using more than one external lock in conjunction with a +single ConditionVariable instance? Yes, at least it could be viewed +as a simpler API (since you don't have to reiterate the lock argument +in each Wait() call). One of the constructors now takes a specific +lock as an argument, and a there are corresponding Wait() calls that +don't specify a lock now. It turns that the resulting implmentation +can't be made more efficient, as the internal lock needs to be used by +Signal() and Broadcast(), to access internal data structures. As a +result, I was not able to utilize the user supplied lock (which is +being used by the user elsewhere presumably) to protect the private +member access. + +9) Since you have a second lock, how can be be sure that there is no +possible deadlock scenario? Our internal_lock_ is always the last +lock acquired, and the first one released, and hence a deadlock (due +to critical section problems) is impossible as a consequence of our +lock. + +10) When doing a Broadcast(), why did you copy all the events into +an STL queue, rather than making a linked-loop, and iterating over it? +The iterating during Broadcast() is done so outside the protection +of the internal lock. As a result, other threads, such as the thread +wherein a related event is waiting, could asynchronously manipulate +the links around a cv_event. As a result, the link structure cannot +be used outside a lock. Broadcast() could iterate over waiting +events by cycling in-and-out of the protection of the internal_lock, +but that appears more expensive than copying the list into an STL +stack. + +11) Why did the lock.h file need to be modified so much for this +change? Central to a Condition Variable is the atomic release of a +lock during a Wait(). This places Wait() functionality exactly +mid-way between the two classes, Lock and Condition Variable. Given +that there can be nested Acquire()'s of locks, and Wait() had to +Release() completely a held lock, it was necessary to augment the Lock +class with a recursion counter. Even more subtle is the fact that the +recursion counter (in a Lock) must be protected, as many threads can +access it asynchronously. As a positive fallout of this, there are +now some DCHECKS to be sure no one Release()s a Lock more than they +Acquire()ed it, and there is ifdef'ed functionality that can detect +nested locks (legal under windows, but not under Posix). + +12) Why is it that the cv_events removed from list in Broadcast() and Signal() +are not leaked? How are they recovered?? The cv_events that appear to leak are +taken from the waiting_list_. For each element in that list, there is currently +a thread in or around the WaitForSingleObject() call of Wait(), and those +threads have references to these otherwise leaked events. They are passed as +arguments to be recycled just aftre returning from WaitForSingleObject(). + +13) Why did you use a custom container class (the linked list), when STL has +perfectly good containers, such as an STL list? The STL list, as with any +container, does not guarantee the utility of an iterator across manipulation +(such as insertions and deletions) of the underlying container. The custom +double-linked-list container provided that assurance. I don't believe any +combination of STL containers provided the services that were needed at the same +O(1) efficiency as the custom linked list. The unusual requirement +for the container class is that a reference to an item within a container (an +iterator) needed to be maintained across an arbitrary manipulation of the +container. This requirement exposes itself in the Wait() method, where a +waiting_event must be selected prior to the WaitForSingleObject(), and then it +must be used as part of recycling to remove the related instance from the +waiting_list. A hash table (STL map) could be used, but I was embarrased to +use a complex and relatively low efficiency container when a doubly linked list +provided O(1) performance in all required operations. Since other operations +to provide performance-and/or-fairness required queue (FIFO) and list (LIFO) +containers, I would also have needed to use an STL list/queue as well as an STL +map. In the end I decided it would be "fun" to just do it right, and I +put so many assertions (DCHECKs) into the container class that it is trivial to +code review and validate its correctness. + +*/ + +ConditionVariable::ConditionVariable(Lock* user_lock) + : impl_(NULL) { + static bool use_vista_native_cv = BindVistaCondVarFunctions(); + if (use_vista_native_cv) + impl_= new WinVistaCondVar(user_lock); + else + impl_ = new WinXPCondVar(user_lock); +} + +ConditionVariable::~ConditionVariable() { + delete impl_; +} + +void ConditionVariable::Wait() { + impl_->Wait(); +} + +void ConditionVariable::TimedWait(const TimeDelta& max_time) { + impl_->TimedWait(max_time); +} + +void ConditionVariable::Broadcast() { + impl_->Broadcast(); +} + +void ConditionVariable::Signal() { + impl_->Signal(); +} + +} // namespace base
diff --git a/src/base/synchronization/lock.cc b/src/base/synchronization/lock.cc new file mode 100644 index 0000000..6445ce8 --- /dev/null +++ b/src/base/synchronization/lock.cc
@@ -0,0 +1,41 @@ +// 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 is used for debugging assertion support. The Lock class +// is functionally a wrapper around the LockImpl class, so the only +// real intelligence in the class is in the debugging logic. + +#if !defined(NDEBUG) + +#include "base/synchronization/lock.h" +#include "base/logging.h" + +namespace base { + +Lock::Lock() : lock_() { + owned_by_thread_ = false; + owning_thread_id_ = static_cast<PlatformThreadId>(0); +} + +void Lock::AssertAcquired() const { + DCHECK(owned_by_thread_); + DCHECK_EQ(owning_thread_id_, PlatformThread::CurrentId()); +} + +void Lock::CheckHeldAndUnmark() { + DCHECK(owned_by_thread_); + DCHECK_EQ(owning_thread_id_, PlatformThread::CurrentId()); + owned_by_thread_ = false; + owning_thread_id_ = static_cast<PlatformThreadId>(0); +} + +void Lock::CheckUnheldAndMark() { + DCHECK(!owned_by_thread_); + owned_by_thread_ = true; + owning_thread_id_ = PlatformThread::CurrentId(); +} + +} // namespace base + +#endif // NDEBUG
diff --git a/src/base/synchronization/lock.h b/src/base/synchronization/lock.h new file mode 100644 index 0000000..dce7fdb --- /dev/null +++ b/src/base/synchronization/lock.h
@@ -0,0 +1,134 @@ +// 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. + +#ifndef BASE_SYNCHRONIZATION_LOCK_H_ +#define BASE_SYNCHRONIZATION_LOCK_H_ + +#include "base/base_export.h" +#include "base/synchronization/lock_impl.h" +#include "base/threading/platform_thread.h" + +namespace base { + +// A convenient wrapper for an OS specific critical section. The only real +// intelligence in this class is in debug mode for the support for the +// AssertAcquired() method. +class BASE_EXPORT Lock { + public: +#if defined(NDEBUG) // Optimized wrapper implementation + Lock() : lock_() {} + ~Lock() {} + void Acquire() { lock_.Lock(); } + void Release() { lock_.Unlock(); } + + // If the lock is not held, take it and return true. If the lock is already + // held by another thread, immediately return false. This must not be called + // by a thread already holding the lock (what happens is undefined and an + // assertion may fail). + bool Try() { return lock_.Try(); } + + // Null implementation if not debug. + void AssertAcquired() const {} +#else + Lock(); + ~Lock() {} + + // NOTE: Although windows critical sections support recursive locks, we do not + // allow this, and we will commonly fire a DCHECK() if a thread attempts to + // acquire the lock a second time (while already holding it). + void Acquire() { + lock_.Lock(); + CheckUnheldAndMark(); + } + void Release() { + CheckHeldAndUnmark(); + lock_.Unlock(); + } + + bool Try() { + bool rv = lock_.Try(); + if (rv) { + CheckUnheldAndMark(); + } + return rv; + } + + void AssertAcquired() const; +#endif // NDEBUG + +#if defined(OS_POSIX) || defined(OS_STARBOARD) + // The posix implementation of ConditionVariable needs to be able + // to see our lock and tweak our debugging counters, as it releases + // and acquires locks inside of pthread_cond_{timed,}wait. + friend class ConditionVariable; +#elif defined(OS_WIN) + // The Windows Vista implementation of ConditionVariable needs the + // native handle of the critical section. + friend class WinVistaCondVar; +#endif + + private: +#if !defined(NDEBUG) + // Members and routines taking care of locks assertions. + // Note that this checks for recursive locks and allows them + // if the variable is set. This is allowed by the underlying implementation + // on windows but not on Posix, so we're doing unneeded checks on Posix. + // It's worth it to share the code. + void CheckHeldAndUnmark(); + void CheckUnheldAndMark(); + + // All private data is implicitly protected by lock_. + // Be VERY careful to only access members under that lock. + + // Determines validity of owning_thread_id_. Needed as we don't have + // a null owning_thread_id_ value. + bool owned_by_thread_; + base::PlatformThreadId owning_thread_id_; +#endif // NDEBUG + + // Platform specific underlying lock implementation. + internal::LockImpl lock_; + + DISALLOW_COPY_AND_ASSIGN(Lock); +}; + +// A helper class that acquires the given Lock while the AutoLock is in scope. +class AutoLock { + public: + explicit AutoLock(Lock& lock) : lock_(lock) { + lock_.Acquire(); + } + + ~AutoLock() { + lock_.AssertAcquired(); + lock_.Release(); + } + + private: + Lock& lock_; + DISALLOW_COPY_AND_ASSIGN(AutoLock); +}; + +// AutoUnlock is a helper that will Release() the |lock| argument in the +// constructor, and re-Acquire() it in the destructor. +class AutoUnlock { + public: + explicit AutoUnlock(Lock& lock) : lock_(lock) { + // We require our caller to have the lock. + lock_.AssertAcquired(); + lock_.Release(); + } + + ~AutoUnlock() { + lock_.Acquire(); + } + + private: + Lock& lock_; + DISALLOW_COPY_AND_ASSIGN(AutoUnlock); +}; + +} // namespace base + +#endif // BASE_SYNCHRONIZATION_LOCK_H_
diff --git a/src/base/synchronization/lock_impl.h b/src/base/synchronization/lock_impl.h new file mode 100644 index 0000000..308154e --- /dev/null +++ b/src/base/synchronization/lock_impl.h
@@ -0,0 +1,69 @@ +// 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. + +#ifndef BASE_SYNCHRONIZATION_LOCK_IMPL_H_ +#define BASE_SYNCHRONIZATION_LOCK_IMPL_H_ + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#elif defined(OS_STARBOARD) +#include "starboard/mutex.h" +#elif defined(__LB_SHELL__) +#include "lb_mutex.h" +#elif defined(OS_POSIX) +#include <pthread.h> +#endif + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace internal { + +// This class implements the underlying platform-specific spin-lock mechanism +// used for the Lock class. Most users should not use LockImpl directly, but +// should instead use Lock. +class BASE_EXPORT LockImpl { + public: +#if defined(OS_WIN) + typedef CRITICAL_SECTION OSLockType; +#elif defined(OS_STARBOARD) + typedef SbMutex OSLockType; +#elif defined(__LB_SHELL__) + typedef lb_shell_mutex_t OSLockType; +#elif defined(OS_POSIX) + typedef pthread_mutex_t OSLockType; +#endif + + LockImpl(); + ~LockImpl(); + + // If the lock is not held, take it and return true. If the lock is already + // held by something else, immediately return false. + bool Try(); + + // Take the lock, blocking until it is available if necessary. + void Lock(); + + // Release the lock. This must only be called by the lock's holder: after + // a successful call to Try, or a call to Lock. + void Unlock(); + + // Return the native underlying lock. + // TODO(awalker): refactor lock and condition variables so that this is + // unnecessary. + OSLockType* os_lock() { return &os_lock_; } + + private: + OSLockType os_lock_; + + DISALLOW_COPY_AND_ASSIGN(LockImpl); +}; + +} // namespace internal +} // namespace base + +#endif // BASE_SYNCHRONIZATION_LOCK_IMPL_H_
diff --git a/src/base/synchronization/lock_impl_posix.cc b/src/base/synchronization/lock_impl_posix.cc new file mode 100644 index 0000000..f638fcd --- /dev/null +++ b/src/base/synchronization/lock_impl_posix.cc
@@ -0,0 +1,54 @@ +// 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/synchronization/lock_impl.h" + +#include <errno.h> + +#include "base/logging.h" + +namespace base { +namespace internal { + +LockImpl::LockImpl() { +#ifndef NDEBUG + // In debug, setup attributes for lock error checking. + pthread_mutexattr_t mta; + int rv = pthread_mutexattr_init(&mta); + DCHECK_EQ(rv, 0); + rv = pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_ERRORCHECK); + DCHECK_EQ(rv, 0); + rv = pthread_mutex_init(&os_lock_, &mta); + DCHECK_EQ(rv, 0); + rv = pthread_mutexattr_destroy(&mta); + DCHECK_EQ(rv, 0); +#else + // In release, go with the default lock attributes. + pthread_mutex_init(&os_lock_, NULL); +#endif +} + +LockImpl::~LockImpl() { + int rv = pthread_mutex_destroy(&os_lock_); + DCHECK_EQ(rv, 0); +} + +bool LockImpl::Try() { + int rv = pthread_mutex_trylock(&os_lock_); + DCHECK(rv == 0 || rv == EBUSY); + return rv == 0; +} + +void LockImpl::Lock() { + int rv = pthread_mutex_lock(&os_lock_); + DCHECK_EQ(rv, 0); +} + +void LockImpl::Unlock() { + int rv = pthread_mutex_unlock(&os_lock_); + DCHECK_EQ(rv, 0); +} + +} // namespace internal +} // namespace base
diff --git a/src/base/synchronization/lock_impl_shell.cc b/src/base/synchronization/lock_impl_shell.cc new file mode 100644 index 0000000..7cf6483 --- /dev/null +++ b/src/base/synchronization/lock_impl_shell.cc
@@ -0,0 +1,53 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/synchronization/lock_impl.h" + +#include <errno.h> + +#include "base/logging.h" + +namespace base { +namespace internal { + +LockImpl::LockImpl() { + int rv = lb_shell_mutex_init(&os_lock_); + DCHECK_EQ(rv, 0); +} + +LockImpl::~LockImpl() { + int rv = lb_shell_mutex_destroy(&os_lock_); + DCHECK_EQ(rv, 0); +} + +bool LockImpl::Try() { + int rv = lb_shell_mutex_trylock(&os_lock_); + DCHECK(rv == 0 || rv == EBUSY); + return rv == 0; +} + +void LockImpl::Lock() { + int rv = lb_shell_mutex_lock(&os_lock_); + DCHECK_EQ(rv, 0); +} + +void LockImpl::Unlock() { + int rv = lb_shell_mutex_unlock(&os_lock_); + DCHECK_EQ(rv, 0); +} + +} // namespace internal +} // namespace base
diff --git a/src/base/synchronization/lock_impl_starboard.cc b/src/base/synchronization/lock_impl_starboard.cc new file mode 100644 index 0000000..296981a --- /dev/null +++ b/src/base/synchronization/lock_impl_starboard.cc
@@ -0,0 +1,50 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/synchronization/lock_impl.h" + +#include "base/logging.h" +#include "starboard/mutex.h" + +namespace base { +namespace internal { + +LockImpl::LockImpl() { + bool result = SbMutexCreate(&os_lock_); + DCHECK(result); +} + +LockImpl::~LockImpl() { + bool result = SbMutexDestroy(&os_lock_); + DCHECK(result); +} + +bool LockImpl::Try() { + SbMutexResult result = SbMutexAcquireTry(&os_lock_); + DCHECK_NE(kSbMutexDestroyed, result); + return SbMutexIsSuccess(result); +} + +void LockImpl::Lock() { + SbMutexResult result = SbMutexAcquire(&os_lock_); + DCHECK_NE(kSbMutexDestroyed, result); +} + +void LockImpl::Unlock() { + bool result = SbMutexRelease(&os_lock_); + DCHECK(result); +} + +} // namespace internal +} // namespace base
diff --git a/src/base/synchronization/lock_impl_win.cc b/src/base/synchronization/lock_impl_win.cc new file mode 100644 index 0000000..bb8a23d --- /dev/null +++ b/src/base/synchronization/lock_impl_win.cc
@@ -0,0 +1,36 @@ +// 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/synchronization/lock_impl.h" + +namespace base { +namespace internal { + +LockImpl::LockImpl() { + // The second parameter is the spin count, for short-held locks it avoid the + // contending thread from going to sleep which helps performance greatly. + ::InitializeCriticalSectionAndSpinCount(&os_lock_, 2000); +} + +LockImpl::~LockImpl() { + ::DeleteCriticalSection(&os_lock_); +} + +bool LockImpl::Try() { + if (::TryEnterCriticalSection(&os_lock_) != FALSE) { + return true; + } + return false; +} + +void LockImpl::Lock() { + ::EnterCriticalSection(&os_lock_); +} + +void LockImpl::Unlock() { + ::LeaveCriticalSection(&os_lock_); +} + +} // namespace internal +} // namespace base
diff --git a/src/base/synchronization/lock_unittest.cc b/src/base/synchronization/lock_unittest.cc new file mode 100644 index 0000000..a048f85 --- /dev/null +++ b/src/base/synchronization/lock_unittest.cc
@@ -0,0 +1,216 @@ +// 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/synchronization/lock.h" + +#include <stdlib.h> + +#include "base/compiler_specific.h" +#include "base/threading/platform_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +// Basic test to make sure that Acquire()/Release()/Try() don't crash ---------- + +class BasicLockTestThread : public PlatformThread::Delegate { + public: + BasicLockTestThread(Lock* lock) : lock_(lock), acquired_(0) {} + + virtual void ThreadMain() OVERRIDE { + for (int i = 0; i < 10; i++) { + lock_->Acquire(); + acquired_++; + lock_->Release(); + } + for (int i = 0; i < 10; i++) { + lock_->Acquire(); + acquired_++; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); + lock_->Release(); + } + for (int i = 0; i < 10; i++) { + if (lock_->Try()) { + acquired_++; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); + lock_->Release(); + } + } + } + + int acquired() const { return acquired_; } + + private: + Lock* lock_; + int acquired_; + + DISALLOW_COPY_AND_ASSIGN(BasicLockTestThread); +}; + +TEST(LockTest, Basic) { + Lock lock; + BasicLockTestThread thread(&lock); + PlatformThreadHandle handle = kNullThreadHandle; + + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + + int acquired = 0; + for (int i = 0; i < 5; i++) { + lock.Acquire(); + acquired++; + lock.Release(); + } + for (int i = 0; i < 10; i++) { + lock.Acquire(); + acquired++; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); + lock.Release(); + } + for (int i = 0; i < 10; i++) { + if (lock.Try()) { + acquired++; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); + lock.Release(); + } + } + for (int i = 0; i < 5; i++) { + lock.Acquire(); + acquired++; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); + lock.Release(); + } + + PlatformThread::Join(handle); + + EXPECT_GE(acquired, 20); + EXPECT_GE(thread.acquired(), 20); +} + +// Test that Try() works as expected ------------------------------------------- + +class TryLockTestThread : public PlatformThread::Delegate { + public: + TryLockTestThread(Lock* lock) : lock_(lock), got_lock_(false) {} + + virtual void ThreadMain() OVERRIDE { + got_lock_ = lock_->Try(); + if (got_lock_) + lock_->Release(); + } + + bool got_lock() const { return got_lock_; } + + private: + Lock* lock_; + bool got_lock_; + + DISALLOW_COPY_AND_ASSIGN(TryLockTestThread); +}; + +TEST(LockTest, TryLock) { + Lock lock; + + ASSERT_TRUE(lock.Try()); + // We now have the lock.... + + // This thread will not be able to get the lock. + { + TryLockTestThread thread(&lock); + PlatformThreadHandle handle = kNullThreadHandle; + + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + + PlatformThread::Join(handle); + + ASSERT_FALSE(thread.got_lock()); + } + + lock.Release(); + + // This thread will.... + { + TryLockTestThread thread(&lock); + PlatformThreadHandle handle = kNullThreadHandle; + + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + + PlatformThread::Join(handle); + + ASSERT_TRUE(thread.got_lock()); + // But it released it.... + ASSERT_TRUE(lock.Try()); + } + + lock.Release(); +} + +// Tests that locks actually exclude ------------------------------------------- + +class MutexLockTestThread : public PlatformThread::Delegate { + public: + MutexLockTestThread(Lock* lock, int* value) : lock_(lock), value_(value) {} + + // Static helper which can also be called from the main thread. + static void DoStuff(Lock* lock, int* value) { + for (int i = 0; i < 40; i++) { + lock->Acquire(); + int v = *value; + PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 10)); + *value = v + 1; + lock->Release(); + } + } + + virtual void ThreadMain() OVERRIDE { + DoStuff(lock_, value_); + } + + private: + Lock* lock_; + int* value_; + + DISALLOW_COPY_AND_ASSIGN(MutexLockTestThread); +}; + +TEST(LockTest, MutexTwoThreads) { + Lock lock; + int value = 0; + + MutexLockTestThread thread(&lock, &value); + PlatformThreadHandle handle = kNullThreadHandle; + + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + + MutexLockTestThread::DoStuff(&lock, &value); + + PlatformThread::Join(handle); + + EXPECT_EQ(2 * 40, value); +} + +TEST(LockTest, MutexFourThreads) { + Lock lock; + int value = 0; + + MutexLockTestThread thread1(&lock, &value); + MutexLockTestThread thread2(&lock, &value); + MutexLockTestThread thread3(&lock, &value); + PlatformThreadHandle handle1 = kNullThreadHandle; + PlatformThreadHandle handle2 = kNullThreadHandle; + PlatformThreadHandle handle3 = kNullThreadHandle; + + ASSERT_TRUE(PlatformThread::Create(0, &thread1, &handle1)); + ASSERT_TRUE(PlatformThread::Create(0, &thread2, &handle2)); + ASSERT_TRUE(PlatformThread::Create(0, &thread3, &handle3)); + + MutexLockTestThread::DoStuff(&lock, &value); + + PlatformThread::Join(handle1); + PlatformThread::Join(handle2); + PlatformThread::Join(handle3); + + EXPECT_EQ(4 * 40, value); +} + +} // namespace base
diff --git a/src/base/synchronization/spin_wait.h b/src/base/synchronization/spin_wait.h new file mode 100644 index 0000000..ad54ca8 --- /dev/null +++ b/src/base/synchronization/spin_wait.h
@@ -0,0 +1,50 @@ +// 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. + +// This file provides a macro ONLY for use in testing. +// DO NOT USE IN PRODUCTION CODE. There are much better ways to wait. + +// This code is very helpful in testing multi-threaded code, without depending +// on almost any primitives. This is especially helpful if you are testing +// those primitive multi-threaded constructs. + +// We provide a simple one argument spin wait (for 1 second), and a generic +// spin wait (for longer periods of time). + +#ifndef BASE_SYNCHRONIZATION_SPIN_WAIT_H_ +#define BASE_SYNCHRONIZATION_SPIN_WAIT_H_ + +#include "base/threading/platform_thread.h" +#include "base/time.h" + +// Provide a macro that will wait no longer than 1 second for an asynchronous +// change is the value of an expression. +// A typical use would be: +// +// SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(0 == f(x)); +// +// The expression will be evaluated repeatedly until it is true, or until +// the time (1 second) expires. +// Since tests generally have a 5 second watch dog timer, this spin loop is +// typically used to get the padding needed on a given test platform to assure +// that the test passes, even if load varies, and external events vary. + +#define SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(expression) \ + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(base::TimeDelta::FromSeconds(1), \ + (expression)) + +#define SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(delta, expression) do { \ + base::TimeTicks start = base::TimeTicks::Now(); \ + const base::TimeDelta kTimeout = delta; \ + while (!(expression)) { \ + if (kTimeout < base::TimeTicks::Now() - start) { \ + EXPECT_LE((base::TimeTicks::Now() - start).InMilliseconds(), \ + kTimeout.InMilliseconds()) << "Timed out"; \ + break; \ + } \ + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); \ + } \ + } while (0) + +#endif // BASE_SYNCHRONIZATION_SPIN_WAIT_H_
diff --git a/src/base/synchronization/waitable_event.h b/src/base/synchronization/waitable_event.h new file mode 100644 index 0000000..dabf885 --- /dev/null +++ b/src/base/synchronization/waitable_event.h
@@ -0,0 +1,190 @@ +// 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_SYNCHRONIZATION_WAITABLE_EVENT_H_ +#define BASE_SYNCHRONIZATION_WAITABLE_EVENT_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +#if defined(OS_POSIX) || defined(OS_STARBOARD) +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) +#include <vector> +#else +#include <list> +#endif +#include <utility> +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#endif + +namespace base { + +// This replaces INFINITE from Win32 +static const int kNoTimeout = -1; + +class TimeDelta; + +// A WaitableEvent can be a useful thread synchronization tool when you want to +// allow one thread to wait for another thread to finish some work. For +// non-Windows systems, this can only be used from within a single address +// space. +// +// Use a WaitableEvent when you would otherwise use a Lock+ConditionVariable to +// protect a simple boolean value. However, if you find yourself using a +// WaitableEvent in conjunction with a Lock to wait for a more complex state +// change (e.g., for an item to be added to a queue), then you should probably +// be using a ConditionVariable instead of a WaitableEvent. +// +// NOTE: On Windows, this class provides a subset of the functionality afforded +// by a Windows event object. This is intentional. If you are writing Windows +// specific code and you need other features of a Windows event, then you might +// be better off just using an Windows event directly. +class BASE_EXPORT WaitableEvent { + public: + // If manual_reset is true, then to set the event state to non-signaled, a + // consumer must call the Reset method. If this parameter is false, then the + // system automatically resets the event state to non-signaled after a single + // waiting thread has been released. + WaitableEvent(bool manual_reset, bool initially_signaled); + +#if defined(OS_WIN) + // Create a WaitableEvent from an Event HANDLE which has already been + // created. This objects takes ownership of the HANDLE and will close it when + // deleted. + explicit WaitableEvent(HANDLE event_handle); + + // Releases ownership of the handle from this object. + HANDLE Release(); +#endif + + ~WaitableEvent(); + + // Put the event in the un-signaled state. + void Reset(); + + // Put the event in the signaled state. Causing any thread blocked on Wait + // to be woken up. + void Signal(); + + // Returns true if the event is in the signaled state, else false. If this + // is not a manual reset event, then this test will cause a reset. + bool IsSignaled(); + + // Wait indefinitely for the event to be signaled. + void Wait(); + + // Wait up until max_time has passed for the event to be signaled. Returns + // true if the event was signaled. If this method returns false, then it + // does not necessarily mean that max_time was exceeded. + bool TimedWait(const TimeDelta& max_time); + +#if defined(OS_WIN) + HANDLE handle() const { return handle_; } +#endif + + // Wait, synchronously, on multiple events. + // waitables: an array of WaitableEvent pointers + // count: the number of elements in @waitables + // + // returns: the index of a WaitableEvent which has been signaled. + // + // You MUST NOT delete any of the WaitableEvent objects while this wait is + // happening. + static size_t WaitMany(WaitableEvent** waitables, size_t count); + + // For asynchronous waiting, see WaitableEventWatcher + + // This is a private helper class. It's here because it's used by friends of + // this class (such as WaitableEventWatcher) to be able to enqueue elements + // of the wait-list + class Waiter { + public: + // Signal the waiter to wake up. + // + // Consider the case of a Waiter which is in multiple WaitableEvent's + // wait-lists. Each WaitableEvent is automatic-reset and two of them are + // signaled at the same time. Now, each will wake only the first waiter in + // the wake-list before resetting. However, if those two waiters happen to + // be the same object (as can happen if another thread didn't have a chance + // to dequeue the waiter from the other wait-list in time), two auto-resets + // will have happened, but only one waiter has been signaled! + // + // Because of this, a Waiter may "reject" a wake by returning false. In + // this case, the auto-reset WaitableEvent shouldn't act as if anything has + // been notified. + virtual bool Fire(WaitableEvent* signaling_event) = 0; + + // Waiters may implement this in order to provide an extra condition for + // two Waiters to be considered equal. In WaitableEvent::Dequeue, if the + // pointers match then this function is called as a final check. See the + // comments in ~Handle for why. + virtual bool Compare(void* tag) = 0; + + protected: + virtual ~Waiter() {} + }; + + private: + friend class WaitableEventWatcher; + +#if defined(OS_WIN) + HANDLE handle_; +#else + // On Windows, one can close a HANDLE which is currently being waited on. The + // MSDN documentation says that the resulting behaviour is 'undefined', but + // it doesn't crash. However, if we were to include the following members + // directly then, on POSIX, one couldn't use WaitableEventWatcher to watch an + // event which gets deleted. This mismatch has bitten us several times now, + // so we have a kernel of the WaitableEvent, which is reference counted. + // WaitableEventWatchers may then take a reference and thus match the Windows + // behaviour. + struct WaitableEventKernel : + public RefCountedThreadSafe<WaitableEventKernel> { + public: + WaitableEventKernel(bool manual_reset, bool initially_signaled); + + bool Dequeue(Waiter* waiter, void* tag); + + base::Lock lock_; + const bool manual_reset_; + bool signaled_; +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) + std::vector<Waiter*> waiters_; +#else + std::list<Waiter*> waiters_; +#endif + + private: + friend class RefCountedThreadSafe<WaitableEventKernel>; + ~WaitableEventKernel(); + }; + + typedef std::pair<WaitableEvent*, size_t> WaiterAndIndex; + + // When dealing with arrays of WaitableEvent*, we want to sort by the address + // of the WaitableEvent in order to have a globally consistent locking order. + // In that case we keep them, in sorted order, in an array of pairs where the + // second element is the index of the WaitableEvent in the original, + // unsorted, array. + static size_t EnqueueMany(WaiterAndIndex* waitables, + size_t count, Waiter* waiter); + + bool SignalAll(); + bool SignalOne(); + void Enqueue(Waiter* waiter); + + scoped_refptr<WaitableEventKernel> kernel_; +#endif + + DISALLOW_COPY_AND_ASSIGN(WaitableEvent); +}; + +} // namespace base + +#endif // BASE_SYNCHRONIZATION_WAITABLE_EVENT_H_
diff --git a/src/base/synchronization/waitable_event_posix.cc b/src/base/synchronization/waitable_event_posix.cc new file mode 100644 index 0000000..7066aee --- /dev/null +++ b/src/base/synchronization/waitable_event_posix.cc
@@ -0,0 +1,423 @@ +// 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 <algorithm> +#include <vector> + +#include "base/logging.h" +#include "base/synchronization/waitable_event.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_restrictions.h" + +// ----------------------------------------------------------------------------- +// A WaitableEvent on POSIX is implemented as a wait-list. Currently we don't +// support cross-process events (where one process can signal an event which +// others are waiting on). Because of this, we can avoid having one thread per +// listener in several cases. +// +// The WaitableEvent maintains a list of waiters, protected by a lock. Each +// waiter is either an async wait, in which case we have a Task and the +// MessageLoop to run it on, or a blocking wait, in which case we have the +// condition variable to signal. +// +// Waiting involves grabbing the lock and adding oneself to the wait list. Async +// waits can be canceled, which means grabbing the lock and removing oneself +// from the list. +// +// Waiting on multiple events is handled by adding a single, synchronous wait to +// the wait-list of many events. An event passes a pointer to itself when +// firing a waiter and so we can store that pointer to find out which event +// triggered. +// ----------------------------------------------------------------------------- + +namespace base { + +// ----------------------------------------------------------------------------- +// This is just an abstract base class for waking the two types of waiters +// ----------------------------------------------------------------------------- +WaitableEvent::WaitableEvent(bool manual_reset, bool initially_signaled) + : kernel_(new WaitableEventKernel(manual_reset, initially_signaled)) { +} + +WaitableEvent::~WaitableEvent() { +} + +void WaitableEvent::Reset() { + base::AutoLock locked(kernel_->lock_); + kernel_->signaled_ = false; +} + +void WaitableEvent::Signal() { + base::AutoLock locked(kernel_->lock_); + + if (kernel_->signaled_) + return; + + if (kernel_->manual_reset_) { + SignalAll(); + kernel_->signaled_ = true; + } else { + // In the case of auto reset, if no waiters were woken, we remain + // signaled. + if (!SignalOne()) + kernel_->signaled_ = true; + } +} + +bool WaitableEvent::IsSignaled() { + base::AutoLock locked(kernel_->lock_); + + const bool result = kernel_->signaled_; + if (result && !kernel_->manual_reset_) + kernel_->signaled_ = false; + return result; +} + +// ----------------------------------------------------------------------------- +// Synchronous waits + +// ----------------------------------------------------------------------------- +// This is a synchronous waiter. The thread is waiting on the given condition +// variable and the fired flag in this object. +// ----------------------------------------------------------------------------- +class SyncWaiter : public WaitableEvent::Waiter { + public: + SyncWaiter() + : fired_(false), + signaling_event_(NULL), + lock_(), + cv_(&lock_) { + } + + virtual bool Fire(WaitableEvent* signaling_event) OVERRIDE { + base::AutoLock locked(lock_); + + if (fired_) + return false; + + fired_ = true; + signaling_event_ = signaling_event; + + cv_.Broadcast(); + + // Unlike AsyncWaiter objects, SyncWaiter objects are stack-allocated on + // the blocking thread's stack. There is no |delete this;| in Fire. The + // SyncWaiter object is destroyed when it goes out of scope. + + return true; + } + + WaitableEvent* signaling_event() const { + return signaling_event_; + } + + // --------------------------------------------------------------------------- + // These waiters are always stack allocated and don't delete themselves. Thus + // there's no problem and the ABA tag is the same as the object pointer. + // --------------------------------------------------------------------------- + virtual bool Compare(void* tag) OVERRIDE { + return this == tag; + } + + // --------------------------------------------------------------------------- + // Called with lock held. + // --------------------------------------------------------------------------- + bool fired() const { + return fired_; + } + + // --------------------------------------------------------------------------- + // During a TimedWait, we need a way to make sure that an auto-reset + // WaitableEvent doesn't think that this event has been signaled between + // unlocking it and removing it from the wait-list. Called with lock held. + // --------------------------------------------------------------------------- + void Disable() { + fired_ = true; + } + + base::Lock* lock() { + return &lock_; + } + + base::ConditionVariable* cv() { + return &cv_; + } + + private: + bool fired_; + WaitableEvent* signaling_event_; // The WaitableEvent which woke us + base::Lock lock_; + base::ConditionVariable cv_; +}; + +void WaitableEvent::Wait() { + bool result = TimedWait(TimeDelta::FromSeconds(-1)); + DCHECK(result) << "TimedWait() should never fail with infinite timeout"; +} + +bool WaitableEvent::TimedWait(const TimeDelta& max_time) { + base::ThreadRestrictions::AssertWaitAllowed(); + const Time end_time(Time::Now() + max_time); + const bool finite_time = max_time.ToInternalValue() >= 0; + + kernel_->lock_.Acquire(); + if (kernel_->signaled_) { + if (!kernel_->manual_reset_) { + // In this case we were signaled when we had no waiters. Now that + // someone has waited upon us, we can automatically reset. + kernel_->signaled_ = false; + } + + kernel_->lock_.Release(); + return true; + } + + SyncWaiter sw; + sw.lock()->Acquire(); + + Enqueue(&sw); + kernel_->lock_.Release(); + // We are violating locking order here by holding the SyncWaiter lock but not + // the WaitableEvent lock. However, this is safe because we don't lock @lock_ + // again before unlocking it. + + for (;;) { + const Time current_time(Time::Now()); + + if (sw.fired() || (finite_time && current_time >= end_time)) { + const bool return_value = sw.fired(); + + // We can't acquire @lock_ before releasing the SyncWaiter lock (because + // of locking order), however, in between the two a signal could be fired + // and @sw would accept it, however we will still return false, so the + // signal would be lost on an auto-reset WaitableEvent. Thus we call + // Disable which makes sw::Fire return false. + sw.Disable(); + sw.lock()->Release(); + + kernel_->lock_.Acquire(); + kernel_->Dequeue(&sw, &sw); + kernel_->lock_.Release(); + + return return_value; + } + + if (finite_time) { + const TimeDelta max_wait(end_time - current_time); + sw.cv()->TimedWait(max_wait); + } else { + sw.cv()->Wait(); + } + } +} + +// ----------------------------------------------------------------------------- +// Synchronous waiting on multiple objects. + +static bool // StrictWeakOrdering +cmp_fst_addr(const std::pair<WaitableEvent*, size_t> &a, + const std::pair<WaitableEvent*, size_t> &b) { + return a.first < b.first; +} + +// static +size_t WaitableEvent::WaitMany(WaitableEvent** raw_waitables, + size_t count) { + base::ThreadRestrictions::AssertWaitAllowed(); + DCHECK(count) << "Cannot wait on no events"; + + // We need to acquire the locks in a globally consistent order. Thus we sort + // the array of waitables by address. We actually sort a pairs so that we can + // map back to the original index values later. + std::vector<std::pair<WaitableEvent*, size_t> > waitables; + waitables.reserve(count); + for (size_t i = 0; i < count; ++i) + waitables.push_back(std::make_pair(raw_waitables[i], i)); + + DCHECK_EQ(count, waitables.size()); + + sort(waitables.begin(), waitables.end(), cmp_fst_addr); + + // The set of waitables must be distinct. Since we have just sorted by + // address, we can check this cheaply by comparing pairs of consecutive + // elements. + for (size_t i = 0; i < waitables.size() - 1; ++i) { + DCHECK(waitables[i].first != waitables[i+1].first); + } + + SyncWaiter sw; + + const size_t r = EnqueueMany(&waitables[0], count, &sw); + if (r) { + // One of the events is already signaled. The SyncWaiter has not been + // enqueued anywhere. EnqueueMany returns the count of remaining waitables + // when the signaled one was seen, so the index of the signaled event is + // @count - @r. + return waitables[count - r].second; + } + + // At this point, we hold the locks on all the WaitableEvents and we have + // enqueued our waiter in them all. + sw.lock()->Acquire(); + // Release the WaitableEvent locks in the reverse order + for (size_t i = 0; i < count; ++i) { + waitables[count - (1 + i)].first->kernel_->lock_.Release(); + } + + for (;;) { + if (sw.fired()) + break; + + sw.cv()->Wait(); + } + sw.lock()->Release(); + + // The address of the WaitableEvent which fired is stored in the SyncWaiter. + WaitableEvent *const signaled_event = sw.signaling_event(); + // This will store the index of the raw_waitables which fired. + size_t signaled_index = 0; + + // Take the locks of each WaitableEvent in turn (except the signaled one) and + // remove our SyncWaiter from the wait-list + for (size_t i = 0; i < count; ++i) { + if (raw_waitables[i] != signaled_event) { + raw_waitables[i]->kernel_->lock_.Acquire(); + // There's no possible ABA issue with the address of the SyncWaiter here + // because it lives on the stack. Thus the tag value is just the pointer + // value again. + raw_waitables[i]->kernel_->Dequeue(&sw, &sw); + raw_waitables[i]->kernel_->lock_.Release(); + } else { + signaled_index = i; + } + } + + return signaled_index; +} + +// ----------------------------------------------------------------------------- +// If return value == 0: +// The locks of the WaitableEvents have been taken in order and the Waiter has +// been enqueued in the wait-list of each. None of the WaitableEvents are +// currently signaled +// else: +// None of the WaitableEvent locks are held. The Waiter has not been enqueued +// in any of them and the return value is the index of the first WaitableEvent +// which was signaled, from the end of the array. +// ----------------------------------------------------------------------------- +// static +size_t WaitableEvent::EnqueueMany + (std::pair<WaitableEvent*, size_t>* waitables, + size_t count, Waiter* waiter) { + if (!count) + return 0; + + waitables[0].first->kernel_->lock_.Acquire(); + if (waitables[0].first->kernel_->signaled_) { + if (!waitables[0].first->kernel_->manual_reset_) + waitables[0].first->kernel_->signaled_ = false; + waitables[0].first->kernel_->lock_.Release(); + return count; + } + + const size_t r = EnqueueMany(waitables + 1, count - 1, waiter); + if (r) { + waitables[0].first->kernel_->lock_.Release(); + } else { + waitables[0].first->Enqueue(waiter); + } + + return r; +} + +// ----------------------------------------------------------------------------- + + +// ----------------------------------------------------------------------------- +// Private functions... + +WaitableEvent::WaitableEventKernel::WaitableEventKernel(bool manual_reset, + bool initially_signaled) + : manual_reset_(manual_reset), + signaled_(initially_signaled) { +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) + waiters_.reserve(1); +#endif +} + +WaitableEvent::WaitableEventKernel::~WaitableEventKernel() { +} + +// ----------------------------------------------------------------------------- +// Wake all waiting waiters. Called with lock held. +// ----------------------------------------------------------------------------- +bool WaitableEvent::SignalAll() { + bool signaled_at_least_one = false; +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) + for (std::vector<Waiter*>::iterator +#else + for (std::list<Waiter*>::iterator +#endif + i = kernel_->waiters_.begin(); i != kernel_->waiters_.end(); ++i) { + if ((*i)->Fire(this)) + signaled_at_least_one = true; + } + + kernel_->waiters_.clear(); + return signaled_at_least_one; +} + +// --------------------------------------------------------------------------- +// Try to wake a single waiter. Return true if one was woken. Called with lock +// held. +// --------------------------------------------------------------------------- +bool WaitableEvent::SignalOne() { + for (;;) { + if (kernel_->waiters_.empty()) + return false; + + const bool r = (*kernel_->waiters_.begin())->Fire(this); +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) + // This appears to be slow, but the size of the vector is almost always 1. + // Therefore, this line is usually constant time. + kernel_->waiters_.erase(kernel_->waiters_.begin()); +#else + kernel_->waiters_.pop_front(); +#endif + if (r) + return true; + } +} + +// ----------------------------------------------------------------------------- +// Add a waiter to the list of those waiting. Called with lock held. +// ----------------------------------------------------------------------------- +void WaitableEvent::Enqueue(Waiter* waiter) { + kernel_->waiters_.push_back(waiter); +} + +// ----------------------------------------------------------------------------- +// Remove a waiter from the list of those waiting. Return true if the waiter was +// actually removed. Called with lock held. +// ----------------------------------------------------------------------------- +bool WaitableEvent::WaitableEventKernel::Dequeue(Waiter* waiter, void* tag) { +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) + for (std::vector<Waiter*>::iterator +#else + for (std::list<Waiter*>::iterator +#endif + i = waiters_.begin(); i != waiters_.end(); ++i) { + if (*i == waiter && (*i)->Compare(tag)) { + waiters_.erase(i); + return true; + } + } + + return false; +} + +// ----------------------------------------------------------------------------- + +} // namespace base
diff --git a/src/base/synchronization/waitable_event_shell.cc b/src/base/synchronization/waitable_event_shell.cc new file mode 100644 index 0000000..9df0680 --- /dev/null +++ b/src/base/synchronization/waitable_event_shell.cc
@@ -0,0 +1,18 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// We use the same code as posix version +#include "waitable_event_posix.cc"
diff --git a/src/base/synchronization/waitable_event_starboard.cc b/src/base/synchronization/waitable_event_starboard.cc new file mode 100644 index 0000000..a49cc7c --- /dev/null +++ b/src/base/synchronization/waitable_event_starboard.cc
@@ -0,0 +1,18 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// We use the same code as the POSIX version. +#include "base/synchronization/waitable_event_posix.cc"
diff --git a/src/base/synchronization/waitable_event_unittest.cc b/src/base/synchronization/waitable_event_unittest.cc new file mode 100644 index 0000000..d00adc7 --- /dev/null +++ b/src/base/synchronization/waitable_event_unittest.cc
@@ -0,0 +1,108 @@ +// 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/synchronization/waitable_event.h" + +#include "base/compiler_specific.h" +#include "base/time.h" +#include "base/threading/platform_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(WaitableEventTest, ManualBasics) { + WaitableEvent event(true, false); + + EXPECT_FALSE(event.IsSignaled()); + + event.Signal(); + EXPECT_TRUE(event.IsSignaled()); + EXPECT_TRUE(event.IsSignaled()); + + event.Reset(); + EXPECT_FALSE(event.IsSignaled()); + EXPECT_FALSE(event.TimedWait(TimeDelta::FromMilliseconds(10))); + + event.Signal(); + event.Wait(); + EXPECT_TRUE(event.TimedWait(TimeDelta::FromMilliseconds(10))); +} + +TEST(WaitableEventTest, AutoBasics) { + WaitableEvent event(false, false); + + EXPECT_FALSE(event.IsSignaled()); + + event.Signal(); + EXPECT_TRUE(event.IsSignaled()); + EXPECT_FALSE(event.IsSignaled()); + + event.Reset(); + EXPECT_FALSE(event.IsSignaled()); + EXPECT_FALSE(event.TimedWait(TimeDelta::FromMilliseconds(10))); + + event.Signal(); + event.Wait(); + EXPECT_FALSE(event.TimedWait(TimeDelta::FromMilliseconds(10))); + + event.Signal(); + EXPECT_TRUE(event.TimedWait(TimeDelta::FromMilliseconds(10))); +} + +TEST(WaitableEventTest, WaitManyShortcut) { + WaitableEvent* ev[5]; + for (unsigned i = 0; i < 5; ++i) + ev[i] = new WaitableEvent(false, false); + + ev[3]->Signal(); + EXPECT_EQ(WaitableEvent::WaitMany(ev, 5), 3u); + + ev[3]->Signal(); + EXPECT_EQ(WaitableEvent::WaitMany(ev, 5), 3u); + + ev[4]->Signal(); + EXPECT_EQ(WaitableEvent::WaitMany(ev, 5), 4u); + + ev[0]->Signal(); + EXPECT_EQ(WaitableEvent::WaitMany(ev, 5), 0u); + + for (unsigned i = 0; i < 5; ++i) + delete ev[i]; +} + +class WaitableEventSignaler : public PlatformThread::Delegate { + public: + WaitableEventSignaler(double seconds, WaitableEvent* ev) + : seconds_(seconds), + ev_(ev) { + } + + virtual void ThreadMain() OVERRIDE { + PlatformThread::Sleep(TimeDelta::FromSeconds(static_cast<int>(seconds_))); + ev_->Signal(); + } + + private: + const double seconds_; + WaitableEvent *const ev_; +}; + +TEST(WaitableEventTest, WaitMany) { + WaitableEvent* ev[5]; + for (unsigned i = 0; i < 5; ++i) + ev[i] = new WaitableEvent(false, false); + + WaitableEventSignaler signaler(0.1, ev[2]); + PlatformThreadHandle thread; + PlatformThread::Create(0, &signaler, &thread); + + EXPECT_EQ(WaitableEvent::WaitMany(ev, 5), 2u); + + PlatformThread::Join(thread); + + for (unsigned i = 0; i < 5; ++i) + delete ev[i]; +} + +} // namespace base
diff --git a/src/base/synchronization/waitable_event_watcher.h b/src/base/synchronization/waitable_event_watcher.h new file mode 100644 index 0000000..f5c1510 --- /dev/null +++ b/src/base/synchronization/waitable_event_watcher.h
@@ -0,0 +1,164 @@ +// 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_SYNCHRONIZATION_WAITABLE_EVENT_WATCHER_H_ +#define BASE_SYNCHRONIZATION_WAITABLE_EVENT_WATCHER_H_ + +#include "base/base_export.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include "base/win/object_watcher.h" +#else +#include "base/callback.h" +#include "base/message_loop.h" +#include "base/synchronization/waitable_event.h" +#endif + +namespace base { + +class Flag; +class AsyncWaiter; +class AsyncCallbackTask; +class WaitableEvent; + +// ----------------------------------------------------------------------------- +// This class provides a way to wait on a WaitableEvent asynchronously. +// +// Each instance of this object can be waiting on a single WaitableEvent. When +// the waitable event is signaled, a callback is made in the thread of a given +// MessageLoop. This callback can be deleted by deleting the waiter. +// +// Typical usage: +// +// class MyClass : public base::WaitableEventWatcher::Delegate { +// public: +// void DoStuffWhenSignaled(WaitableEvent *waitable_event) { +// watcher_.StartWatching(waitable_event, this); +// } +// virtual void OnWaitableEventSignaled(WaitableEvent* waitable_event) { +// // OK, time to do stuff! +// } +// private: +// base::WaitableEventWatcher watcher_; +// }; +// +// In the above example, MyClass wants to "do stuff" when waitable_event +// becomes signaled. WaitableEventWatcher makes this task easy. When MyClass +// goes out of scope, the watcher_ will be destroyed, and there is no need to +// worry about OnWaitableEventSignaled being called on a deleted MyClass +// pointer. +// +// BEWARE: With automatically reset WaitableEvents, a signal may be lost if it +// occurs just before a WaitableEventWatcher is deleted. There is currently no +// safe way to stop watching an automatic reset WaitableEvent without possibly +// missing a signal. +// +// NOTE: you /are/ allowed to delete the WaitableEvent while still waiting on +// it with a Watcher. It will act as if the event was never signaled. +// ----------------------------------------------------------------------------- + +class BASE_EXPORT WaitableEventWatcher +#if !defined(OS_WIN) + : public MessageLoop::DestructionObserver +#endif +{ + public: + + WaitableEventWatcher(); + virtual ~WaitableEventWatcher(); + + class BASE_EXPORT Delegate { + public: + // ------------------------------------------------------------------------- + // This is called on the MessageLoop thread when WaitableEvent has been + // signaled. + // + // Note: the event may not be signaled by the time that this function is + // called. This indicates only that it has been signaled at some point in + // the past. + // ------------------------------------------------------------------------- + virtual void OnWaitableEventSignaled(WaitableEvent* waitable_event) = 0; + + protected: + virtual ~Delegate() { } + }; + + // --------------------------------------------------------------------------- + // When @event is signaled, the given delegate is called on the thread of the + // current message loop when StartWatching is called. The delegate is not + // deleted. + // --------------------------------------------------------------------------- + bool StartWatching(WaitableEvent* event, Delegate* delegate); + + // --------------------------------------------------------------------------- + // Cancel the current watch. Must be called from the same thread which + // started the watch. + // + // Does nothing if no event is being watched, nor if the watch has completed. + // The delegate will *not* be called for the current watch after this + // function returns. Since the delegate runs on the same thread as this + // function, it cannot be called during this function either. + // --------------------------------------------------------------------------- + void StopWatching(); + + // --------------------------------------------------------------------------- + // Return the currently watched event, or NULL if no object is currently being + // watched. + // --------------------------------------------------------------------------- + WaitableEvent* GetWatchedEvent(); + + // --------------------------------------------------------------------------- + // Return the delegate, or NULL if there is no delegate. + // --------------------------------------------------------------------------- + Delegate* delegate() { + return delegate_; + } + + private: +#if defined(OS_WIN) + // --------------------------------------------------------------------------- + // The helper class exists because, if WaitableEventWatcher were to inherit + // from ObjectWatcher::Delegate, then it couldn't also have an inner class + // called Delegate (at least on Windows). Thus this object exists to proxy + // the callback function + // --------------------------------------------------------------------------- + class ObjectWatcherHelper : public win::ObjectWatcher::Delegate { + public: + ObjectWatcherHelper(WaitableEventWatcher* watcher); + + // ------------------------------------------------------------------------- + // Implementation of ObjectWatcher::Delegate + // ------------------------------------------------------------------------- + void OnObjectSignaled(HANDLE h); + + private: + WaitableEventWatcher *const watcher_; + }; + + void OnObjectSignaled(); + + ObjectWatcherHelper helper_; + win::ObjectWatcher watcher_; +#else + // --------------------------------------------------------------------------- + // Implementation of MessageLoop::DestructionObserver + // --------------------------------------------------------------------------- + virtual void WillDestroyCurrentMessageLoop() OVERRIDE; + + MessageLoop* message_loop_; + scoped_refptr<Flag> cancel_flag_; + AsyncWaiter* waiter_; + base::Closure callback_; + scoped_refptr<WaitableEvent::WaitableEventKernel> kernel_; +#endif + + WaitableEvent* event_; + + Delegate* delegate_; +}; + +} // namespace base + +#endif // BASE_SYNCHRONIZATION_WAITABLE_EVENT_WATCHER_H_
diff --git a/src/base/synchronization/waitable_event_watcher_posix.cc b/src/base/synchronization/waitable_event_watcher_posix.cc new file mode 100644 index 0000000..3180b4b --- /dev/null +++ b/src/base/synchronization/waitable_event_watcher_posix.cc
@@ -0,0 +1,270 @@ +// 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/synchronization/waitable_event_watcher.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" + +namespace base { + +// ----------------------------------------------------------------------------- +// WaitableEventWatcher (async waits). +// +// The basic design is that we add an AsyncWaiter to the wait-list of the event. +// That AsyncWaiter has a pointer to MessageLoop, and a Task to be posted to it. +// The MessageLoop ends up running the task, which calls the delegate. +// +// Since the wait can be canceled, we have a thread-safe Flag object which is +// set when the wait has been canceled. At each stage in the above, we check the +// flag before going onto the next stage. Since the wait may only be canceled in +// the MessageLoop which runs the Task, we are assured that the delegate cannot +// be called after canceling... + +// ----------------------------------------------------------------------------- +// A thread-safe, reference-counted, write-once flag. +// ----------------------------------------------------------------------------- +class Flag : public RefCountedThreadSafe<Flag> { + public: + Flag() { flag_ = false; } + + void Set() { + AutoLock locked(lock_); + flag_ = true; + } + + bool value() const { + AutoLock locked(lock_); + return flag_; + } + + private: + friend class RefCountedThreadSafe<Flag>; + ~Flag() {} + + mutable Lock lock_; + bool flag_; + + DISALLOW_COPY_AND_ASSIGN(Flag); +}; + +// ----------------------------------------------------------------------------- +// This is an asynchronous waiter which posts a task to a MessageLoop when +// fired. An AsyncWaiter may only be in a single wait-list. +// ----------------------------------------------------------------------------- +class AsyncWaiter : public WaitableEvent::Waiter { + public: + AsyncWaiter(MessageLoop* message_loop, + const base::Closure& callback, + Flag* flag) + : message_loop_(message_loop), + callback_(callback), + flag_(flag) { } + + virtual bool Fire(WaitableEvent* event) OVERRIDE { + // Post the callback if we haven't been cancelled. + if (!flag_->value()) { + message_loop_->PostTask(FROM_HERE, callback_); + } + + // We are removed from the wait-list by the WaitableEvent itself. It only + // remains to delete ourselves. + delete this; + + // We can always return true because an AsyncWaiter is never in two + // different wait-lists at the same time. + return true; + } + + // See StopWatching for discussion + virtual bool Compare(void* tag) OVERRIDE { + return tag == flag_.get(); + } + + private: + MessageLoop *const message_loop_; + base::Closure callback_; + scoped_refptr<Flag> flag_; +}; + +// ----------------------------------------------------------------------------- +// For async waits we need to make a callback in a MessageLoop thread. We do +// this by posting a callback, which calls the delegate and keeps track of when +// the event is canceled. +// ----------------------------------------------------------------------------- +void AsyncCallbackHelper(Flag* flag, + WaitableEventWatcher::Delegate* delegate, + WaitableEvent* event) { + // Runs in MessageLoop thread. + if (!flag->value()) { + // This is to let the WaitableEventWatcher know that the event has occured + // because it needs to be able to return NULL from GetWatchedObject + flag->Set(); + delegate->OnWaitableEventSignaled(event); + } +} + +WaitableEventWatcher::WaitableEventWatcher() + : message_loop_(NULL), + cancel_flag_(NULL), + waiter_(NULL), + event_(NULL), + delegate_(NULL) { +} + +WaitableEventWatcher::~WaitableEventWatcher() { + StopWatching(); +} + +// ----------------------------------------------------------------------------- +// The Handle is how the user cancels a wait. After deleting the Handle we +// insure that the delegate cannot be called. +// ----------------------------------------------------------------------------- +bool WaitableEventWatcher::StartWatching + (WaitableEvent* event, WaitableEventWatcher::Delegate* delegate) { + MessageLoop *const current_ml = MessageLoop::current(); + DCHECK(current_ml) << "Cannot create WaitableEventWatcher without a " + "current MessageLoop"; + + // A user may call StartWatching from within the callback function. In this + // case, we won't know that we have finished watching, expect that the Flag + // will have been set in AsyncCallbackHelper(). + if (cancel_flag_.get() && cancel_flag_->value()) { + if (message_loop_) { + message_loop_->RemoveDestructionObserver(this); + message_loop_ = NULL; + } + + cancel_flag_ = NULL; + } + + DCHECK(!cancel_flag_.get()) << "StartWatching called while still watching"; + + cancel_flag_ = new Flag; + callback_ = base::Bind(&AsyncCallbackHelper, cancel_flag_, delegate, event); + WaitableEvent::WaitableEventKernel* kernel = event->kernel_.get(); + + AutoLock locked(kernel->lock_); + + delegate_ = delegate; + event_ = event; + + if (kernel->signaled_) { + if (!kernel->manual_reset_) + kernel->signaled_ = false; + + // No hairpinning - we can't call the delegate directly here. We have to + // enqueue a task on the MessageLoop as normal. + current_ml->PostTask(FROM_HERE, callback_); + return true; + } + + message_loop_ = current_ml; + current_ml->AddDestructionObserver(this); + + kernel_ = kernel; + waiter_ = new AsyncWaiter(current_ml, callback_, cancel_flag_); + event->Enqueue(waiter_); + + return true; +} + +void WaitableEventWatcher::StopWatching() { + delegate_ = NULL; + + if (message_loop_) { + message_loop_->RemoveDestructionObserver(this); + message_loop_ = NULL; + } + + if (!cancel_flag_.get()) // if not currently watching... + return; + + if (cancel_flag_->value()) { + // In this case, the event has fired, but we haven't figured that out yet. + // The WaitableEvent may have been deleted too. + cancel_flag_ = NULL; + return; + } + + if (!kernel_.get()) { + // We have no kernel. This means that we never enqueued a Waiter on an + // event because the event was already signaled when StartWatching was + // called. + // + // In this case, a task was enqueued on the MessageLoop and will run. + // We set the flag in case the task hasn't yet run. The flag will stop the + // delegate getting called. If the task has run then we have the last + // reference to the flag and it will be deleted immedately after. + cancel_flag_->Set(); + cancel_flag_ = NULL; + return; + } + + AutoLock locked(kernel_->lock_); + // We have a lock on the kernel. No one else can signal the event while we + // have it. + + // We have a possible ABA issue here. If Dequeue was to compare only the + // pointer values then it's possible that the AsyncWaiter could have been + // fired, freed and the memory reused for a different Waiter which was + // enqueued in the same wait-list. We would think that that waiter was our + // AsyncWaiter and remove it. + // + // To stop this, Dequeue also takes a tag argument which is passed to the + // virtual Compare function before the two are considered a match. So we need + // a tag which is good for the lifetime of this handle: the Flag. Since we + // have a reference to the Flag, its memory cannot be reused while this object + // still exists. So if we find a waiter with the correct pointer value, and + // which shares a Flag pointer, we have a real match. + if (kernel_->Dequeue(waiter_, cancel_flag_.get())) { + // Case 2: the waiter hasn't been signaled yet; it was still on the wait + // list. We've removed it, thus we can delete it and the task (which cannot + // have been enqueued with the MessageLoop because the waiter was never + // signaled) + delete waiter_; + callback_.Reset(); + cancel_flag_ = NULL; + return; + } + + // Case 3: the waiter isn't on the wait-list, thus it was signaled. It may + // not have run yet, so we set the flag to tell it not to bother enqueuing the + // task on the MessageLoop, but to delete it instead. The Waiter deletes + // itself once run. + cancel_flag_->Set(); + cancel_flag_ = NULL; + + // If the waiter has already run then the task has been enqueued. If the Task + // hasn't yet run, the flag will stop the delegate from getting called. (This + // is thread safe because one may only delete a Handle from the MessageLoop + // thread.) + // + // If the delegate has already been called then we have nothing to do. The + // task has been deleted by the MessageLoop. +} + +WaitableEvent* WaitableEventWatcher::GetWatchedEvent() { + if (!cancel_flag_.get()) + return NULL; + + if (cancel_flag_->value()) + return NULL; + + return event_; +} + +// ----------------------------------------------------------------------------- +// This is called when the MessageLoop which the callback will be run it is +// deleted. We need to cancel the callback as if we had been deleted, but we +// will still be deleted at some point in the future. +// ----------------------------------------------------------------------------- +void WaitableEventWatcher::WillDestroyCurrentMessageLoop() { + StopWatching(); +} + +} // namespace base
diff --git a/src/base/synchronization/waitable_event_watcher_unittest.cc b/src/base/synchronization/waitable_event_watcher_unittest.cc new file mode 100644 index 0000000..8744aa2 --- /dev/null +++ b/src/base/synchronization/waitable_event_watcher_unittest.cc
@@ -0,0 +1,176 @@ +// 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/message_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/synchronization/waitable_event_watcher.h" +#include "base/threading/platform_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +// The message loops on which each waitable event timer should be tested. +const MessageLoop::Type testing_message_loops[] = { + MessageLoop::TYPE_DEFAULT, + MessageLoop::TYPE_IO, +#if !defined(OS_IOS) // iOS does not allow direct running of the UI loop. + MessageLoop::TYPE_UI, +#endif +}; + +const int kNumTestingMessageLoops = arraysize(testing_message_loops); + +class QuitDelegate : public WaitableEventWatcher::Delegate { + public: + virtual void OnWaitableEventSignaled(WaitableEvent* event) OVERRIDE { + MessageLoop::current()->Quit(); + } +}; + +class DecrementCountDelegate : public WaitableEventWatcher::Delegate { + public: + explicit DecrementCountDelegate(int* counter) : counter_(counter) { + } + virtual void OnWaitableEventSignaled(WaitableEvent* object) OVERRIDE { + --(*counter_); + } + private: + int* counter_; +}; + +void RunTest_BasicSignal(MessageLoop::Type message_loop_type) { + MessageLoop message_loop(message_loop_type); + + // A manual-reset event that is not yet signaled. + WaitableEvent event(true, false); + + WaitableEventWatcher watcher; + EXPECT_TRUE(watcher.GetWatchedEvent() == NULL); + + QuitDelegate delegate; + watcher.StartWatching(&event, &delegate); + EXPECT_EQ(&event, watcher.GetWatchedEvent()); + + event.Signal(); + + MessageLoop::current()->Run(); + + EXPECT_TRUE(watcher.GetWatchedEvent() == NULL); +} + +void RunTest_BasicCancel(MessageLoop::Type message_loop_type) { + MessageLoop message_loop(message_loop_type); + + // A manual-reset event that is not yet signaled. + WaitableEvent event(true, false); + + WaitableEventWatcher watcher; + + QuitDelegate delegate; + watcher.StartWatching(&event, &delegate); + + watcher.StopWatching(); +} + +void RunTest_CancelAfterSet(MessageLoop::Type message_loop_type) { + MessageLoop message_loop(message_loop_type); + + // A manual-reset event that is not yet signaled. + WaitableEvent event(true, false); + + WaitableEventWatcher watcher; + + int counter = 1; + DecrementCountDelegate delegate(&counter); + + watcher.StartWatching(&event, &delegate); + + event.Signal(); + + // Let the background thread do its business + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(30)); + + watcher.StopWatching(); + + MessageLoop::current()->RunUntilIdle(); + + // Our delegate should not have fired. + EXPECT_EQ(1, counter); +} + +void RunTest_OutlivesMessageLoop(MessageLoop::Type message_loop_type) { + // Simulate a MessageLoop that dies before an WaitableEventWatcher. This + // ordinarily doesn't happen when people use the Thread class, but it can + // happen when people use the Singleton pattern or atexit. + WaitableEvent event(true, false); + { + WaitableEventWatcher watcher; + { + MessageLoop message_loop(message_loop_type); + + QuitDelegate delegate; + watcher.StartWatching(&event, &delegate); + } + } +} + +void RunTest_DeleteUnder(MessageLoop::Type message_loop_type) { + // Delete the WaitableEvent out from under the Watcher. This is explictly + // allowed by the interface. + + MessageLoop message_loop(message_loop_type); + + { + WaitableEventWatcher watcher; + + WaitableEvent* event = new WaitableEvent(false, false); + QuitDelegate delegate; + watcher.StartWatching(event, &delegate); + delete event; + } +} + +} // namespace + +//----------------------------------------------------------------------------- + +TEST(WaitableEventWatcherTest, BasicSignal) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_BasicSignal(testing_message_loops[i]); + } +} + +TEST(WaitableEventWatcherTest, BasicCancel) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_BasicCancel(testing_message_loops[i]); + } +} + +TEST(WaitableEventWatcherTest, CancelAfterSet) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_CancelAfterSet(testing_message_loops[i]); + } +} + +TEST(WaitableEventWatcherTest, OutlivesMessageLoop) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_OutlivesMessageLoop(testing_message_loops[i]); + } +} + +#if defined(OS_WIN) +// Crashes sometimes on vista. http://crbug.com/62119 +#define MAYBE_DeleteUnder DISABLED_DeleteUnder +#else +#define MAYBE_DeleteUnder DeleteUnder +#endif +TEST(WaitableEventWatcherTest, MAYBE_DeleteUnder) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_DeleteUnder(testing_message_loops[i]); + } +} + +} // namespace base
diff --git a/src/base/synchronization/waitable_event_watcher_win.cc b/src/base/synchronization/waitable_event_watcher_win.cc new file mode 100644 index 0000000..43e3c47 --- /dev/null +++ b/src/base/synchronization/waitable_event_watcher_win.cc
@@ -0,0 +1,60 @@ +// 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/synchronization/waitable_event_watcher.h" + +#include "base/compiler_specific.h" +#include "base/synchronization/waitable_event.h" +#include "base/win/object_watcher.h" + +namespace base { + +WaitableEventWatcher::ObjectWatcherHelper::ObjectWatcherHelper( + WaitableEventWatcher* watcher) + : watcher_(watcher) { +}; + +void WaitableEventWatcher::ObjectWatcherHelper::OnObjectSignaled(HANDLE h) { + watcher_->OnObjectSignaled(); +} + + +WaitableEventWatcher::WaitableEventWatcher() + : ALLOW_THIS_IN_INITIALIZER_LIST(helper_(this)), + event_(NULL), + delegate_(NULL) { +} + +WaitableEventWatcher::~WaitableEventWatcher() { +} + +bool WaitableEventWatcher::StartWatching(WaitableEvent* event, + Delegate* delegate) { + delegate_ = delegate; + event_ = event; + + return watcher_.StartWatching(event->handle(), &helper_); +} + +void WaitableEventWatcher::StopWatching() { + delegate_ = NULL; + event_ = NULL; + watcher_.StopWatching(); +} + +WaitableEvent* WaitableEventWatcher::GetWatchedEvent() { + return event_; +} + +void WaitableEventWatcher::OnObjectSignaled() { + WaitableEvent* event = event_; + Delegate* delegate = delegate_; + event_ = NULL; + delegate_ = NULL; + DCHECK(event); + + delegate->OnWaitableEventSignaled(event); +} + +} // namespace base
diff --git a/src/base/synchronization/waitable_event_win.cc b/src/base/synchronization/waitable_event_win.cc new file mode 100644 index 0000000..28f8fc3 --- /dev/null +++ b/src/base/synchronization/waitable_event_win.cc
@@ -0,0 +1,102 @@ +// 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/synchronization/waitable_event.h" + +#include <math.h> +#include <windows.h> + +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" +#include "base/time.h" + +namespace base { + +WaitableEvent::WaitableEvent(bool manual_reset, bool signaled) + : handle_(CreateEvent(NULL, manual_reset, signaled, NULL)) { + // We're probably going to crash anyways if this is ever NULL, so we might as + // well make our stack reports more informative by crashing here. + CHECK(handle_); +} + +WaitableEvent::WaitableEvent(HANDLE handle) + : handle_(handle) { + CHECK(handle) << "Tried to create WaitableEvent from NULL handle"; +} + +WaitableEvent::~WaitableEvent() { + CloseHandle(handle_); +} + +HANDLE WaitableEvent::Release() { + HANDLE rv = handle_; + handle_ = INVALID_HANDLE_VALUE; + return rv; +} + +void WaitableEvent::Reset() { + ResetEvent(handle_); +} + +void WaitableEvent::Signal() { + SetEvent(handle_); +} + +bool WaitableEvent::IsSignaled() { + return TimedWait(TimeDelta::FromMilliseconds(0)); +} + +void WaitableEvent::Wait() { + base::ThreadRestrictions::AssertWaitAllowed(); + DWORD result = WaitForSingleObject(handle_, INFINITE); + // It is most unexpected that this should ever fail. Help consumers learn + // about it if it should ever fail. + DCHECK_EQ(WAIT_OBJECT_0, result) << "WaitForSingleObject failed"; +} + +bool WaitableEvent::TimedWait(const TimeDelta& max_time) { + base::ThreadRestrictions::AssertWaitAllowed(); + DCHECK(max_time >= TimeDelta::FromMicroseconds(0)); + // Be careful here. TimeDelta has a precision of microseconds, but this API + // is in milliseconds. If there are 5.5ms left, should the delay be 5 or 6? + // It should be 6 to avoid returning too early. + double timeout = ceil(max_time.InMillisecondsF()); + DWORD result = WaitForSingleObject(handle_, static_cast<DWORD>(timeout)); + switch (result) { + case WAIT_OBJECT_0: + return true; + case WAIT_TIMEOUT: + return false; + } + // It is most unexpected that this should ever fail. Help consumers learn + // about it if it should ever fail. + NOTREACHED() << "WaitForSingleObject failed"; + return false; +} + +// static +size_t WaitableEvent::WaitMany(WaitableEvent** events, size_t count) { + base::ThreadRestrictions::AssertWaitAllowed(); + HANDLE handles[MAXIMUM_WAIT_OBJECTS]; + CHECK_LE(count, MAXIMUM_WAIT_OBJECTS) + << "Can only wait on " << MAXIMUM_WAIT_OBJECTS << " with WaitMany"; + + for (size_t i = 0; i < count; ++i) + handles[i] = events[i]->handle(); + + // The cast is safe because count is small - see the CHECK above. + DWORD result = + WaitForMultipleObjects(static_cast<DWORD>(count), + handles, + FALSE, // don't wait for all the objects + INFINITE); // no timeout + if (result >= WAIT_OBJECT_0 + count) { + NOTREACHED() << "WaitForMultipleObjects failed: " << GetLastError(); + return 0; + } + + return result - WAIT_OBJECT_0; +} + +} // namespace base
diff --git a/src/base/sys_byteorder.h b/src/base/sys_byteorder.h new file mode 100644 index 0000000..5548347 --- /dev/null +++ b/src/base/sys_byteorder.h
@@ -0,0 +1,155 @@ +// 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. + +// This header defines cross-platform ByteSwap() implementations for 16, 32 and +// 64-bit values, and NetToHostXX() / HostToNextXX() functions equivalent to +// the traditional ntohX() and htonX() functions. +// Use the functions defined here rather than using the platform-specific +// functions directly. + +#ifndef BASE_SYS_BYTEORDER_H_ +#define BASE_SYS_BYTEORDER_H_ + +#include "base/basictypes.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <winsock2.h> +#else +#include <arpa/inet.h> +#endif + +// Include headers to provide byteswap for all platforms. +#if defined(OS_STARBOARD) +#include "starboard/byte_swap.h" +#elif defined(COMPILER_MSVC) +#include <stdlib.h> +#elif defined(OS_MACOSX) +#include <libkern/OSByteOrder.h> +#elif defined(OS_OPENBSD) +#include <sys/endian.h> +#else +#include <byteswap.h> +#endif + + +namespace base { + +// Returns a value with all bytes in |x| swapped, i.e. reverses the endianness. +inline uint16 ByteSwap(uint16 x) { +#if defined(OS_STARBOARD) + return SbByteSwapU16(x); +#elif defined(COMPILER_MSVC) + return _byteswap_ushort(x); +#elif defined(OS_MACOSX) + return OSSwapInt16(x); +#elif defined(OS_OPENBSD) + return swap16(x); +#else + return bswap_16(x); +#endif +} +inline uint32 ByteSwap(uint32 x) { +#if defined(OS_STARBOARD) + return SbByteSwapU32(x); +#elif defined(COMPILER_MSVC) + return _byteswap_ulong(x); +#elif defined(OS_MACOSX) + return OSSwapInt32(x); +#elif defined(OS_OPENBSD) + return swap32(x); +#else + return bswap_32(x); +#endif +} +inline uint64 ByteSwap(uint64 x) { +#if defined(OS_STARBOARD) + return SbByteSwapU64(x); +#elif defined(COMPILER_MSVC) + return _byteswap_uint64(x); +#elif defined(OS_MACOSX) + return OSSwapInt64(x); +#elif defined(OS_OPENBSD) + return swap64(x); +#else + return bswap_64(x); +#endif +} + +// Converts the bytes in |x| from host order (endianness) to little endian, and +// returns the result. +inline uint16 ByteSwapToLE16(uint16 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return x; +#else + return ByteSwap(x); +#endif +} +inline uint32 ByteSwapToLE32(uint32 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return x; +#else + return ByteSwap(x); +#endif +} +inline uint64 ByteSwapToLE64(uint64 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return x; +#else + return ByteSwap(x); +#endif +} + +// Converts the bytes in |x| from network to host order (endianness), and +// returns the result. +inline uint16 NetToHost16(uint16 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} +inline uint32 NetToHost32(uint32 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} +inline uint64 NetToHost64(uint64 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} + +// Converts the bytes in |x| from host to network order (endianness), and +// returns the result. +inline uint16 HostToNet16(uint16 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} +inline uint32 HostToNet32(uint32 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} +inline uint64 HostToNet64(uint64 x) { +#if defined(ARCH_CPU_LITTLE_ENDIAN) + return ByteSwap(x); +#else + return x; +#endif +} + +} // namespace base + + +#endif // BASE_SYS_BYTEORDER_H_
diff --git a/src/base/sys_info.cc b/src/base/sys_info.cc new file mode 100644 index 0000000..fa940bf --- /dev/null +++ b/src/base/sys_info.cc
@@ -0,0 +1,20 @@ +// 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/sys_info.h" + +#include "base/time.h" + +namespace base { + +// static +int64 SysInfo::Uptime() { + // This code relies on an implementation detail of TimeTicks::Now() - that + // its return value happens to coincide with the system uptime value in + // microseconds, on Win/Mac/iOS/Linux/ChromeOS and Android. + int64 uptime_in_microseconds = TimeTicks::Now().ToInternalValue(); + return uptime_in_microseconds / 1000; +} + +} // namespace base
diff --git a/src/base/sys_info.h b/src/base/sys_info.h new file mode 100644 index 0000000..e653590 --- /dev/null +++ b/src/base/sys_info.h
@@ -0,0 +1,118 @@ +// 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_SYS_INFO_H_ +#define BASE_SYS_INFO_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/file_path.h" +#include "build/build_config.h" + +namespace base { + +class BASE_EXPORT SysInfo { + public: + // Return the number of logical processors/cores on the current machine. + static int NumberOfProcessors(); + + // Return the number of bytes of physical memory on the current machine. + static int64 AmountOfPhysicalMemory(); + +#if !defined(OS_STARBOARD) + // Return the number of bytes of current available physical memory on the + // machine. + static int64 AmountOfAvailablePhysicalMemory(); +#endif + + // Return the number of megabytes of physical memory on the current machine. + static int AmountOfPhysicalMemoryMB() { + return static_cast<int>(AmountOfPhysicalMemory() / 1024 / 1024); + } + + // Return the available disk space in bytes on the volume containing |path|, + // or -1 on failure. + static int64 AmountOfFreeDiskSpace(const FilePath& path); + + // Returns system uptime in milliseconds. + static int64 Uptime(); + +#if !defined(OS_STARBOARD) + // Returns the name of the host operating system. + static std::string OperatingSystemName(); + + // Returns the version of the host operating system. + static std::string OperatingSystemVersion(); +#endif + + // Retrieves detailed numeric values for the OS version. + // TODO(port): Implement a Linux version of this method and enable the + // corresponding unit test. + // DON'T USE THIS ON THE MAC OR WINDOWS to determine the current OS release + // for OS version-specific feature checks and workarounds. If you must use + // an OS version check instead of a feature check, use the base::mac::IsOS* + // family from base/mac/mac_util.h, or base::win::GetVersion from + // base/win/windows_version.h. + static void OperatingSystemVersionNumbers(int32* major_version, + int32* minor_version, + int32* bugfix_version); + +#if !defined(OS_STARBOARD) + // Returns the architecture of the running operating system. + // Exact return value may differ across platforms. + // e.g. a 32-bit x86 kernel on a 64-bit capable CPU will return "x86", + // whereas a x86-64 kernel on the same CPU will return "x86_64" + static std::string OperatingSystemArchitecture(); + + // Avoid using this. Use base/cpu.h to get information about the CPU instead. + // http://crbug.com/148884 + // Returns the CPU model name of the system. If it can not be figured out, + // an empty string is returned. + static std::string CPUModelName(); + + // Return the smallest amount of memory (in bytes) which the VM system will + // allocate. + static size_t VMAllocationGranularity(); +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) + // Returns the maximum SysV shared memory segment size. + 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(); +#endif // defined(OS_CHROMEOS) + +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) + // Returns the Android build's codename. + static std::string GetAndroidBuildCodename(); + + // Returns the Android build ID. + static std::string GetAndroidBuildID(); + + // Returns the device's name. + static std::string GetDeviceName(); + + static int DalvikHeapSizeMB(); +#endif // defined(OS_ANDROID) +}; + +} // namespace base + +#endif // BASE_SYS_INFO_H_
diff --git a/src/base/sys_info_android.cc b/src/base/sys_info_android.cc new file mode 100644 index 0000000..c0bbe44 --- /dev/null +++ b/src/base/sys_info_android.cc
@@ -0,0 +1,125 @@ +// 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/sys_info.h" + +#include <sys/system_properties.h> + +#include "base/logging.h" +#include "base/string_number_conversions.h" +#include "base/string_piece.h" + +namespace { + +// Default version of Android to fall back to when actual version numbers +// cannot be acquired. +// TODO(dfalcantara): Keep this reasonably up to date with the latest publicly +// available version of Android. +static const int kDefaultAndroidMajorVersion = 4; +static const int kDefaultAndroidMinorVersion = 0; +static const int kDefaultAndroidBugfixVersion = 3; + +// Parse out the OS version numbers from the system properties. +void ParseOSVersionNumbers(const char* os_version_str, + int32 *major_version, + int32 *minor_version, + int32 *bugfix_version) { + if (os_version_str[0]) { + // Try to parse out the version numbers from the string. + int num_read = sscanf(os_version_str, "%d.%d.%d", major_version, + minor_version, bugfix_version); + + if (num_read > 0) { + // If we don't have a full set of version numbers, make the extras 0. + if (num_read < 2) *minor_version = 0; + if (num_read < 3) *bugfix_version = 0; + return; + } + } + + // For some reason, we couldn't parse the version number string. + *major_version = kDefaultAndroidMajorVersion; + *minor_version = kDefaultAndroidMinorVersion; + *bugfix_version = kDefaultAndroidBugfixVersion; +} + +int ParseHeapSize(const base::StringPiece& str) { + const int64 KB = 1024; + const int64 MB = 1024 * KB; + const int64 GB = 1024 * MB; + CHECK_GT(str.size(), 0u); + int64 factor = 1; + size_t length = str.size(); + if (str[length - 1] == 'k') { + factor = KB; + length--; + } else if (str[length - 1] == 'm') { + factor = MB; + length--; + } else if (str[length - 1] == 'g') { + factor = GB; + length--; + } else { + CHECK('0' <= str[length - 1] && str[length - 1] <= '9'); + } + int64 result = 0; + bool parsed = base::StringToInt64(str.substr(0, length), &result); + CHECK(parsed); + result = result * factor / MB; + // dalvik.vm.heapsize property is writable by user, + // truncate it to reasonable value to avoid overflows later. + result = std::min<int64>(std::max<int64>(32, result), 1024); + return static_cast<int>(result); +} + +int GetDalvikHeapSizeMB() { + char heap_size_str[PROP_VALUE_MAX]; + __system_property_get("dalvik.vm.heapsize", heap_size_str); + return ParseHeapSize(heap_size_str); +} + +} // anonymous namespace + +namespace base { + +std::string SysInfo::OperatingSystemName() { + return "Android"; +} + +std::string SysInfo::GetAndroidBuildCodename() { + char os_version_codename_str[PROP_VALUE_MAX]; + __system_property_get("ro.build.version.codename", os_version_codename_str); + return std::string(os_version_codename_str); +} + +std::string SysInfo::GetAndroidBuildID() { + char os_build_id_str[PROP_VALUE_MAX]; + __system_property_get("ro.build.id", os_build_id_str); + return std::string(os_build_id_str); +} + +std::string SysInfo::GetDeviceName() { + char device_model_str[PROP_VALUE_MAX]; + __system_property_get("ro.product.model", device_model_str); + return std::string(device_model_str); +} + +void SysInfo::OperatingSystemVersionNumbers(int32* major_version, + int32* minor_version, + int32* bugfix_version) { + // Read the version number string out from the properties. + char os_version_str[PROP_VALUE_MAX]; + __system_property_get("ro.build.version.release", os_version_str); + + // Parse out the numbers. + ParseOSVersionNumbers(os_version_str, major_version, minor_version, + bugfix_version); +} + +int SysInfo::DalvikHeapSizeMB() { + static int heap_size = GetDalvikHeapSizeMB(); + return heap_size; +} + +} // namespace base
diff --git a/src/base/sys_info_chromeos.cc b/src/base/sys_info_chromeos.cc new file mode 100644 index 0000000..92a3f0c --- /dev/null +++ b/src/base/sys_info_chromeos.cc
@@ -0,0 +1,121 @@ +// 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/sys_info.h" + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/string_number_conversions.h" +#include "base/string_piece.h" +#include "base/string_tokenizer.h" +#include "base/threading/thread_restrictions.h" + +#include <execinfo.h> + +namespace base { + +static const char* kLinuxStandardBaseVersionKeys[] = { + "CHROMEOS_RELEASE_VERSION", + "GOOGLE_RELEASE", + "DISTRIB_RELEASE", + NULL +}; + +const char kLinuxStandardBaseReleaseFile[] = "/etc/lsb-release"; + +struct ChromeOSVersionNumbers { + ChromeOSVersionNumbers() + : major_version(0), + minor_version(0), + bugfix_version(0), + parsed(false) { + } + + int32 major_version; + int32 minor_version; + int32 bugfix_version; + bool parsed; +}; + +static base::LazyInstance<ChromeOSVersionNumbers> + g_chrome_os_version_numbers = LAZY_INSTANCE_INITIALIZER; + +// 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. + base::ThreadRestrictions::ScopedAllowIO allow_io; + + FilePath path(kLinuxStandardBaseReleaseFile); + std::string contents; + if (file_util::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; +} + +// static +std::string SysInfo::GetLinuxStandardBaseVersionKey() { + return std::string(kLinuxStandardBaseVersionKeys[0]); +} + +// 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; + } + + 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 +FilePath SysInfo::GetLsbReleaseFilePath() { + return FilePath(kLinuxStandardBaseReleaseFile); +} + +} // namespace base
diff --git a/src/base/sys_info_freebsd.cc b/src/base/sys_info_freebsd.cc new file mode 100644 index 0000000..832b359 --- /dev/null +++ b/src/base/sys_info_freebsd.cc
@@ -0,0 +1,36 @@ +// 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/sys_info.h" + +#include <sys/sysctl.h> + +#include "base/logging.h" + +namespace base { + +int64 SysInfo::AmountOfPhysicalMemory() { + int pages, page_size; + size_t size = sizeof(pages); + sysctlbyname("vm.stats.vm.v_page_count", &pages, &size, NULL, 0); + sysctlbyname("vm.stats.vm.v_page_size", &page_size, &size, NULL, 0); + if (pages == -1 || page_size == -1) { + NOTREACHED(); + return 0; + } + return static_cast<int64>(pages) * page_size; +} + +// static +size_t SysInfo::MaxSharedMemorySize() { + size_t limit; + size_t size = sizeof(limit); + if (sysctlbyname("kern.ipc.shmmax", &limit, &size, NULL, 0) < 0) { + NOTREACHED(); + return 0; + } + return limit; +} + +} // namespace base
diff --git a/src/base/sys_info_ios.mm b/src/base/sys_info_ios.mm new file mode 100644 index 0000000..7863f6b --- /dev/null +++ b/src/base/sys_info_ios.mm
@@ -0,0 +1,90 @@ +// 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/sys_info.h" + +#import <UIKit/UIKit.h> +#include <mach/mach.h> +#include <sys/sysctl.h> +#include <sys/types.h> + +#include "base/logging.h" +#include "base/mac/scoped_mach_port.h" +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/sys_string_conversions.h" + +namespace base { + +// static +std::string SysInfo::OperatingSystemName() { + static dispatch_once_t get_system_name_once; + static std::string* system_name; + dispatch_once(&get_system_name_once, ^{ + base::mac::ScopedNSAutoreleasePool pool; + system_name = new std::string( + SysNSStringToUTF8([[UIDevice currentDevice] systemName])); + }); + // Examples of returned value: 'iPhone OS' on iPad 5.1.1 + // and iPhone 5.1.1. + return *system_name; +} + +// static +std::string SysInfo::OperatingSystemVersion() { + static dispatch_once_t get_system_version_once; + static std::string* system_version; + dispatch_once(&get_system_version_once, ^{ + base::mac::ScopedNSAutoreleasePool pool; + system_version = new std::string( + SysNSStringToUTF8([[UIDevice currentDevice] systemVersion])); + }); + return *system_version; +} + +// static +void SysInfo::OperatingSystemVersionNumbers(int32* major_version, + int32* minor_version, + int32* bugfix_version) { + base::mac::ScopedNSAutoreleasePool pool; + std::string system_version = OperatingSystemVersion(); + if (!system_version.empty()) { + // Try to parse out the version numbers from the string. + int num_read = sscanf(system_version.c_str(), "%d.%d.%d", major_version, + minor_version, bugfix_version); + if (num_read < 1) + *major_version = 0; + if (num_read < 2) + *minor_version = 0; + if (num_read < 3) + *bugfix_version = 0; + } +} + +// static +int64 SysInfo::AmountOfPhysicalMemory() { + struct host_basic_info hostinfo; + mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; + base::mac::ScopedMachPort host(mach_host_self()); + int result = host_info(host, + HOST_BASIC_INFO, + reinterpret_cast<host_info_t>(&hostinfo), + &count); + if (result != KERN_SUCCESS) { + NOTREACHED(); + return 0; + } + DCHECK_EQ(HOST_BASIC_INFO_COUNT, count); + return static_cast<int64>(hostinfo.max_mem); +} + +// static +std::string SysInfo::CPUModelName() { + char name[256]; + size_t len = arraysize(name); + if (sysctlbyname("machdep.cpu.brand_string", &name, &len, NULL, 0) == 0) + return name; + return std::string(); +} + +} // namespace base
diff --git a/src/base/sys_info_linux.cc b/src/base/sys_info_linux.cc new file mode 100644 index 0000000..6683674 --- /dev/null +++ b/src/base/sys_info_linux.cc
@@ -0,0 +1,79 @@ +// 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/sys_info.h" + +#include <limits> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/string_number_conversions.h" + +namespace base { + +int64 SysInfo::AmountOfPhysicalMemory() { + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + if (pages == -1 || page_size == -1) { + NOTREACHED(); + return 0; + } + + return static_cast<int64>(pages) * page_size; +} + +// static +int64 SysInfo::AmountOfAvailablePhysicalMemory() { + long available_pages = sysconf(_SC_AVPHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + if (available_pages == -1 || page_size == -1) { + NOTREACHED(); + return 0; + } + return static_cast<int64>(available_pages) * page_size; +} + +// static +size_t SysInfo::MaxSharedMemorySize() { + static int64 limit; + static bool limit_valid = false; + if (!limit_valid) { + std::string contents; + file_util::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); +} + +// static +std::string SysInfo::CPUModelName() { + const char kModelNamePrefix[] = "model name"; + std::string contents; + file_util::ReadFileToString(FilePath("/proc/cpuinfo"), &contents); + DCHECK(!contents.empty()); + if (!contents.empty()) { + std::istringstream iss(contents); + std::string line; + while (std::getline(iss, line)){ + if (line.compare(0, strlen(kModelNamePrefix), kModelNamePrefix) == 0) { + size_t pos = line.find(": "); + return line.substr(pos + 2); + } + } + } + return std::string(); +} + +} // namespace base
diff --git a/src/base/sys_info_mac.cc b/src/base/sys_info_mac.cc new file mode 100644 index 0000000..b444565 --- /dev/null +++ b/src/base/sys_info_mac.cc
@@ -0,0 +1,88 @@ +// 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/sys_info.h" + +#include <ApplicationServices/ApplicationServices.h> +#include <CoreServices/CoreServices.h> +#include <mach/mach_host.h> +#include <mach/mach_init.h> +#include <sys/sysctl.h> +#include <sys/types.h> + +#include "base/logging.h" +#include "base/mac/scoped_mach_port.h" +#include "base/stringprintf.h" + +namespace base { + +// static +std::string SysInfo::OperatingSystemName() { + return "Mac OS X"; +} + +// static +std::string SysInfo::OperatingSystemVersion() { + int32 major, minor, bugfix; + OperatingSystemVersionNumbers(&major, &minor, &bugfix); + return base::StringPrintf("%d.%d.%d", major, minor, bugfix); +} + +// static +void SysInfo::OperatingSystemVersionNumbers(int32* major_version, + int32* minor_version, + int32* bugfix_version) { + Gestalt(gestaltSystemVersionMajor, + reinterpret_cast<SInt32*>(major_version)); + Gestalt(gestaltSystemVersionMinor, + reinterpret_cast<SInt32*>(minor_version)); + Gestalt(gestaltSystemVersionBugFix, + reinterpret_cast<SInt32*>(bugfix_version)); +} + +// static +int64 SysInfo::AmountOfPhysicalMemory() { + struct host_basic_info hostinfo; + mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; + base::mac::ScopedMachPort host(mach_host_self()); + int result = host_info(host, + HOST_BASIC_INFO, + reinterpret_cast<host_info_t>(&hostinfo), + &count); + if (result != KERN_SUCCESS) { + NOTREACHED(); + return 0; + } + DCHECK_EQ(HOST_BASIC_INFO_COUNT, count); + return static_cast<int64>(hostinfo.max_mem); +} + +// static +int64 SysInfo::AmountOfAvailablePhysicalMemory() { + base::mac::ScopedMachPort host(mach_host_self()); + vm_statistics_data_t vm_info; + mach_msg_type_number_t count = HOST_VM_INFO_COUNT; + + if (host_statistics(host.get(), + HOST_VM_INFO, + reinterpret_cast<host_info_t>(&vm_info), + &count) != KERN_SUCCESS) { + NOTREACHED(); + return 0; + } + + return static_cast<int64>( + vm_info.free_count - vm_info.speculative_count) * PAGE_SIZE; +} + +// static +std::string SysInfo::CPUModelName() { + char name[256]; + size_t len = arraysize(name); + if (sysctlbyname("machdep.cpu.brand_string", &name, &len, NULL, 0) == 0) + return name; + return std::string(); +} + +} // namespace base
diff --git a/src/base/sys_info_openbsd.cc b/src/base/sys_info_openbsd.cc new file mode 100644 index 0000000..b9aec20 --- /dev/null +++ b/src/base/sys_info_openbsd.cc
@@ -0,0 +1,48 @@ +// 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/sys_info.h" + +#include <sys/param.h> +#include <sys/shm.h> +#include <sys/sysctl.h> + +#include "base/logging.h" + +namespace base { + +int SysInfo::NumberOfProcessors() { + int mib[] = { CTL_HW, HW_NCPU }; + int ncpu; + size_t size = sizeof(ncpu); + if (sysctl(mib, arraysize(mib), &ncpu, &size, NULL, 0) == -1) { + NOTREACHED(); + return 1; + } + return ncpu; +} + +int64 SysInfo::AmountOfPhysicalMemory() { + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGESIZE); + if (pages == -1 || page_size == -1) { + NOTREACHED(); + return 0; + } + + return static_cast<int64>(pages) * page_size; +} + +size_t SysInfo::MaxSharedMemorySize() { + int mib[] = { CTL_KERN, KERN_SHMINFO, KERN_SHMINFO_SHMMAX }; + size_t limit; + size_t size = sizeof(limit); + if (sysctl(mib, arraysize(mib), &limit, &size, NULL, 0) < 0) { + NOTREACHED(); + return 0; + } + return limit; +} + +} // namespace base
diff --git a/src/base/sys_info_posix.cc b/src/base/sys_info_posix.cc new file mode 100644 index 0000000..f2119cd --- /dev/null +++ b/src/base/sys_info_posix.cc
@@ -0,0 +1,98 @@ +// 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/sys_info.h" + +#include <errno.h> +#include <string.h> +#include <sys/param.h> +#include <sys/utsname.h> +#include <unistd.h> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" +#include "base/utf_string_conversions.h" + +#if defined(OS_ANDROID) +#include <sys/vfs.h> +#define statvfs statfs // Android uses a statvfs-like statfs struct and call. +#else +#include <sys/statvfs.h> +#endif + +namespace base { + +#if !defined(OS_OPENBSD) +int SysInfo::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); + if (res == -1) { + NOTREACHED(); + return 1; + } + + return static_cast<int>(res); +} +#endif + +// static +int64 SysInfo::AmountOfFreeDiskSpace(const FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + + struct statvfs stats; + if (statvfs(path.value().c_str(), &stats) != 0) { + return -1; + } + return static_cast<int64>(stats.f_bavail) * stats.f_frsize; +} + +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) +// static +std::string SysInfo::OperatingSystemName() { + struct utsname info; + if (uname(&info) < 0) { + NOTREACHED(); + return ""; + } + return std::string(info.sysname); +} +#endif + +#if !defined(OS_MACOSX) +// static +std::string SysInfo::OperatingSystemVersion() { + struct utsname info; + if (uname(&info) < 0) { + NOTREACHED(); + return ""; + } + return std::string(info.release); +} +#endif + +// static +std::string SysInfo::OperatingSystemArchitecture() { + struct utsname info; + if (uname(&info) < 0) { + NOTREACHED(); + return ""; + } + std::string arch(info.machine); + if (arch == "i386" || arch == "i486" || arch == "i586" || arch == "i686") { + arch = "x86"; + } else if (arch == "amd64") { + arch = "x86_64"; + } + return arch; +} + +// static +size_t SysInfo::VMAllocationGranularity() { + return getpagesize(); +} + +} // namespace base
diff --git a/src/base/sys_info_starboard.cc b/src/base/sys_info_starboard.cc new file mode 100644 index 0000000..30b5f75 --- /dev/null +++ b/src/base/sys_info_starboard.cc
@@ -0,0 +1,43 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/sys_info.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "starboard/system.h" + +namespace base { + +// static +int SysInfo::NumberOfProcessors() { + return SbSystemGetNumberOfProcessors(); +} + +// static +int64 SysInfo::AmountOfFreeDiskSpace(const FilePath& path) { + // TODO: This is referred to ONLY by disk_cache::BackendImpl, which I do + // not think is currently used in Cobalt. There's no need to implement this + // unless we want to use it for something. If not, we should remove the + // reference to it, and this amazing implementation. + NOTIMPLEMENTED(); + return SB_INT64_C(1) * 1024 * 1024 * 1024; +} + +// static +int64 SysInfo::AmountOfPhysicalMemory() { + return SbSystemGetTotalMemory(); +} + +} // namespace base
diff --git a/src/base/sys_info_unittest.cc b/src/base/sys_info_unittest.cc new file mode 100644 index 0000000..04404d4 --- /dev/null +++ b/src/base/sys_info_unittest.cc
@@ -0,0 +1,114 @@ +// 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/sys_info.h" +#include "base/threading/platform_thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +typedef PlatformTest SysInfoTest; + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) && !defined(__LB_SHELL__) +TEST_F(SysInfoTest, MaxSharedMemorySize) { + // We aren't actually testing that it's correct, just that it's sane. + EXPECT_GT(base::SysInfo::MaxSharedMemorySize(), 0u); +} +#endif + +TEST_F(SysInfoTest, NumProcs) { + // We aren't actually testing that it's correct, just that it's sane. + EXPECT_GE(base::SysInfo::NumberOfProcessors(), 1); +} + +TEST_F(SysInfoTest, AmountOfMem) { + // We aren't actually testing that it's correct, just that it's sane. + EXPECT_GT(base::SysInfo::AmountOfPhysicalMemory(), 0); + EXPECT_GT(base::SysInfo::AmountOfPhysicalMemoryMB(), 0); +} + +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) +// Disk space queries are not available cross-platform. + +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)); + EXPECT_GT(base::SysInfo::AmountOfFreeDiskSpace(tmp_path), 0) + << tmp_path.value(); +} +#endif + +#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS) +TEST_F(SysInfoTest, OperatingSystemVersionNumbers) { + int32 os_major_version = -1; + int32 os_minor_version = -1; + int32 os_bugfix_version = -1; + base::SysInfo::OperatingSystemVersionNumbers(&os_major_version, + &os_minor_version, + &os_bugfix_version); + EXPECT_GT(os_major_version, -1); + EXPECT_GT(os_minor_version, -1); + EXPECT_GT(os_bugfix_version, -1); +} +#endif + +TEST_F(SysInfoTest, Uptime) { + int64 up_time_1 = base::SysInfo::Uptime(); + // UpTime() is implemented internally using TimeTicks::Now(), which documents + // system resolution as being 1-15ms. Sleep a little longer than that. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20)); + int64 up_time_2 = base::SysInfo::Uptime(); + EXPECT_GT(up_time_1, 0); + EXPECT_GT(up_time_2, up_time_1); +} + +#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); + EXPECT_EQ(1, os_major_version); + EXPECT_EQ(2, os_minor_version); + EXPECT_EQ(3, os_bugfix_version); +} + +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); + EXPECT_EQ(1, os_major_version); + EXPECT_EQ(2, os_minor_version); + EXPECT_EQ(3, os_bugfix_version); +} + +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); +} + +#endif // OS_CHROMEOS
diff --git a/src/base/sys_info_win.cc b/src/base/sys_info_win.cc new file mode 100644 index 0000000..98d2f7c --- /dev/null +++ b/src/base/sys_info_win.cc
@@ -0,0 +1,127 @@ +// 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/sys_info.h" + +#include <windows.h> + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/stringprintf.h" +#include "base/threading/thread_restrictions.h" +#include "base/win/windows_version.h" + +namespace base { + +// static +int SysInfo::NumberOfProcessors() { + return win::OSInfo::GetInstance()->processors(); +} + +// static +int64 SysInfo::AmountOfPhysicalMemory() { + MEMORYSTATUSEX memory_info; + memory_info.dwLength = sizeof(memory_info); + if (!GlobalMemoryStatusEx(&memory_info)) { + NOTREACHED(); + return 0; + } + + int64 rv = static_cast<int64>(memory_info.ullTotalPhys); + if (rv < 0) + rv = kint64max; + return rv; +} + +// static +int64 SysInfo::AmountOfAvailablePhysicalMemory() { + MEMORYSTATUSEX memory_info; + memory_info.dwLength = sizeof(memory_info); + if (!GlobalMemoryStatusEx(&memory_info)) { + NOTREACHED(); + return 0; + } + + int64 rv = static_cast<int64>(memory_info.ullAvailPhys); + if (rv < 0) + rv = kint64max; + return rv; +} + +// static +int64 SysInfo::AmountOfFreeDiskSpace(const FilePath& path) { + base::ThreadRestrictions::AssertIOAllowed(); + + ULARGE_INTEGER available, total, free; + if (!GetDiskFreeSpaceExW(path.value().c_str(), &available, &total, &free)) { + return -1; + } + int64 rv = static_cast<int64>(available.QuadPart); + if (rv < 0) + rv = kint64max; + return rv; +} + +// static +std::string SysInfo::OperatingSystemName() { + return "Windows NT"; +} + +// static +std::string SysInfo::OperatingSystemVersion() { + win::OSInfo* os_info = win::OSInfo::GetInstance(); + win::OSInfo::VersionNumber version_number = os_info->version_number(); + std::string version(StringPrintf("%d.%d", version_number.major, + version_number.minor)); + win::OSInfo::ServicePack service_pack = os_info->service_pack(); + if (service_pack.major != 0) { + version += StringPrintf(" SP%d", service_pack.major); + if (service_pack.minor != 0) + version += StringPrintf(".%d", service_pack.minor); + } + return version; +} + +// TODO: Implement OperatingSystemVersionComplete, which would include +// patchlevel/service pack number. +// See chrome/browser/feedback/feedback_util.h, FeedbackUtil::SetOSVersion. + +// static +std::string SysInfo::OperatingSystemArchitecture() { + win::OSInfo::WindowsArchitecture arch = + win::OSInfo::GetInstance()->architecture(); + switch (arch) { + case win::OSInfo::X86_ARCHITECTURE: + return "x86"; + case win::OSInfo::X64_ARCHITECTURE: + return "x86_64"; + case win::OSInfo::IA64_ARCHITECTURE: + return "ia64"; + default: + return ""; + } +} + +// static +std::string SysInfo::CPUModelName() { + return win::OSInfo::GetInstance()->processor_model_name(); +} + +// static +size_t SysInfo::VMAllocationGranularity() { + return win::OSInfo::GetInstance()->allocation_granularity(); +} + +// static +void SysInfo::OperatingSystemVersionNumbers(int32* major_version, + int32* minor_version, + int32* bugfix_version) { + win::OSInfo* os_info = win::OSInfo::GetInstance(); + *major_version = os_info->version_number().major; + *minor_version = os_info->version_number().minor; + *bugfix_version = 0; +} + +} // namespace base
diff --git a/src/base/sys_string_conversions.h b/src/base/sys_string_conversions.h new file mode 100644 index 0000000..3f7631c --- /dev/null +++ b/src/base/sys_string_conversions.h
@@ -0,0 +1,83 @@ +// 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_SYS_STRING_CONVERSIONS_H_ +#define BASE_SYS_STRING_CONVERSIONS_H_ + +// Provides system-dependent string type conversions for cases where it's +// necessary to not use ICU. Generally, you should not need this in Chrome, +// but it is used in some shared code. Dependencies should be minimal. + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/string16.h" +#include "base/string_piece.h" + +#if defined(OS_MACOSX) +#include <CoreFoundation/CoreFoundation.h> +#ifdef __OBJC__ +@class NSString; +#else +class NSString; +#endif +#endif // OS_MACOSX + +namespace base { + +// Converts between wide and UTF-8 representations of a string. On error, the +// result is system-dependent. +BASE_EXPORT std::string SysWideToUTF8(const std::wstring& wide); +BASE_EXPORT std::wstring SysUTF8ToWide(const StringPiece& utf8); + +// Converts between wide and the system multi-byte representations of a string. +// DANGER: This will lose information and can change (on Windows, this can +// change between reboots). +BASE_EXPORT std::string SysWideToNativeMB(const std::wstring& wide); +BASE_EXPORT std::wstring SysNativeMBToWide(const StringPiece& native_mb); + +// Windows-specific ------------------------------------------------------------ + +#if defined(OS_WIN) + +// Converts between 8-bit and wide strings, using the given code page. The +// code page identifier is one accepted by the Windows function +// MultiByteToWideChar(). +BASE_EXPORT std::wstring SysMultiByteToWide(const StringPiece& mb, + uint32 code_page); +BASE_EXPORT std::string SysWideToMultiByte(const std::wstring& wide, + uint32 code_page); + +#endif // defined(OS_WIN) + +// Mac-specific ---------------------------------------------------------------- + +#if defined(OS_MACOSX) + +// Converts between STL strings and CFStringRefs/NSStrings. + +// Creates a string, and returns it with a refcount of 1. You are responsible +// for releasing it. Returns NULL on failure. +BASE_EXPORT CFStringRef SysUTF8ToCFStringRef(const std::string& utf8); +BASE_EXPORT CFStringRef SysUTF16ToCFStringRef(const string16& utf16); + +// Same, but returns an autoreleased NSString. +BASE_EXPORT NSString* SysUTF8ToNSString(const std::string& utf8); +BASE_EXPORT NSString* SysUTF16ToNSString(const string16& utf16); + +// Converts a CFStringRef to an STL string. Returns an empty string on failure. +BASE_EXPORT std::string SysCFStringRefToUTF8(CFStringRef ref); +BASE_EXPORT string16 SysCFStringRefToUTF16(CFStringRef ref); + +// Same, but accepts NSString input. Converts nil NSString* to the appropriate +// string type of length 0. +BASE_EXPORT std::string SysNSStringToUTF8(NSString* ref); +BASE_EXPORT string16 SysNSStringToUTF16(NSString* ref); + +#endif // defined(OS_MACOSX) + +} // namespace base + +#endif // BASE_SYS_STRING_CONVERSIONS_H_
diff --git a/src/base/sys_string_conversions_mac.mm b/src/base/sys_string_conversions_mac.mm new file mode 100644 index 0000000..d1e3743 --- /dev/null +++ b/src/base/sys_string_conversions_mac.mm
@@ -0,0 +1,187 @@ +// 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/sys_string_conversions.h" + +#import <Foundation/Foundation.h> + +#include <vector> + +#include "base/mac/foundation_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/string_piece.h" + +namespace base { + +namespace { + +// Convert the supplied CFString into the specified encoding, and return it as +// an STL string of the template type. Returns an empty string on failure. +// +// Do not assert in this function since it is used by the asssertion code! +template<typename StringType> +static StringType CFStringToSTLStringWithEncodingT(CFStringRef cfstring, + CFStringEncoding encoding) { + CFIndex length = CFStringGetLength(cfstring); + if (length == 0) + return StringType(); + + CFRange whole_string = CFRangeMake(0, length); + CFIndex out_size; + CFIndex converted = CFStringGetBytes(cfstring, + whole_string, + encoding, + 0, // lossByte + false, // isExternalRepresentation + NULL, // buffer + 0, // maxBufLen + &out_size); + if (converted == 0 || out_size == 0) + return StringType(); + + // out_size is the number of UInt8-sized units needed in the destination. + // A buffer allocated as UInt8 units might not be properly aligned to + // contain elements of StringType::value_type. Use a container for the + // proper value_type, and convert out_size by figuring the number of + // value_type elements per UInt8. Leave room for a NUL terminator. + typename StringType::size_type elements = + out_size * sizeof(UInt8) / sizeof(typename StringType::value_type) + 1; + + std::vector<typename StringType::value_type> out_buffer(elements); + converted = CFStringGetBytes(cfstring, + whole_string, + encoding, + 0, // lossByte + false, // isExternalRepresentation + reinterpret_cast<UInt8*>(&out_buffer[0]), + out_size, + NULL); // usedBufLen + if (converted == 0) + return StringType(); + + out_buffer[elements - 1] = '\0'; + return StringType(&out_buffer[0], elements - 1); +} + +// Given an STL string |in| with an encoding specified by |in_encoding|, +// convert it to |out_encoding| and return it as an STL string of the +// |OutStringType| template type. Returns an empty string on failure. +// +// Do not assert in this function since it is used by the asssertion code! +template<typename InStringType, typename OutStringType> +static OutStringType STLStringToSTLStringWithEncodingsT( + const InStringType& in, + CFStringEncoding in_encoding, + CFStringEncoding out_encoding) { + typename InStringType::size_type in_length = in.length(); + if (in_length == 0) + return OutStringType(); + + base::mac::ScopedCFTypeRef<CFStringRef> cfstring( + CFStringCreateWithBytesNoCopy(NULL, + reinterpret_cast<const UInt8*>(in.data()), + in_length * + sizeof(typename InStringType::value_type), + in_encoding, + false, + kCFAllocatorNull)); + if (!cfstring) + return OutStringType(); + + return CFStringToSTLStringWithEncodingT<OutStringType>(cfstring, + out_encoding); +} + +// Given an STL string |in| with an encoding specified by |in_encoding|, +// return it as a CFStringRef. Returns NULL on failure. +template<typename StringType> +static CFStringRef STLStringToCFStringWithEncodingsT( + const StringType& in, + CFStringEncoding in_encoding) { + typename StringType::size_type in_length = in.length(); + if (in_length == 0) + return CFSTR(""); + + return CFStringCreateWithBytes(kCFAllocatorDefault, + reinterpret_cast<const UInt8*>(in.data()), + in_length * + sizeof(typename StringType::value_type), + in_encoding, + false); +} + +// Specify the byte ordering explicitly, otherwise CFString will be confused +// when strings don't carry BOMs, as they typically won't. +static const CFStringEncoding kNarrowStringEncoding = kCFStringEncodingUTF8; +#ifdef __BIG_ENDIAN__ +static const CFStringEncoding kMediumStringEncoding = kCFStringEncodingUTF16BE; +static const CFStringEncoding kWideStringEncoding = kCFStringEncodingUTF32BE; +#elif defined(__LITTLE_ENDIAN__) +static const CFStringEncoding kMediumStringEncoding = kCFStringEncodingUTF16LE; +static const CFStringEncoding kWideStringEncoding = kCFStringEncodingUTF32LE; +#endif // __LITTLE_ENDIAN__ + +} // namespace + +// Do not assert in this function since it is used by the asssertion code! +std::string SysWideToUTF8(const std::wstring& wide) { + return STLStringToSTLStringWithEncodingsT<std::wstring, std::string>( + wide, kWideStringEncoding, kNarrowStringEncoding); +} + +// Do not assert in this function since it is used by the asssertion code! +std::wstring SysUTF8ToWide(const StringPiece& utf8) { + return STLStringToSTLStringWithEncodingsT<StringPiece, std::wstring>( + utf8, kNarrowStringEncoding, kWideStringEncoding); +} + +std::string SysWideToNativeMB(const std::wstring& wide) { + return SysWideToUTF8(wide); +} + +std::wstring SysNativeMBToWide(const StringPiece& native_mb) { + return SysUTF8ToWide(native_mb); +} + +CFStringRef SysUTF8ToCFStringRef(const std::string& utf8) { + return STLStringToCFStringWithEncodingsT(utf8, kNarrowStringEncoding); +} + +CFStringRef SysUTF16ToCFStringRef(const string16& utf16) { + return STLStringToCFStringWithEncodingsT(utf16, kMediumStringEncoding); +} + +NSString* SysUTF8ToNSString(const std::string& utf8) { + return (NSString*)base::mac::CFTypeRefToNSObjectAutorelease( + SysUTF8ToCFStringRef(utf8)); +} + +NSString* SysUTF16ToNSString(const string16& utf16) { + return (NSString*)base::mac::CFTypeRefToNSObjectAutorelease( + SysUTF16ToCFStringRef(utf16)); +} + +std::string SysCFStringRefToUTF8(CFStringRef ref) { + return CFStringToSTLStringWithEncodingT<std::string>(ref, + kNarrowStringEncoding); +} + +string16 SysCFStringRefToUTF16(CFStringRef ref) { + return CFStringToSTLStringWithEncodingT<string16>(ref, + kMediumStringEncoding); +} + +std::string SysNSStringToUTF8(NSString* nsstring) { + if (!nsstring) + return std::string(); + return SysCFStringRefToUTF8(reinterpret_cast<CFStringRef>(nsstring)); +} + +string16 SysNSStringToUTF16(NSString* nsstring) { + if (!nsstring) + return string16(); + return SysCFStringRefToUTF16(reinterpret_cast<CFStringRef>(nsstring)); +} + +} // namespace base
diff --git a/src/base/sys_string_conversions_mac_unittest.mm b/src/base/sys_string_conversions_mac_unittest.mm new file mode 100644 index 0000000..8b0ac88 --- /dev/null +++ b/src/base/sys_string_conversions_mac_unittest.mm
@@ -0,0 +1,18 @@ +// 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. + +#import <Foundation/Foundation.h> + +#include "base/string16.h" +#include "base/sys_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(SysStrings, ConversionsFromNSString) { + EXPECT_STREQ("Hello, world!", + base::SysNSStringToUTF8(@"Hello, world!").c_str()); + + // Conversions should be able to handle a NULL value without crashing. + EXPECT_STREQ("", base::SysNSStringToUTF8(nil).c_str()); + EXPECT_EQ(string16(), base::SysNSStringToUTF16(nil)); +}
diff --git a/src/base/sys_string_conversions_posix.cc b/src/base/sys_string_conversions_posix.cc new file mode 100644 index 0000000..73146e5 --- /dev/null +++ b/src/base/sys_string_conversions_posix.cc
@@ -0,0 +1,162 @@ +// 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/sys_string_conversions.h" + +#include <wchar.h> + +#include "base/string_piece.h" +#include "base/utf_string_conversions.h" + +namespace base { + +std::string SysWideToUTF8(const std::wstring& wide) { + // In theory this should be using the system-provided conversion rather + // than our ICU, but this will do for now. + return WideToUTF8(wide); +} +std::wstring SysUTF8ToWide(const StringPiece& utf8) { + // In theory this should be using the system-provided conversion rather + // than our ICU, but this will do for now. + std::wstring out; + UTF8ToWide(utf8.data(), utf8.size(), &out); + return out; +} + +#if defined(OS_CHROMEOS) || defined(OS_ANDROID) || defined(__LB_SHELL__) || \ + defined(OS_STARBOARD) +// TODO(port): Consider reverting the OS_ANDROID when we have wcrtomb() +// support and a better understanding of what calls these routines. + +// ChromeOS always runs in UTF-8 locale. +std::string SysWideToNativeMB(const std::wstring& wide) { + return WideToUTF8(wide); +} + +std::wstring SysNativeMBToWide(const StringPiece& native_mb) { + return SysUTF8ToWide(native_mb); +} + +#else + +std::string SysWideToNativeMB(const std::wstring& wide) { + mbstate_t ps; + + // Calculate the number of multi-byte characters. We walk through the string + // without writing the output, counting the number of multi-byte characters. + size_t num_out_chars = 0; + memset(&ps, 0, sizeof(ps)); + for (size_t i = 0; i < wide.size(); ++i) { + const wchar_t src = wide[i]; + // Use a temp buffer since calling wcrtomb with an output of NULL does not + // calculate the output length. + char buf[16]; + // Skip NULLs to avoid wcrtomb's special handling of them. + size_t res = src ? wcrtomb(buf, src, &ps) : 0; + switch (res) { + // Handle any errors and return an empty string. + case static_cast<size_t>(-1): + return std::string(); + break; + case 0: + // We hit an embedded null byte, keep going. + ++num_out_chars; + break; + default: + num_out_chars += res; + break; + } + } + + if (num_out_chars == 0) + return std::string(); + + std::string out; + out.resize(num_out_chars); + + // We walk the input string again, with |i| tracking the index of the + // wide input, and |j| tracking the multi-byte output. + memset(&ps, 0, sizeof(ps)); + for (size_t i = 0, j = 0; i < wide.size(); ++i) { + const wchar_t src = wide[i]; + // We don't want wcrtomb to do its funkiness for embedded NULLs. + size_t res = src ? wcrtomb(&out[j], src, &ps) : 0; + switch (res) { + // Handle any errors and return an empty string. + case static_cast<size_t>(-1): + return std::string(); + break; + case 0: + // We hit an embedded null byte, keep going. + ++j; // Output is already zeroed. + break; + default: + j += res; + break; + } + } + + return out; +} + +std::wstring SysNativeMBToWide(const StringPiece& native_mb) { + mbstate_t ps; + + // Calculate the number of wide characters. We walk through the string + // without writing the output, counting the number of wide characters. + size_t num_out_chars = 0; + memset(&ps, 0, sizeof(ps)); + for (size_t i = 0; i < native_mb.size(); ) { + const char* src = native_mb.data() + i; + size_t res = mbrtowc(NULL, src, native_mb.size() - i, &ps); + switch (res) { + // Handle any errors and return an empty string. + case static_cast<size_t>(-2): + case static_cast<size_t>(-1): + return std::wstring(); + break; + case 0: + // We hit an embedded null byte, keep going. + i += 1; // Fall through. + default: + i += res; + ++num_out_chars; + break; + } + } + + if (num_out_chars == 0) + return std::wstring(); + + std::wstring out; + out.resize(num_out_chars); + + memset(&ps, 0, sizeof(ps)); // Clear the shift state. + // We walk the input string again, with |i| tracking the index of the + // multi-byte input, and |j| tracking the wide output. + for (size_t i = 0, j = 0; i < native_mb.size(); ++j) { + const char* src = native_mb.data() + i; + wchar_t* dst = &out[j]; + size_t res = mbrtowc(dst, src, native_mb.size() - i, &ps); + switch (res) { + // Handle any errors and return an empty string. + case static_cast<size_t>(-2): + case static_cast<size_t>(-1): + return std::wstring(); + break; + case 0: + i += 1; // Skip null byte. + break; + default: + i += res; + break; + } + } + + return out; +} + +#endif // OS_CHROMEOS + +} // namespace base
diff --git a/src/base/sys_string_conversions_shell.cc b/src/base/sys_string_conversions_shell.cc new file mode 100644 index 0000000..504204f --- /dev/null +++ b/src/base/sys_string_conversions_shell.cc
@@ -0,0 +1,18 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// We use the same code as posix version +#include "sys_string_conversions_posix.cc"
diff --git a/src/base/sys_string_conversions_starboard.cc b/src/base/sys_string_conversions_starboard.cc new file mode 100644 index 0000000..60357c9 --- /dev/null +++ b/src/base/sys_string_conversions_starboard.cc
@@ -0,0 +1,16 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// We use the same code as the POSIX version. +#include "sys_string_conversions_posix.cc"
diff --git a/src/base/sys_string_conversions_unittest.cc b/src/base/sys_string_conversions_unittest.cc new file mode 100644 index 0000000..a3393ba --- /dev/null +++ b/src/base/sys_string_conversions_unittest.cc
@@ -0,0 +1,188 @@ +// 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 <string> + +#include "base/basictypes.h" +#include "base/string_piece.h" +#include "base/test/scoped_locale.h" +#include "base/utf_string_conversions.h" +#include "base/sys_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +#ifdef WCHAR_T_IS_UTF32 +static const std::wstring kSysWideOldItalicLetterA = L"\x10300"; +#else +static const std::wstring kSysWideOldItalicLetterA = L"\xd800\xdf00"; +#endif + +TEST(SysStrings, SysWideToUTF8) { + using base::SysWideToUTF8; + EXPECT_EQ("Hello, world", SysWideToUTF8(L"Hello, world")); + EXPECT_EQ("\xe4\xbd\xa0\xe5\xa5\xbd", SysWideToUTF8(L"\x4f60\x597d")); + + // >16 bits + EXPECT_EQ("\xF0\x90\x8C\x80", SysWideToUTF8(kSysWideOldItalicLetterA)); + + // Error case. When Windows finds a UTF-16 character going off the end of + // a string, it just converts that literal value to UTF-8, even though this + // is invalid. + // + // This is what XP does, but Vista has different behavior, so we don't bother + // verifying it: + // EXPECT_EQ("\xE4\xBD\xA0\xED\xA0\x80zyxw", + // SysWideToUTF8(L"\x4f60\xd800zyxw")); + + // Test embedded NULLs. + std::wstring wide_null(L"a"); + wide_null.push_back(0); + wide_null.push_back('b'); + + std::string expected_null("a"); + expected_null.push_back(0); + expected_null.push_back('b'); + + EXPECT_EQ(expected_null, SysWideToUTF8(wide_null)); +} + +TEST(SysStrings, SysUTF8ToWide) { + using base::SysUTF8ToWide; + EXPECT_EQ(L"Hello, world", SysUTF8ToWide("Hello, world")); + EXPECT_EQ(L"\x4f60\x597d", SysUTF8ToWide("\xe4\xbd\xa0\xe5\xa5\xbd")); + // >16 bits + EXPECT_EQ(kSysWideOldItalicLetterA, SysUTF8ToWide("\xF0\x90\x8C\x80")); + + // Error case. When Windows finds an invalid UTF-8 character, it just skips + // it. This seems weird because it's inconsistent with the reverse conversion. + // + // This is what XP does, but Vista has different behavior, so we don't bother + // verifying it: + // EXPECT_EQ(L"\x4f60zyxw", SysUTF8ToWide("\xe4\xbd\xa0\xe5\xa5zyxw")); + + // Test embedded NULLs. + std::string utf8_null("a"); + utf8_null.push_back(0); + utf8_null.push_back('b'); + + std::wstring expected_null(L"a"); + expected_null.push_back(0); + expected_null.push_back('b'); + + EXPECT_EQ(expected_null, SysUTF8ToWide(utf8_null)); +} + +#if defined(OS_LINUX) // Tests depend on setting a specific Linux locale. + +TEST(SysStrings, SysWideToNativeMB) { + using base::SysWideToNativeMB; + base::ScopedLocale locale("en_US.utf-8"); + EXPECT_EQ("Hello, world", SysWideToNativeMB(L"Hello, world")); + EXPECT_EQ("\xe4\xbd\xa0\xe5\xa5\xbd", SysWideToNativeMB(L"\x4f60\x597d")); + + // >16 bits + EXPECT_EQ("\xF0\x90\x8C\x80", SysWideToNativeMB(kSysWideOldItalicLetterA)); + + // Error case. When Windows finds a UTF-16 character going off the end of + // a string, it just converts that literal value to UTF-8, even though this + // is invalid. + // + // This is what XP does, but Vista has different behavior, so we don't bother + // verifying it: + // EXPECT_EQ("\xE4\xBD\xA0\xED\xA0\x80zyxw", + // SysWideToNativeMB(L"\x4f60\xd800zyxw")); + + // Test embedded NULLs. + std::wstring wide_null(L"a"); + wide_null.push_back(0); + wide_null.push_back('b'); + + std::string expected_null("a"); + expected_null.push_back(0); + expected_null.push_back('b'); + + EXPECT_EQ(expected_null, SysWideToNativeMB(wide_null)); +} + +// We assume the test is running in a UTF8 locale. +TEST(SysStrings, SysNativeMBToWide) { + using base::SysNativeMBToWide; + base::ScopedLocale locale("en_US.utf-8"); + EXPECT_EQ(L"Hello, world", SysNativeMBToWide("Hello, world")); + EXPECT_EQ(L"\x4f60\x597d", SysNativeMBToWide("\xe4\xbd\xa0\xe5\xa5\xbd")); + // >16 bits + EXPECT_EQ(kSysWideOldItalicLetterA, SysNativeMBToWide("\xF0\x90\x8C\x80")); + + // Error case. When Windows finds an invalid UTF-8 character, it just skips + // it. This seems weird because it's inconsistent with the reverse conversion. + // + // This is what XP does, but Vista has different behavior, so we don't bother + // verifying it: + // EXPECT_EQ(L"\x4f60zyxw", SysNativeMBToWide("\xe4\xbd\xa0\xe5\xa5zyxw")); + + // Test embedded NULLs. + std::string utf8_null("a"); + utf8_null.push_back(0); + utf8_null.push_back('b'); + + std::wstring expected_null(L"a"); + expected_null.push_back(0); + expected_null.push_back('b'); + + EXPECT_EQ(expected_null, SysNativeMBToWide(utf8_null)); +} + +static const wchar_t* const kConvertRoundtripCases[] = { + L"Google Video", + // "网页 图片 资讯更多 »" + L"\x7f51\x9875\x0020\x56fe\x7247\x0020\x8d44\x8baf\x66f4\x591a\x0020\x00bb", + // "Παγκόσμιος Ιστός" + L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9" + L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2", + // "Поиск страниц на русском" + L"\x041f\x043e\x0438\x0441\x043a\x0020\x0441\x0442" + L"\x0440\x0430\x043d\x0438\x0446\x0020\x043d\x0430" + L"\x0020\x0440\x0443\x0441\x0441\x043a\x043e\x043c", + // "전체서비스" + L"\xc804\xccb4\xc11c\xbe44\xc2a4", + + // Test characters that take more than 16 bits. This will depend on whether + // wchar_t is 16 or 32 bits. +#if defined(WCHAR_T_IS_UTF16) + L"\xd800\xdf00", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44", +#elif defined(WCHAR_T_IS_UTF32) + L"\x10300", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\x11d40\x11d41\x11d42\x11d43\x11d44", +#endif +}; + + +TEST(SysStrings, SysNativeMBAndWide) { + base::ScopedLocale locale("en_US.utf-8"); + for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + std::wstring wide = kConvertRoundtripCases[i]; + std::wstring trip = base::SysNativeMBToWide(base::SysWideToNativeMB(wide)); + EXPECT_EQ(wide.size(), trip.size()); + EXPECT_EQ(wide, trip); + } + + // We assume our test is running in UTF-8, so double check through ICU. + for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + std::wstring wide = kConvertRoundtripCases[i]; + std::wstring trip = base::SysNativeMBToWide(WideToUTF8(wide)); + EXPECT_EQ(wide.size(), trip.size()); + EXPECT_EQ(wide, trip); + } + + for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + std::wstring wide = kConvertRoundtripCases[i]; + std::wstring trip = UTF8ToWide(base::SysWideToNativeMB(wide)); + EXPECT_EQ(wide.size(), trip.size()); + EXPECT_EQ(wide, trip); + } +} +#endif // OS_LINUX
diff --git a/src/base/sys_string_conversions_win.cc b/src/base/sys_string_conversions_win.cc new file mode 100644 index 0000000..8b2367b --- /dev/null +++ b/src/base/sys_string_conversions_win.cc
@@ -0,0 +1,70 @@ +// Copyright (c) 2006-2008 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/sys_string_conversions.h" + +#include <windows.h> + +#include "base/string_piece.h" + +namespace base { + +// Do not assert in this function since it is used by the asssertion code! +std::string SysWideToUTF8(const std::wstring& wide) { + return SysWideToMultiByte(wide, CP_UTF8); +} + +// Do not assert in this function since it is used by the asssertion code! +std::wstring SysUTF8ToWide(const StringPiece& utf8) { + return SysMultiByteToWide(utf8, CP_UTF8); +} + +std::string SysWideToNativeMB(const std::wstring& wide) { + return SysWideToMultiByte(wide, CP_ACP); +} + +std::wstring SysNativeMBToWide(const StringPiece& native_mb) { + return SysMultiByteToWide(native_mb, CP_ACP); +} + +// Do not assert in this function since it is used by the asssertion code! +std::wstring SysMultiByteToWide(const StringPiece& mb, uint32 code_page) { + if (mb.empty()) + return std::wstring(); + + int mb_length = static_cast<int>(mb.length()); + // Compute the length of the buffer. + int charcount = MultiByteToWideChar(code_page, 0, + mb.data(), mb_length, NULL, 0); + if (charcount == 0) + return std::wstring(); + + std::wstring wide; + wide.resize(charcount); + MultiByteToWideChar(code_page, 0, mb.data(), mb_length, &wide[0], charcount); + + return wide; +} + +// Do not assert in this function since it is used by the asssertion code! +std::string SysWideToMultiByte(const std::wstring& wide, uint32 code_page) { + int wide_length = static_cast<int>(wide.length()); + if (wide_length == 0) + return std::string(); + + // Compute the length of the buffer we'll need. + int charcount = WideCharToMultiByte(code_page, 0, wide.data(), wide_length, + NULL, 0, NULL, NULL); + if (charcount == 0) + return std::string(); + + std::string mb; + mb.resize(charcount); + WideCharToMultiByte(code_page, 0, wide.data(), wide_length, + &mb[0], charcount, NULL, NULL); + + return mb; +} + +} // namespace base
diff --git a/src/base/system_monitor/system_monitor.cc b/src/base/system_monitor/system_monitor.cc new file mode 100644 index 0000000..a3581e1 --- /dev/null +++ b/src/base/system_monitor/system_monitor.cc
@@ -0,0 +1,204 @@ +// 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/system_monitor/system_monitor.h" + +#include <utility> + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/stl_util.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" + +namespace base { + +static SystemMonitor* g_system_monitor = NULL; + +#if defined(ENABLE_BATTERY_MONITORING) +// The amount of time (in ms) to wait before running the initial +// battery check. +static int kDelayedBatteryCheckMs = 10 * 1000; +#endif // defined(ENABLE_BATTERY_MONITORING) + +SystemMonitor::RemovableStorageInfo::RemovableStorageInfo() { +} + +SystemMonitor::RemovableStorageInfo::RemovableStorageInfo( + const std::string& id, + const string16& device_name, + const FilePath::StringType& device_location) + : device_id(id), + name(device_name), + location(device_location) { +} + +SystemMonitor::SystemMonitor() + : power_observer_list_(new ObserverListThreadSafe<PowerObserver>()), + devices_changed_observer_list_( + new ObserverListThreadSafe<DevicesChangedObserver>()), + battery_in_use_(false), + suspended_(false) { + DCHECK(!g_system_monitor); + g_system_monitor = this; + + DCHECK(MessageLoop::current()); +#if defined(ENABLE_BATTERY_MONITORING) + delayed_battery_check_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(kDelayedBatteryCheckMs), this, + &SystemMonitor::BatteryCheck); +#endif // defined(ENABLE_BATTERY_MONITORING) +#if defined(OS_MACOSX) + PlatformInit(); +#endif +} + +SystemMonitor::~SystemMonitor() { +#if defined(OS_MACOSX) + PlatformDestroy(); +#endif + DCHECK_EQ(this, g_system_monitor); + g_system_monitor = NULL; +} + +// static +SystemMonitor* SystemMonitor::Get() { + return g_system_monitor; +} + +void SystemMonitor::ProcessPowerMessage(PowerEvent event_id) { + // Suppress duplicate notifications. Some platforms may + // send multiple notifications of the same event. + switch (event_id) { + case POWER_STATE_EVENT: +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) + { + bool on_battery = IsBatteryPower(); + if (on_battery != battery_in_use_) { + battery_in_use_ = on_battery; + NotifyPowerStateChange(); + } + } +#endif + break; + case RESUME_EVENT: + if (suspended_) { + suspended_ = false; + NotifyResume(); + } + break; + case SUSPEND_EVENT: + if (!suspended_) { + suspended_ = true; + NotifySuspend(); + } + break; + } +} + +void SystemMonitor::ProcessDevicesChanged(DeviceType device_type) { + NotifyDevicesChanged(device_type); +} + +void SystemMonitor::ProcessRemovableStorageAttached( + const std::string& id, + const string16& name, + const FilePath::StringType& location) { + { + base::AutoLock lock(removable_storage_lock_); + if (ContainsKey(removable_storage_map_, id)) { + // This can happen if our unique id scheme fails. Ignore the incoming + // non-unique attachment. + return; + } + RemovableStorageInfo info(id, name, location); + removable_storage_map_.insert(std::make_pair(id, info)); + } + NotifyRemovableStorageAttached(id, name, location); +} + +void SystemMonitor::ProcessRemovableStorageDetached(const std::string& id) { + { + base::AutoLock lock(removable_storage_lock_); + RemovableStorageMap::iterator it = removable_storage_map_.find(id); + if (it == removable_storage_map_.end()) + return; + removable_storage_map_.erase(it); + } + NotifyRemovableStorageDetached(id); +} + +std::vector<SystemMonitor::RemovableStorageInfo> +SystemMonitor::GetAttachedRemovableStorage() const { + std::vector<RemovableStorageInfo> results; + + base::AutoLock lock(removable_storage_lock_); + for (RemovableStorageMap::const_iterator it = removable_storage_map_.begin(); + it != removable_storage_map_.end(); + ++it) { + results.push_back(it->second); + } + return results; +} + +void SystemMonitor::AddPowerObserver(PowerObserver* obs) { + power_observer_list_->AddObserver(obs); +} + +void SystemMonitor::RemovePowerObserver(PowerObserver* obs) { + power_observer_list_->RemoveObserver(obs); +} + +void SystemMonitor::AddDevicesChangedObserver(DevicesChangedObserver* obs) { + devices_changed_observer_list_->AddObserver(obs); +} + +void SystemMonitor::RemoveDevicesChangedObserver(DevicesChangedObserver* obs) { + devices_changed_observer_list_->RemoveObserver(obs); +} + +void SystemMonitor::NotifyDevicesChanged(DeviceType device_type) { + DVLOG(1) << "DevicesChanged with device type " << device_type; + devices_changed_observer_list_->Notify( + &DevicesChangedObserver::OnDevicesChanged, device_type); +} + +void SystemMonitor::NotifyRemovableStorageAttached( + const std::string& id, + const string16& name, + const FilePath::StringType& location) { + DVLOG(1) << "RemovableStorageAttached with name " << UTF16ToUTF8(name) + << " and id " << id; + devices_changed_observer_list_->Notify( + &DevicesChangedObserver::OnRemovableStorageAttached, id, name, location); +} + +void SystemMonitor::NotifyRemovableStorageDetached(const std::string& id) { + DVLOG(1) << "RemovableStorageDetached for id " << id; + devices_changed_observer_list_->Notify( + &DevicesChangedObserver::OnRemovableStorageDetached, id); +} + +void SystemMonitor::NotifyPowerStateChange() { + DVLOG(1) << "PowerStateChange: " << (BatteryPower() ? "On" : "Off") + << " battery"; + power_observer_list_->Notify(&PowerObserver::OnPowerStateChange, + BatteryPower()); +} + +void SystemMonitor::NotifySuspend() { + DVLOG(1) << "Power Suspending"; + power_observer_list_->Notify(&PowerObserver::OnSuspend); +} + +void SystemMonitor::NotifyResume() { + DVLOG(1) << "Power Resuming"; + power_observer_list_->Notify(&PowerObserver::OnResume); +} + +void SystemMonitor::BatteryCheck() { + ProcessPowerMessage(SystemMonitor::POWER_STATE_EVENT); +} + +} // namespace base
diff --git a/src/base/system_monitor/system_monitor.h b/src/base/system_monitor/system_monitor.h new file mode 100644 index 0000000..935fc03 --- /dev/null +++ b/src/base/system_monitor/system_monitor.h
@@ -0,0 +1,237 @@ +// 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_SYSTEM_MONITOR_SYSTEM_MONITOR_H_ +#define BASE_SYSTEM_MONITOR_SYSTEM_MONITOR_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/string16.h" +#include "base/synchronization/lock.h" +#include "build/build_config.h" + +// Windows HiRes timers drain the battery faster so we need to know the battery +// status. This isn't true for other platforms. +#if defined(OS_WIN) +#define ENABLE_BATTERY_MONITORING 1 +#else +#undef ENABLE_BATTERY_MONITORING +#endif // !OS_WIN + +#include "base/observer_list_threadsafe.h" +#if defined(ENABLE_BATTERY_MONITORING) +#include "base/timer.h" +#endif // defined(ENABLE_BATTERY_MONITORING) + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include <IOKit/pwr_mgt/IOPMLib.h> +#include <IOKit/IOMessage.h> +#endif // OS_MACOSX && !OS_IOS + +#if defined(OS_IOS) +#include <objc/runtime.h> +#endif // OS_IOS + +namespace base { + +// Class for monitoring various system-related subsystems +// such as power management, network status, etc. +// TODO(mbelshe): Add support beyond just power management. +class BASE_EXPORT SystemMonitor { + public: + // Normalized list of power events. + enum PowerEvent { + POWER_STATE_EVENT, // The Power status of the system has changed. + SUSPEND_EVENT, // The system is being suspended. + RESUME_EVENT // The system is being resumed. + }; + + // Type of devices whose change need to be monitored, such as add/remove. + enum DeviceType { + DEVTYPE_AUDIO_CAPTURE, // Audio capture device, e.g., microphone. + DEVTYPE_VIDEO_CAPTURE, // Video capture device, e.g., webcam. + DEVTYPE_UNKNOWN, // Other devices. + }; + + struct BASE_EXPORT RemovableStorageInfo { + RemovableStorageInfo(); + RemovableStorageInfo(const std::string& id, + const string16& device_name, + const FilePath::StringType& device_location); + + // Unique device id - persists between device attachments. + std::string device_id; + + // Human readable removable storage device name. + string16 name; + + // Current attached removable storage device location. + FilePath::StringType location; + }; + + // Create SystemMonitor. Only one SystemMonitor instance per application + // is allowed. + SystemMonitor(); + ~SystemMonitor(); + + // Get the application-wide SystemMonitor (if not present, returns NULL). + static SystemMonitor* Get(); + +#if defined(OS_MACOSX) + // Allocate system resources needed by the SystemMonitor class. + // + // This function must be called before instantiating an instance of the class + // and before the Sandbox is initialized. +#if !defined(OS_IOS) + static void AllocateSystemIOPorts(); +#else + static void AllocateSystemIOPorts() {} +#endif // OS_IOS +#endif // OS_MACOSX + + // Returns information for attached removable storage. + std::vector<RemovableStorageInfo> GetAttachedRemovableStorage() const; + + // + // Power-related APIs + // + + // Is the computer currently on battery power. + // Can be called on any thread. + bool BatteryPower() const { + // Using a lock here is not necessary for just a bool. + return battery_in_use_; + } + + // Callbacks will be called on the thread which creates the SystemMonitor. + // During the callback, Add/RemoveObserver will block until the callbacks + // are finished. Observers should implement quick callback functions; if + // lengthy operations are needed, the observer should take care to invoke + // the operation on an appropriate thread. + class BASE_EXPORT PowerObserver { + public: + // Notification of a change in power status of the computer, such + // as from switching between battery and A/C power. + virtual void OnPowerStateChange(bool /*on_battery_power*/) {} + + // Notification that the system is suspending. + virtual void OnSuspend() {} + + // Notification that the system is resuming. + virtual void OnResume() {} + + protected: + virtual ~PowerObserver() {} + }; + + class BASE_EXPORT DevicesChangedObserver { + public: + // Notification that the devices connected to the system have changed. + // This is only implemented on Windows currently. + virtual void OnDevicesChanged(DeviceType /*device_type*/) {} + + // When a removable storage device is attached or detached, one of these + // two events is triggered. + virtual void OnRemovableStorageAttached( + const std::string& /*id*/, + const string16& /*name*/, + const FilePath::StringType& /*location*/) {} + virtual void OnRemovableStorageDetached(const std::string& /*id*/) {} + + protected: + virtual ~DevicesChangedObserver() {} + }; + + // Add a new observer. + // Can be called from any thread. + // Must not be called from within a notification callback. + void AddPowerObserver(PowerObserver* obs); + void AddDevicesChangedObserver(DevicesChangedObserver* obs); + + // Remove an existing observer. + // Can be called from any thread. + // Must not be called from within a notification callback. + void RemovePowerObserver(PowerObserver* obs); + void RemoveDevicesChangedObserver(DevicesChangedObserver* obs); + + // The ProcessFoo() style methods are a broken pattern and should not + // be copied. Any significant addition to this class is blocked on + // refactoring to improve the state of affairs. See http://crbug.com/149059 + +#if defined(OS_WIN) + // Windows-specific handling of a WM_POWERBROADCAST message. + // Embedders of this API should hook their top-level window + // message loop and forward WM_POWERBROADCAST through this call. + void ProcessWmPowerBroadcastMessage(int event_id); +#endif + + // Cross-platform handling of a power event. + void ProcessPowerMessage(PowerEvent event_id); + + // Cross-platform handling of a device change event. + void ProcessDevicesChanged(DeviceType device_type); + void ProcessRemovableStorageAttached(const std::string& id, + const string16& name, + const FilePath::StringType& location); + void ProcessRemovableStorageDetached(const std::string& id); + + private: + // Mapping of unique device id to device info tuple. + typedef std::map<std::string, RemovableStorageInfo> RemovableStorageMap; + +#if defined(OS_MACOSX) + void PlatformInit(); + void PlatformDestroy(); +#endif + + // Platform-specific method to check whether the system is currently + // running on battery power. Returns true if running on batteries, + // false otherwise. + bool IsBatteryPower(); + + // Checks the battery status and notifies observers if the battery + // status has changed. + void BatteryCheck(); + + // Functions to trigger notifications. + void NotifyDevicesChanged(DeviceType device_type); + void NotifyRemovableStorageAttached(const std::string& id, + const string16& name, + const FilePath::StringType& location); + void NotifyRemovableStorageDetached(const std::string& id); + void NotifyPowerStateChange(); + void NotifySuspend(); + void NotifyResume(); + + scoped_refptr<ObserverListThreadSafe<PowerObserver> > power_observer_list_; + scoped_refptr<ObserverListThreadSafe<DevicesChangedObserver> > + devices_changed_observer_list_; + bool battery_in_use_; + bool suspended_; + +#if defined(ENABLE_BATTERY_MONITORING) + base::OneShotTimer<SystemMonitor> delayed_battery_check_; +#endif + +#if defined(OS_IOS) + // Holds pointers to system event notification observers. + std::vector<id> notification_observers_; +#endif + + // For manipulating removable_storage_map_ structure. + mutable base::Lock removable_storage_lock_; + // Map of all the attached removable storage devices. + RemovableStorageMap removable_storage_map_; + + DISALLOW_COPY_AND_ASSIGN(SystemMonitor); +}; + +} // namespace base + +#endif // BASE_SYSTEM_MONITOR_SYSTEM_MONITOR_H_
diff --git a/src/base/system_monitor/system_monitor_android.cc b/src/base/system_monitor/system_monitor_android.cc new file mode 100644 index 0000000..f29b7b0 --- /dev/null +++ b/src/base/system_monitor/system_monitor_android.cc
@@ -0,0 +1,36 @@ +// 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/system_monitor/system_monitor.h" +#include "jni/SystemMonitor_jni.h" + +namespace base { + +namespace android { + +// Native implementation of SystemMonitor.java. +void OnBatteryChargingChanged(JNIEnv* env, jclass clazz) { + SystemMonitor::Get()->ProcessPowerMessage(SystemMonitor::POWER_STATE_EVENT); +} + +void OnMainActivityResumed(JNIEnv* env, jclass clazz) { + SystemMonitor::Get()->ProcessPowerMessage(SystemMonitor::RESUME_EVENT); +} + +void OnMainActivitySuspended(JNIEnv* env, jclass clazz) { + SystemMonitor::Get()->ProcessPowerMessage(SystemMonitor::SUSPEND_EVENT); +} + +} // namespace android + +bool SystemMonitor::IsBatteryPower() { + JNIEnv* env = base::android::AttachCurrentThread(); + return base::android::Java_SystemMonitor_isBatteryPower(env); +} + +bool RegisterSystemMonitor(JNIEnv* env) { + return base::android::RegisterNativesImpl(env); +} + +} // namespace base
diff --git a/src/base/system_monitor/system_monitor_android.h b/src/base/system_monitor/system_monitor_android.h new file mode 100644 index 0000000..9f60560 --- /dev/null +++ b/src/base/system_monitor/system_monitor_android.h
@@ -0,0 +1,17 @@ +// 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_SYSTEM_MONITOR_SYSTEM_MONITOR_ANDROID_H_ +#define BASE_SYSTEM_MONITOR_SYSTEM_MONITOR_ANDROID_H_ + +#include <jni.h> + +namespace base { + +// Registers the JNI bindings for SystemMonitor. +bool RegisterSystemMonitor(JNIEnv* env); + +} // namespace base + +#endif // BASE_SYSTEM_MONITOR_SYSTEM_MONITOR_ANDROID_H_
diff --git a/src/base/system_monitor/system_monitor_ios.mm b/src/base/system_monitor/system_monitor_ios.mm new file mode 100644 index 0000000..f3251b6 --- /dev/null +++ b/src/base/system_monitor/system_monitor_ios.mm
@@ -0,0 +1,40 @@ +// 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/system_monitor/system_monitor.h" + +#import <UIKit/UIKit.h> + +namespace base { + +void SystemMonitor::PlatformInit() { + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + id foreground = + [nc addObserverForName:UIApplicationWillEnterForegroundNotification + object:nil + queue:nil + usingBlock:^(NSNotification* notification) { + ProcessPowerMessage(RESUME_EVENT); + }]; + id background = + [nc addObserverForName:UIApplicationDidEnterBackgroundNotification + object:nil + queue:nil + usingBlock:^(NSNotification* notification) { + ProcessPowerMessage(SUSPEND_EVENT); + }]; + notification_observers_.push_back(foreground); + notification_observers_.push_back(background); +} + +void SystemMonitor::PlatformDestroy() { + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + for (std::vector<id>::iterator it = notification_observers_.begin(); + it != notification_observers_.end(); ++it) { + [nc removeObserver:*it]; + } + notification_observers_.clear(); +} + +} // namespace base
diff --git a/src/base/system_monitor/system_monitor_mac.mm b/src/base/system_monitor/system_monitor_mac.mm new file mode 100644 index 0000000..d0dbaab --- /dev/null +++ b/src/base/system_monitor/system_monitor_mac.mm
@@ -0,0 +1,98 @@ +// 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. + +// Implementation based on sample code from +// http://developer.apple.com/library/mac/#qa/qa1340/_index.html. + +#include "base/system_monitor/system_monitor.h" + +#include <IOKit/pwr_mgt/IOPMLib.h> +#include <IOKit/IOMessage.h> + +namespace base { + +namespace { + +io_connect_t g_system_power_io_port = 0; +IONotificationPortRef g_notification_port_ref = 0; +io_object_t g_notifier_object = 0; + +void SystemPowerEventCallback(void*, + io_service_t service, + natural_t message_type, + void* message_argument) { + SystemMonitor* sys_monitor = SystemMonitor::Get(); + DCHECK(sys_monitor); + switch (message_type) { + case kIOMessageSystemWillSleep: + sys_monitor->ProcessPowerMessage(SystemMonitor::SUSPEND_EVENT); + IOAllowPowerChange(g_system_power_io_port, + reinterpret_cast<intptr_t>(message_argument)); + break; + + case kIOMessageSystemWillPowerOn: + sys_monitor->ProcessPowerMessage(SystemMonitor::RESUME_EVENT); + break; + } +} + +} // namespace + +// The reason we can't include this code in the constructor is because +// PlatformInit() requires an active runloop and the IO port needs to be +// allocated at sandbox initialization time, before there's a runloop. +// See crbug.com/83783 . + +// static +void SystemMonitor::AllocateSystemIOPorts() { + DCHECK_EQ(g_system_power_io_port, 0u); + + // Notification port allocated by IORegisterForSystemPower. + + g_system_power_io_port = IORegisterForSystemPower( + NULL, &g_notification_port_ref, SystemPowerEventCallback, + &g_notifier_object); + + DCHECK_NE(g_system_power_io_port, 0u); +} + +void SystemMonitor::PlatformInit() { + // Need to call AllocateSystemIOPorts() before constructing a SystemMonitor + // object. + DCHECK_NE(g_system_power_io_port, 0u); + if (g_system_power_io_port == 0) + return; + + // Add the notification port to the application runloop + CFRunLoopAddSource( + CFRunLoopGetCurrent(), + IONotificationPortGetRunLoopSource(g_notification_port_ref), + kCFRunLoopCommonModes); +} + +void SystemMonitor::PlatformDestroy() { + DCHECK_NE(g_system_power_io_port, 0u); + if (g_system_power_io_port == 0) + return; + + // Remove the sleep notification port from the application runloop + CFRunLoopRemoveSource( + CFRunLoopGetCurrent(), + IONotificationPortGetRunLoopSource(g_notification_port_ref), + kCFRunLoopCommonModes); + + // Deregister for system sleep notifications + IODeregisterForSystemPower(&g_notifier_object); + + // IORegisterForSystemPower implicitly opens the Root Power Domain IOService, + // so we close it here. + IOServiceClose(g_system_power_io_port); + + g_system_power_io_port = 0; + + // Destroy the notification port allocated by IORegisterForSystemPower. + IONotificationPortDestroy(g_notification_port_ref); +} + +} // namespace base
diff --git a/src/base/system_monitor/system_monitor_posix.cc b/src/base/system_monitor/system_monitor_posix.cc new file mode 100644 index 0000000..6cf01bf --- /dev/null +++ b/src/base/system_monitor/system_monitor_posix.cc
@@ -0,0 +1,14 @@ +// 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/system_monitor/system_monitor.h" + +namespace base { + +bool SystemMonitor::IsBatteryPower() { + NOTIMPLEMENTED(); + return false; +} + +} // namespace base
diff --git a/src/base/system_monitor/system_monitor_starboard.cc b/src/base/system_monitor/system_monitor_starboard.cc new file mode 100644 index 0000000..ddd36ba --- /dev/null +++ b/src/base/system_monitor/system_monitor_starboard.cc
@@ -0,0 +1,15 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/system_monitor/system_monitor_posix.cc"
diff --git a/src/base/system_monitor/system_monitor_unittest.cc b/src/base/system_monitor/system_monitor_unittest.cc new file mode 100644 index 0000000..10bd7e7 --- /dev/null +++ b/src/base/system_monitor/system_monitor_unittest.cc
@@ -0,0 +1,202 @@ +// 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/system_monitor/system_monitor.h" + +#include "base/file_path.h" +#include "base/message_loop.h" +#include "base/test/mock_devices_changed_observer.h" +#include "base/utf_string_conversions.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +class PowerTest : public SystemMonitor::PowerObserver { + public: + PowerTest() + : power_state_changes_(0), + suspends_(0), + resumes_(0) { + } + + // PowerObserver callbacks. + virtual void OnPowerStateChange(bool on_battery_power) OVERRIDE { + power_state_changes_++; + } + + virtual void OnSuspend() OVERRIDE { + suspends_++; + } + + virtual void OnResume() OVERRIDE { + resumes_++; + } + + // Test status counts. + int power_state_changes() { return power_state_changes_; } + int suspends() { return suspends_; } + int resumes() { return resumes_; } + + private: + int power_state_changes_; // Count of OnPowerStateChange notifications. + int suspends_; // Count of OnSuspend notifications. + int resumes_; // Count of OnResume notifications. +}; + +class SystemMonitorTest : public testing::Test { + protected: + SystemMonitorTest() { +#if defined(OS_MACOSX) + // This needs to happen before SystemMonitor's ctor. + SystemMonitor::AllocateSystemIOPorts(); +#endif + system_monitor_.reset(new SystemMonitor); + } + virtual ~SystemMonitorTest() {} + + MessageLoop message_loop_; + scoped_ptr<SystemMonitor> system_monitor_; + + DISALLOW_COPY_AND_ASSIGN(SystemMonitorTest); +}; + +TEST_F(SystemMonitorTest, PowerNotifications) { + const int kObservers = 5; + + PowerTest test[kObservers]; + for (int index = 0; index < kObservers; ++index) + system_monitor_->AddPowerObserver(&test[index]); + + // Send a bunch of power changes. Since the battery power hasn't + // actually changed, we shouldn't get notifications. + for (int index = 0; index < 5; index++) { + system_monitor_->ProcessPowerMessage(SystemMonitor::POWER_STATE_EVENT); + EXPECT_EQ(test[0].power_state_changes(), 0); + } + + // Sending resume when not suspended should have no effect. + system_monitor_->ProcessPowerMessage(SystemMonitor::RESUME_EVENT); + message_loop_.RunUntilIdle(); + EXPECT_EQ(test[0].resumes(), 0); + + // Pretend we suspended. + system_monitor_->ProcessPowerMessage(SystemMonitor::SUSPEND_EVENT); + message_loop_.RunUntilIdle(); + EXPECT_EQ(test[0].suspends(), 1); + + // Send a second suspend notification. This should be suppressed. + system_monitor_->ProcessPowerMessage(SystemMonitor::SUSPEND_EVENT); + message_loop_.RunUntilIdle(); + EXPECT_EQ(test[0].suspends(), 1); + + // Pretend we were awakened. + system_monitor_->ProcessPowerMessage(SystemMonitor::RESUME_EVENT); + message_loop_.RunUntilIdle(); + EXPECT_EQ(test[0].resumes(), 1); + + // Send a duplicate resume notification. This should be suppressed. + system_monitor_->ProcessPowerMessage(SystemMonitor::RESUME_EVENT); + message_loop_.RunUntilIdle(); + EXPECT_EQ(test[0].resumes(), 1); +} + +TEST_F(SystemMonitorTest, DeviceChangeNotifications) { + const int kObservers = 5; + const string16 kDeviceName = ASCIIToUTF16("media device"); + const std::string kDeviceId1 = "1"; + const std::string kDeviceId2 = "2"; + + testing::Sequence mock_sequencer[kObservers]; + MockDevicesChangedObserver observers[kObservers]; + for (int index = 0; index < kObservers; ++index) { + system_monitor_->AddDevicesChangedObserver(&observers[index]); + + EXPECT_CALL(observers[index], + OnDevicesChanged(SystemMonitor::DEVTYPE_UNKNOWN)) + .Times(3) + .InSequence(mock_sequencer[index]); + EXPECT_CALL(observers[index], OnRemovableStorageAttached(kDeviceId1, + kDeviceName, + testing::_)) + .InSequence(mock_sequencer[index]); + EXPECT_CALL(observers[index], OnRemovableStorageDetached(kDeviceId1)) + .InSequence(mock_sequencer[index]); + EXPECT_CALL(observers[index], OnRemovableStorageDetached(kDeviceId2)) + .Times(0).InSequence(mock_sequencer[index]); + } + + system_monitor_->ProcessDevicesChanged(SystemMonitor::DEVTYPE_UNKNOWN); + message_loop_.RunUntilIdle(); + + system_monitor_->ProcessDevicesChanged(SystemMonitor::DEVTYPE_UNKNOWN); + system_monitor_->ProcessDevicesChanged(SystemMonitor::DEVTYPE_UNKNOWN); + message_loop_.RunUntilIdle(); + + system_monitor_->ProcessRemovableStorageAttached(kDeviceId1, + kDeviceName, + FILE_PATH_LITERAL("path")); + message_loop_.RunUntilIdle(); + + system_monitor_->ProcessRemovableStorageDetached(kDeviceId1); + system_monitor_->ProcessRemovableStorageDetached(kDeviceId2); + message_loop_.RunUntilIdle(); +} + +TEST_F(SystemMonitorTest, GetAttachedRemovableStorageEmpty) { + std::vector<SystemMonitor::RemovableStorageInfo> devices = + system_monitor_->GetAttachedRemovableStorage(); + EXPECT_EQ(0U, devices.size()); +} + +TEST_F(SystemMonitorTest, GetAttachedRemovableStorageAttachDetach) { + const std::string kDeviceId1 = "42"; + const string16 kDeviceName1 = ASCIIToUTF16("test"); + const FilePath kDevicePath1(FILE_PATH_LITERAL("/testfoo")); + system_monitor_->ProcessRemovableStorageAttached(kDeviceId1, + kDeviceName1, + kDevicePath1.value()); + message_loop_.RunUntilIdle(); + std::vector<SystemMonitor::RemovableStorageInfo> devices = + system_monitor_->GetAttachedRemovableStorage(); + ASSERT_EQ(1U, devices.size()); + EXPECT_EQ(kDeviceId1, devices[0].device_id); + EXPECT_EQ(kDeviceName1, devices[0].name); + EXPECT_EQ(kDevicePath1.value(), devices[0].location); + + const std::string kDeviceId2 = "44"; + const string16 kDeviceName2 = ASCIIToUTF16("test2"); + const FilePath kDevicePath2(FILE_PATH_LITERAL("/testbar")); + system_monitor_->ProcessRemovableStorageAttached(kDeviceId2, + kDeviceName2, + kDevicePath2.value()); + message_loop_.RunUntilIdle(); + devices = system_monitor_->GetAttachedRemovableStorage(); + ASSERT_EQ(2U, devices.size()); + EXPECT_EQ(kDeviceId1, devices[0].device_id); + EXPECT_EQ(kDeviceName1, devices[0].name); + EXPECT_EQ(kDevicePath1.value(), devices[0].location); + EXPECT_EQ(kDeviceId2, devices[1].device_id); + EXPECT_EQ(kDeviceName2, devices[1].name); + EXPECT_EQ(kDevicePath2.value(), devices[1].location); + + system_monitor_->ProcessRemovableStorageDetached(kDeviceId1); + message_loop_.RunUntilIdle(); + devices = system_monitor_->GetAttachedRemovableStorage(); + ASSERT_EQ(1U, devices.size()); + EXPECT_EQ(kDeviceId2, devices[0].device_id); + EXPECT_EQ(kDeviceName2, devices[0].name); + EXPECT_EQ(kDevicePath2.value(), devices[0].location); + + system_monitor_->ProcessRemovableStorageDetached(kDeviceId2); + message_loop_.RunUntilIdle(); + devices = system_monitor_->GetAttachedRemovableStorage(); + EXPECT_EQ(0U, devices.size()); +} + +} // namespace + +} // namespace base
diff --git a/src/base/system_monitor/system_monitor_win.cc b/src/base/system_monitor/system_monitor_win.cc new file mode 100644 index 0000000..a8cd54a --- /dev/null +++ b/src/base/system_monitor/system_monitor_win.cc
@@ -0,0 +1,50 @@ +// 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/system_monitor/system_monitor.h" + +namespace base { + +void SystemMonitor::ProcessWmPowerBroadcastMessage(int event_id) { + PowerEvent power_event; + switch (event_id) { + case PBT_APMPOWERSTATUSCHANGE: // The power status changed. + power_event = 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 = RESUME_EVENT; + break; + case PBT_APMSUSPEND: // System has been suspended. + power_event = 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. + } + ProcessPowerMessage(power_event); +} + +// Function to query the system to see if it is currently running on +// battery power. Returns true if running on battery. +bool SystemMonitor::IsBatteryPower() { + SYSTEM_POWER_STATUS status; + if (!GetSystemPowerStatus(&status)) { + DLOG(ERROR) << "GetSystemPowerStatus failed: " << GetLastError(); + return false; + } + return (status.ACLineStatus == 0); +} + +} // namespace base
diff --git a/src/base/task_runner.cc b/src/base/task_runner.cc new file mode 100644 index 0000000..b0563d4 --- /dev/null +++ b/src/base/task_runner.cc
@@ -0,0 +1,68 @@ +// 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/task_runner.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/threading/post_task_and_reply_impl.h" + +namespace base { + +namespace { + +// TODO(akalin): There's only one other implementation of +// PostTaskAndReplyImpl in WorkerPool. Investigate whether it'll be +// possible to merge the two. +class PostTaskAndReplyTaskRunner : public internal::PostTaskAndReplyImpl { + public: + PostTaskAndReplyTaskRunner(TaskRunner* destination); + + private: + virtual bool PostTask(const tracked_objects::Location& from_here, + const Closure& task) OVERRIDE; + + // Non-owning. + TaskRunner* destination_; +}; + +PostTaskAndReplyTaskRunner::PostTaskAndReplyTaskRunner( + TaskRunner* destination) : destination_(destination) { + DCHECK(destination_); +} + +bool PostTaskAndReplyTaskRunner::PostTask( + const tracked_objects::Location& from_here, + const Closure& task) { + return destination_->PostTask(from_here, task); +} + +} // namespace + +bool TaskRunner::PostTask(const tracked_objects::Location& from_here, + const Closure& task) { + return PostDelayedTask(from_here, task, base::TimeDelta()); +} + +bool TaskRunner::PostTaskAndReply( + const tracked_objects::Location& from_here, + const Closure& task, + const Closure& reply) { + return PostTaskAndReplyTaskRunner(this).PostTaskAndReply( + from_here, task, reply); +} + +TaskRunner::TaskRunner() {} + +TaskRunner::~TaskRunner() {} + +void TaskRunner::OnDestruct() const { + delete this; +} + +void TaskRunnerTraits::Destruct(const TaskRunner* task_runner) { + task_runner->OnDestruct(); +} + +} // namespace base
diff --git a/src/base/task_runner.h b/src/base/task_runner.h new file mode 100644 index 0000000..4fbea4d --- /dev/null +++ b/src/base/task_runner.h
@@ -0,0 +1,153 @@ +// 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_TASK_RUNNER_H_ +#define BASE_TASK_RUNNER_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/memory/ref_counted.h" +#include "base/time.h" + +namespace tracked_objects { +class Location; +} // namespace tracked_objects + +namespace base { + +struct TaskRunnerTraits; + +// A TaskRunner is an object that runs posted tasks (in the form of +// Closure objects). The TaskRunner interface provides a way of +// decoupling task posting from the mechanics of how each task will be +// run. TaskRunner provides very weak guarantees as to how posted +// tasks are run (or if they're run at all). In particular, it only +// guarantees: +// +// - Posting a task will not run it synchronously. That is, no +// Post*Task method will call task.Run() directly. +// +// - Increasing the delay can only delay when the task gets run. +// That is, increasing the delay may not affect when the task gets +// run, or it could make it run later than it normally would, but +// it won't make it run earlier than it normally would. +// +// TaskRunner does not guarantee the order in which posted tasks are +// run, whether tasks overlap, or whether they're run on a particular +// thread. Also it does not guarantee a memory model for shared data +// between tasks. (In other words, you should use your own +// synchronization/locking primitives if you need to share data +// between tasks.) +// +// Implementations of TaskRunner should be thread-safe in that all +// methods must be safe to call on any thread. Ownership semantics +// for TaskRunners are in general not clear, which is why the +// interface itself is RefCountedThreadSafe. +// +// Some theoretical implementations of TaskRunner: +// +// - A TaskRunner that uses a thread pool to run posted tasks. +// +// - A TaskRunner that, for each task, spawns a non-joinable thread +// to run that task and immediately quit. +// +// - A TaskRunner that stores the list of posted tasks and has a +// method Run() that runs each runnable task in random order. +class BASE_EXPORT TaskRunner + : public RefCountedThreadSafe<TaskRunner, TaskRunnerTraits> { + public: + // Posts the given task to be run. Returns true if the task may be + // run at some point in the future, and false if the task definitely + // will not be run. + // + // Equivalent to PostDelayedTask(from_here, task, 0). + bool PostTask(const tracked_objects::Location& from_here, + const Closure& task); + + // Like PostTask, but tries to run the posted task only after + // |delay_ms| has passed. + // + // It is valid for an implementation to ignore |delay_ms|; that is, + // to have PostDelayedTask behave the same as PostTask. + virtual bool PostDelayedTask(const tracked_objects::Location& from_here, + const Closure& task, + base::TimeDelta delay) = 0; + + // Returns true if the current thread is a thread on which a task + // may be run, and false if no task will be run on the current + // thread. + // + // It is valid for an implementation to always return true, or in + // general to use 'true' as a default value. + virtual bool RunsTasksOnCurrentThread() const = 0; + + // Posts |task| on the current TaskRunner. On completion, |reply| + // is posted to the thread that called PostTaskAndReply(). Both + // |task| and |reply| are guaranteed to be deleted on the thread + // from which PostTaskAndReply() is invoked. This allows objects + // that must be deleted on the originating thread to be bound into + // the |task| and |reply| Closures. In particular, it can be useful + // to use WeakPtr<> in the |reply| Closure so that the reply + // operation can be canceled. See the following pseudo-code: + // + // class DataBuffer : public RefCountedThreadSafe<DataBuffer> { + // public: + // // Called to add data into a buffer. + // void AddData(void* buf, size_t length); + // ... + // }; + // + // + // class DataLoader : public SupportsWeakPtr<DataLoader> { + // public: + // void GetData() { + // scoped_refptr<DataBuffer> buffer = new DataBuffer(); + // target_thread_.message_loop_proxy()->PostTaskAndReply( + // FROM_HERE, + // base::Bind(&DataBuffer::AddData, buffer), + // base::Bind(&DataLoader::OnDataReceived, AsWeakPtr(), buffer)); + // } + // + // private: + // void OnDataReceived(scoped_refptr<DataBuffer> buffer) { + // // Do something with buffer. + // } + // }; + // + // + // Things to notice: + // * Results of |task| are shared with |reply| by binding a shared argument + // (a DataBuffer instance). + // * The DataLoader object has no special thread safety. + // * The DataLoader object can be deleted while |task| is still running, + // and the reply will cancel itself safely because it is bound to a + // WeakPtr<>. + bool PostTaskAndReply(const tracked_objects::Location& from_here, + const Closure& task, + const Closure& reply); + + protected: + friend struct TaskRunnerTraits; + + // Only the Windows debug build seems to need this: see + // http://crbug.com/112250. + friend class RefCountedThreadSafe<TaskRunner, TaskRunnerTraits>; + + TaskRunner(); + virtual ~TaskRunner(); + + // Called when this object should be destroyed. By default simply + // deletes |this|, but can be overridden to do something else, like + // delete on a certain thread. + virtual void OnDestruct() const; +}; + +struct BASE_EXPORT TaskRunnerTraits { + static void Destruct(const TaskRunner* task_runner); +}; + +} // namespace base + +#endif // BASE_TASK_RUNNER_H_
diff --git a/src/base/task_runner_util.h b/src/base/task_runner_util.h new file mode 100644 index 0000000..b6dd0f3 --- /dev/null +++ b/src/base/task_runner_util.h
@@ -0,0 +1,71 @@ +// 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_TASK_RUNNER_UTIL_H_ +#define BASE_TASK_RUNNER_UTIL_H_ + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback_internal.h" +#include "base/logging.h" +#include "base/task_runner.h" + +namespace base { + +namespace internal { + +// Adapts a function that produces a result via a return value to +// one that returns via an output parameter. +template <typename ReturnType> +void ReturnAsParamAdapter(const Callback<ReturnType(void)>& func, + ReturnType* result) { + *result = func.Run(); +} + +// Adapts a T* result to a callblack that expects a T. +template <typename TaskReturnType, typename ReplyArgType> +void ReplyAdapter(const Callback<void(ReplyArgType)>& callback, + TaskReturnType* result) { + // TODO(ajwong): Remove this conditional and add a DCHECK to enforce that + // |reply| must be non-null in PostTaskAndReplyWithResult() below after + // current code that relies on this API softness has been removed. + // http://crbug.com/162712 + if (!callback.is_null()) + callback.Run(CallbackForward(*result)); +} + +} // namespace internal + +// When you have these methods +// +// R DoWorkAndReturn(); +// void Callback(const R& result); +// +// and want to call them in a PostTaskAndReply kind of fashion where the +// result of DoWorkAndReturn is passed to the Callback, you can use +// PostTaskAndReplyWithResult as in this example: +// +// PostTaskAndReplyWithResult( +// target_thread_.message_loop_proxy(), +// FROM_HERE, +// Bind(&DoWorkAndReturn), +// Bind(&Callback)); +template <typename TaskReturnType, typename ReplyArgType> +bool PostTaskAndReplyWithResult( + TaskRunner* task_runner, + const tracked_objects::Location& from_here, + const Callback<TaskReturnType(void)>& task, + const Callback<void(ReplyArgType)>& reply) { + TaskReturnType* result = new TaskReturnType(); + return task_runner->PostTaskAndReply( + from_here, + base::Bind(&internal::ReturnAsParamAdapter<TaskReturnType>, task, + result), + base::Bind(&internal::ReplyAdapter<TaskReturnType, ReplyArgType>, reply, + base::Owned(result))); +} + +} // namespace base + +#endif // BASE_TASK_RUNNER_UTIL_H_
diff --git a/src/base/task_runner_util_unittest.cc b/src/base/task_runner_util_unittest.cc new file mode 100644 index 0000000..d4d3769 --- /dev/null +++ b/src/base/task_runner_util_unittest.cc
@@ -0,0 +1,133 @@ +// 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/task_runner_util.h" + +#include "base/bind.h" +#include "base/message_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +int ReturnFourtyTwo() { + return 42; +} + +void StoreValue(int* destination, int value) { + *destination = value; +} + +void StoreDoubleValue(double* destination, double value) { + *destination = value; +} + +int g_foo_destruct_count = 0; +int g_foo_free_count = 0; + +struct Foo { + ~Foo() { + ++g_foo_destruct_count; + } +}; + +scoped_ptr<Foo> CreateFoo() { + return scoped_ptr<Foo>(new Foo); +} + +void ExpectFoo(scoped_ptr<Foo> foo) { + EXPECT_TRUE(foo.get()); + scoped_ptr<Foo> local_foo(foo.Pass()); + EXPECT_TRUE(local_foo.get()); + EXPECT_FALSE(foo.get()); +} + +struct FreeFooFunctor { + void operator()(Foo* foo) const { + ++g_foo_free_count; + delete foo; + }; +}; + +scoped_ptr_malloc<Foo, FreeFooFunctor> CreateScopedFoo() { + return scoped_ptr_malloc<Foo, FreeFooFunctor>(new Foo); +} + +void ExpectScopedFoo(scoped_ptr_malloc<Foo, FreeFooFunctor> foo) { + EXPECT_TRUE(foo.get()); + scoped_ptr_malloc<Foo, FreeFooFunctor> local_foo(foo.Pass()); + EXPECT_TRUE(local_foo.get()); + EXPECT_FALSE(foo.get()); +} + +} // namespace + +TEST(TaskRunnerHelpersTest, PostTaskAndReplyWithResult) { + MessageLoop message_loop; + int result = 0; + + PostTaskAndReplyWithResult( + message_loop.message_loop_proxy(), + FROM_HERE, + Bind(&ReturnFourtyTwo), + Bind(&StoreValue, &result)); + + message_loop.RunUntilIdle(); + + EXPECT_EQ(42, result); +} + +TEST(TaskRunnerHelpersTest, PostTaskAndReplyWithResultImplicitConvert) { + MessageLoop message_loop; + double result = 0; + + PostTaskAndReplyWithResult( + message_loop.message_loop_proxy(), + FROM_HERE, + Bind(&ReturnFourtyTwo), + Bind(&StoreDoubleValue, &result)); + + message_loop.RunUntilIdle(); + + EXPECT_DOUBLE_EQ(42.0, result); +} + +TEST(TaskRunnerHelpersTest, PostTaskAndReplyWithResultPassed) { + g_foo_destruct_count = 0; + g_foo_free_count = 0; + + MessageLoop message_loop; + + PostTaskAndReplyWithResult( + message_loop.message_loop_proxy(), + FROM_HERE, + Bind(&CreateFoo), + Bind(&ExpectFoo)); + + message_loop.RunUntilIdle(); + + EXPECT_EQ(1, g_foo_destruct_count); + EXPECT_EQ(0, g_foo_free_count); +} + +TEST(TaskRunnerHelpersTest, PostTaskAndReplyWithResultPassedFreeProc) { + g_foo_destruct_count = 0; + g_foo_free_count = 0; + + MessageLoop message_loop; + + PostTaskAndReplyWithResult( + message_loop.message_loop_proxy(), + FROM_HERE, + Bind(&CreateScopedFoo), + Bind(&ExpectScopedFoo)); + + message_loop.RunUntilIdle(); + + EXPECT_EQ(1, g_foo_destruct_count); + EXPECT_EQ(1, g_foo_free_count); +} + +} // namespace base
diff --git a/src/base/template_util.h b/src/base/template_util.h new file mode 100644 index 0000000..e21d4dc --- /dev/null +++ b/src/base/template_util.h
@@ -0,0 +1,108 @@ +// 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. + +#ifndef BASE_TEMPLATE_UTIL_H_ +#define BASE_TEMPLATE_UTIL_H_ + +#include <cstddef> // For size_t. + +#include "build/build_config.h" + +namespace base { + +// template definitions from tr1 + +template<class T, T v> +struct integral_constant { + static const T value = v; + typedef T value_type; + typedef integral_constant<T, v> type; +}; + +template <class T, T v> const T integral_constant<T, v>::value; + +typedef integral_constant<bool, true> true_type; +typedef integral_constant<bool, false> false_type; + +template <class T> struct is_pointer : false_type {}; +template <class T> struct is_pointer<T*> : true_type {}; + +template <class T, class U> struct is_same : public false_type {}; +template <class T> struct is_same<T,T> : true_type {}; + +template<class> struct is_array : public false_type {}; +template<class T, size_t n> struct is_array<T[n]> : public true_type {}; +template<class T> struct is_array<T[]> : public true_type {}; + +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_void : false_type {}; +template <> struct is_void<void> : true_type {}; + +namespace internal { + +// Types YesType and NoType are guaranteed such that sizeof(YesType) < +// sizeof(NoType). +typedef char YesType; + +struct NoType { + YesType dummy[2]; +}; + +// This class is an implementation detail for is_convertible, and you +// don't need to know how it works to use is_convertible. For those +// who care: we declare two different functions, one whose argument is +// of type To and one with a variadic argument list. We give them +// return types of different size, so we can use sizeof to trick the +// compiler into telling us which function it would have chosen if we +// had called it with an argument of type From. See Alexandrescu's +// _Modern C++ Design_ for more details on this sort of trick. + +struct ConvertHelper { + template <typename To> + static YesType Test(To); + + template <typename To> + static NoType Test(...); + + template <typename From> + static From& Create(); +}; + +// Used to determine if a type is a struct/union/class. Inspired by Boost's +// is_class type_trait implementation. +struct IsClassHelper { + template <typename C> + static YesType Test(void(C::*)(void)); + + template <typename C> + static NoType Test(...); +}; + +} // namespace internal + +// Inherits from true_type if From is convertible to To, false_type otherwise. +// +// Note that if the type is convertible, this will be a true_type REGARDLESS +// of whether or not the conversion would emit a warning. +template <typename From, typename To> +struct is_convertible + : integral_constant<bool, + sizeof(internal::ConvertHelper::Test<To>( + internal::ConvertHelper::Create<From>())) == + sizeof(internal::YesType)> { +}; + +template <typename T> +struct is_class + : integral_constant<bool, + sizeof(internal::IsClassHelper::Test<T>(0)) == + sizeof(internal::YesType)> { +}; + +} // namespace base + +#endif // BASE_TEMPLATE_UTIL_H_
diff --git a/src/base/template_util_unittest.cc b/src/base/template_util_unittest.cc new file mode 100644 index 0000000..4cfa3e4 --- /dev/null +++ b/src/base/template_util_unittest.cc
@@ -0,0 +1,80 @@ +// 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/template_util.h" + +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +struct AStruct {}; +class AClass {}; +enum AnEnum {}; + +class Parent {}; +class Child : public Parent {}; + +// is_pointer<Type> +COMPILE_ASSERT(!is_pointer<int>::value, IsPointer); +COMPILE_ASSERT(!is_pointer<int&>::value, IsPointer); +COMPILE_ASSERT(is_pointer<int*>::value, IsPointer); +COMPILE_ASSERT(is_pointer<const int*>::value, IsPointer); + +// is_array<Type> +COMPILE_ASSERT(!is_array<int>::value, IsArray); +COMPILE_ASSERT(!is_array<int*>::value, IsArray); +COMPILE_ASSERT(!is_array<int(*)[3]>::value, IsArray); +COMPILE_ASSERT(is_array<int[]>::value, IsArray); +COMPILE_ASSERT(is_array<const int[]>::value, IsArray); +COMPILE_ASSERT(is_array<int[3]>::value, IsArray); + +// is_non_const_reference<Type> +COMPILE_ASSERT(!is_non_const_reference<int>::value, IsNonConstReference); +COMPILE_ASSERT(!is_non_const_reference<const int&>::value, IsNonConstReference); +COMPILE_ASSERT(is_non_const_reference<int&>::value, IsNonConstReference); + +// is_convertible<From, To> + +// Extra parens needed to make preprocessor macro parsing happy. Otherwise, +// it sees the equivalent of: +// +// (is_convertible < Child), (Parent > ::value) +// +// Silly C++. +COMPILE_ASSERT( (is_convertible<Child, Parent>::value), IsConvertible); +COMPILE_ASSERT(!(is_convertible<Parent, Child>::value), IsConvertible); +COMPILE_ASSERT(!(is_convertible<Parent, AStruct>::value), IsConvertible); +COMPILE_ASSERT( (is_convertible<int, double>::value), IsConvertible); +COMPILE_ASSERT( (is_convertible<int*, void*>::value), IsConvertible); +COMPILE_ASSERT(!(is_convertible<void*, int*>::value), IsConvertible); + +// Array types are an easy corner case. Make sure to test that +// it does indeed compile. +COMPILE_ASSERT(!(is_convertible<int[10], double>::value), IsConvertible); +COMPILE_ASSERT(!(is_convertible<double, int[10]>::value), IsConvertible); +COMPILE_ASSERT( (is_convertible<int[10], int*>::value), IsConvertible); + +// is_same<Type1, Type2> +COMPILE_ASSERT(!(is_same<Child, Parent>::value), IsSame); +COMPILE_ASSERT(!(is_same<Parent, Child>::value), IsSame); +COMPILE_ASSERT( (is_same<Parent, Parent>::value), IsSame); +COMPILE_ASSERT( (is_same<int*, int*>::value), IsSame); +COMPILE_ASSERT( (is_same<int, int>::value), IsSame); +COMPILE_ASSERT( (is_same<void, void>::value), IsSame); +COMPILE_ASSERT(!(is_same<int, double>::value), IsSame); + + +// is_class<Type> +COMPILE_ASSERT(is_class<AStruct>::value, IsClass); +COMPILE_ASSERT(is_class<AClass>::value, IsClass); +COMPILE_ASSERT(!is_class<AnEnum>::value, IsClass); +COMPILE_ASSERT(!is_class<int>::value, IsClass); +COMPILE_ASSERT(!is_class<char*>::value, IsClass); +COMPILE_ASSERT(!is_class<int&>::value, IsClass); +COMPILE_ASSERT(!is_class<char[3]>::value, IsClass); + +} // namespace +} // namespace base
diff --git a/src/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java b/src/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java new file mode 100644 index 0000000..c2b2ecd --- /dev/null +++ b/src/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java
@@ -0,0 +1,21 @@ +// 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. + +package org.chromium.base.test.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is for disabled tests. + * <p> + * Tests with this annotation will not be run on any of the normal bots. + * Please note that they might eventually run on a special bot. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface DisabledTest { +}
diff --git a/src/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java b/src/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java new file mode 100644 index 0000000..9f8fa93 --- /dev/null +++ b/src/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java
@@ -0,0 +1,24 @@ +// 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. + +package org.chromium.base.test.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is for enormous tests. + * <p> + * Examples of enormous tests are tests that depend on external web sites or + * tests that are long running. + * <p> + * Such tests are likely NOT reliable enough to run on tree closing bots and + * should only be run on FYI bots. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface EnormousTest { +}
diff --git a/src/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java b/src/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java new file mode 100644 index 0000000..aa120ea --- /dev/null +++ b/src/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java
@@ -0,0 +1,29 @@ +// 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. + +package org.chromium.base.test.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The java instrumentation tests are normally fairly large (in terms of + * dependencies), and the test suite ends up containing a large amount of + * tests that are not trivial to filter / group just by their names. + * Instead, we use this annotation: each test should be annotated as: + * @Feature({"Foo", "Bar"}) + * in order for the test runner scripts to be able to filter and group + * them accordingly (for instance, this enable us to run all tests that exercise + * feature Foo). + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Feature { + /** + * @return A list of feature names. + */ + public String[] value(); +}
diff --git a/src/base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java b/src/base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java new file mode 100644 index 0000000..0dc730a --- /dev/null +++ b/src/base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java
@@ -0,0 +1,32 @@ +// 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. + +package org.chromium.base.test.util; + +import android.app.Instrumentation; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +/** + * Utility methods built around the android.app.Instrumentation class. + */ +public final class InstrumentationUtils { + + private InstrumentationUtils() { + } + + public static <R> R runOnMainSyncAndGetResult(Instrumentation instrumentation, + Callable<R> callable) throws Throwable { + FutureTask<R> task = new FutureTask<R>(callable); + instrumentation.runOnMainSync(task); + try { + return task.get(); + } catch (ExecutionException e) { + // Unwrap the cause of the exception and re-throw it. + throw e.getCause(); + } + } +}
diff --git a/src/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java b/src/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java new file mode 100644 index 0000000..a107b32 --- /dev/null +++ b/src/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java
@@ -0,0 +1,28 @@ +// 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. + +package org.chromium.base.test.util; + +/** + * Utility class for scaling various timeouts by a common factor. + * For example, to run tests under Valgrind, you might want the following: + * adb shell "echo 20.0 > /data/local/tmp/chrome_timeout_scale" + */ +public class ScalableTimeout { + private static Double sTimeoutScale = null; + private static final String PROPERTY_FILE = "/data/local/tmp/chrome_timeout_scale"; + + public static long ScaleTimeout(long timeout) { + if (sTimeoutScale == null) { + try { + char[] data = TestFileUtil.readUtf8File(PROPERTY_FILE, 32); + sTimeoutScale = Double.parseDouble(new String(data)); + } catch (Exception e) { + // NumberFormatException, FileNotFoundException, IOException + sTimeoutScale = 1.0; + } + } + return (long)(timeout * sTimeoutScale); + } +}
diff --git a/src/base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java b/src/base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java new file mode 100644 index 0000000..1afbc11 --- /dev/null +++ b/src/base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java
@@ -0,0 +1,77 @@ +// 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. + +package org.chromium.base.test.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.Arrays; + +/** + * Utility class for dealing with files for test. + */ +public class TestFileUtil { + public static void createNewHtmlFile(String name, String title, String body) + throws IOException { + File file = new File(name); + if (!file.createNewFile()) { + throw new IOException("File \"" + name + "\" already exists"); + } + + Writer writer = null; + try { + writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8"); + writer.write("<html><meta charset=\"UTF-8\" />" + + "<head><title>" + title + "</title></head>" + + "<body>" + + (body != null ? body : "") + + "</body>" + + "</html>"); + } finally { + if (writer != null) { + writer.close(); + } + } + } + + public static void deleteFile(String name) { + File file = new File(name); + file.delete(); + } + + /** + * @param fileName the file to read in. + * @param sizeLimit cap on the file size: will throw an exception if exceeded + * @return Array of chars read from the file + * @throws FileNotFoundException file does not exceed + * @throws IOException error encountered accessing the file + */ + public static char[] readUtf8File(String fileName, int sizeLimit) throws + FileNotFoundException, IOException { + Reader reader = null; + try { + File f = new File(fileName); + if (f.length() > sizeLimit) { + throw new IOException("File " + fileName + " length " + f.length() + + " exceeds limit " + sizeLimit); + } + char[] buffer = new char[(int) f.length()]; + reader = new InputStreamReader(new FileInputStream(f), "UTF-8"); + int charsRead = reader.read(buffer); + // Debug check that we've exhausted the input stream (will fail e.g. if the + // file grew after we inspected its length). + assert !reader.ready(); + return charsRead < buffer.length ? Arrays.copyOfRange(buffer, 0, charsRead) : buffer; + } finally { + if (reader != null) reader.close(); + } + } +}
diff --git a/src/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java b/src/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java new file mode 100644 index 0000000..11e6afd --- /dev/null +++ b/src/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java
@@ -0,0 +1,143 @@ +// 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. + +package org.chromium.base.test.util; + +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This class is usefull when writing instrumentation tests that exercise code that posts tasks + * (to the same thread). + * Since the test code is run in a single thread, the posted tasks are never executed. + * The TestThread class lets you run that code on a specific thread synchronously and flush the + * message loop on that thread. + * + * Example of test using this: + * + * public void testMyAwesomeClass() { + * TestThread testThread = new TestThread(); + * testThread.startAndWaitForReadyState(); + * + * testThread.runOnTestThreadSyncAndProcessPendingTasks(new Runnable() { + * @Override + * public void run() { + * MyAwesomeClass.doStuffAsync(); + * } + * }); + * // Once we get there we know doStuffAsync has been executed and all the tasks it posted. + * assertTrue(MyAwesomeClass.stuffWasDone()); + * } + * + * Notes: + * - this is only for tasks posted to the same thread. Anyway if you were posting to a different + * thread, you'd probably need to set that other thread up. + * - this only supports tasks posted using Handler.post(), it won't work with postDelayed and + * postAtTime. + * - if your test instanciates an object and that object is the one doing the posting of tasks, you + * probably want to instanciate it on the test thread as it might create the Handler it posts + * tasks to in the constructor. + */ + +public class TestThread extends Thread { + private Object mThreadReadyLock; + private AtomicBoolean mThreadReady; + private Handler mMainThreadHandler; + private Handler mTestThreadHandler; + + public TestThread() { + mMainThreadHandler = new Handler(); + // We can't use the AtomicBoolean as the lock or findbugs will freak out... + mThreadReadyLock = new Object(); + mThreadReady = new AtomicBoolean(); + } + + @Override + public void run() { + Looper.prepare(); + mTestThreadHandler = new Handler(); + mTestThreadHandler.post(new Runnable() { + @Override + public void run() { + synchronized (mThreadReadyLock) { + mThreadReady.set(true); + mThreadReadyLock.notify(); + } + } + }); + Looper.loop(); + } + + /** + * Starts this TestThread and blocks until it's ready to accept calls. + */ + public void startAndWaitForReadyState() { + checkOnMainThread(); + start(); + synchronized (mThreadReadyLock) { + try { + // Note the mThreadReady and while are not really needed. + // There are there so findbugs don't report warnings. + while (!mThreadReady.get()) { + mThreadReadyLock.wait(); + } + } catch (InterruptedException ie) { + System.err.println("Error starting TestThread."); + ie.printStackTrace(); + } + } + } + + /** + * Runs the passed Runnable synchronously on the TestThread and returns when all pending + * runnables have been excuted. + * Should be called from the main thread. + */ + public void runOnTestThreadSyncAndProcessPendingTasks(Runnable r) { + checkOnMainThread(); + + runOnTestThreadSync(r); + + // Run another task, when it's done it means all pendings tasks have executed. + runOnTestThreadSync(null); + } + + /** + * Runs the passed Runnable on the test thread and blocks until it has finished executing. + * Should be called from the main thread. + * @param r The runnable to be executed. + */ + public void runOnTestThreadSync(final Runnable r) { + checkOnMainThread(); + final Object lock = new Object(); + // Task executed is not really needed since we are only on one thread, it is here to appease + // findbugs. + final AtomicBoolean taskExecuted = new AtomicBoolean(); + mTestThreadHandler.post(new Runnable() { + @Override + public void run() { + if (r != null) r.run(); + synchronized (lock) { + taskExecuted.set(true); + lock.notify(); + } + } + }); + synchronized (lock) { + try { + while (!taskExecuted.get()) { + lock.wait(); + } + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + } + + private void checkOnMainThread() { + assert Looper.myLooper() == mMainThreadHandler.getLooper(); + } +}
diff --git a/src/base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java b/src/base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java new file mode 100644 index 0000000..7c13a32 --- /dev/null +++ b/src/base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java
@@ -0,0 +1,23 @@ +// 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. + +package org.chromium.base.test.util; + +import org.chromium.base.PathUtils; + +/** + * Collection of URL utilities. + */ +public class UrlUtils { + private final static String DATA_DIR = "/chrome/test/data/"; + + /** + * Construct a suitable URL for loading a test data file. + * + * @param path Pathname relative to external/chrome/testing/data + */ + public static String getTestFileUrl(String path) { + return "file://" + PathUtils.getExternalStorageDirectory() + DATA_DIR + path; + } +}
diff --git a/src/base/test/main_hook.cc b/src/base/test/main_hook.cc new file mode 100644 index 0000000..f7384df --- /dev/null +++ b/src/base/test/main_hook.cc
@@ -0,0 +1,35 @@ +// 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/test/main_hook.h" + +#if defined(__LB_SHELL__) || defined(COBALT) +#include "base/at_exit.h" +#include "base/command_line.h" +#include "cobalt/deprecated/platform_delegate.h" +#endif + +#if defined(__LB_SHELL__FOR_RELEASE__) +#error You cannot build unit tests in gold builds. +#endif + +#if defined(__LB_SHELL__) || defined(COBALT) +base::AtExitManager* platform_at_exit_manager_; + +MainHook::MainHook(MainType main_func, int argc, char* argv[]) { + // TODO: MainHooks should no longer be used and in fact this + // class was removed in the recent version of Chromium. Any required + // initialization logic in tests should be done in TestSuite::Initialize(). + CommandLine::Init(argc, argv); + platform_at_exit_manager_ = new base::AtExitManager(); + cobalt::deprecated::PlatformDelegate::Init(); +} +MainHook::~MainHook() { + cobalt::deprecated::PlatformDelegate::Teardown(); + delete platform_at_exit_manager_; +} +#elif !defined(OS_IOS) +MainHook::MainHook(MainType main_func, int argc, char* argv[]) {} +MainHook::~MainHook() {} +#endif
diff --git a/src/base/test/main_hook.h b/src/base/test/main_hook.h new file mode 100644 index 0000000..ff34bcb --- /dev/null +++ b/src/base/test/main_hook.h
@@ -0,0 +1,18 @@ +// 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 "build/build_config.h" +#include "base/basictypes.h" + +// Provides a way of running code before gtest-based tests with access to +// argv and argc. +class MainHook { + public: + typedef int (*MainType)(int, char*[]); + MainHook(MainType main_func, int argc, char* argv[]); + ~MainHook(); + + private: + DISALLOW_COPY_AND_ASSIGN(MainHook); +};
diff --git a/src/base/test/main_hook_ios.mm b/src/base/test/main_hook_ios.mm new file mode 100644 index 0000000..6d7bc44 --- /dev/null +++ b/src/base/test/main_hook_ios.mm
@@ -0,0 +1,108 @@ +// 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/test/main_hook.h" + +#import <UIKit/UIKit.h> + +#include "base/debug/debugger.h" +#include "base/logging.h" +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/memory/scoped_nsobject.h" + +// Springboard will kill any iOS app that fails to check in after launch within +// a given time. These two classes prevent this from happening. + +// MainHook saves the chrome main() and calls UIApplicationMain(), +// providing an application delegate class: ChromeUnitTestDelegate. The delegate +// listens for UIApplicationDidFinishLaunchingNotification. When the +// notification is received, it fires main() again to have the real work done. + +// Example usage: +// int main(int argc, char** argv) { +// MainHook hook(main, argc, argv); +// // Testing code goes here. There should be no code above MainHook. If +// // there is, it will be run twice. +// } + +// Since the executable isn't likely to be a real iOS UI, the delegate puts up a +// window displaying the app name. If a bunch of apps using MainHook are being +// run in a row, this provides an indication of which one is currently running. + +static MainHook::MainType g_main_func = NULL; +static int g_argc; +static char** g_argv; + +@interface UIApplication (Testing) +- (void) _terminateWithStatus:(int)status; +@end + +@interface ChromeUnitTestDelegate : NSObject { +@private + scoped_nsobject<UIWindow> window_; +} +- (void)runTests; +@end + +@implementation ChromeUnitTestDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + + CGRect bounds = [[UIScreen mainScreen] bounds]; + + // Yes, this is leaked, it's just to make what's running visible. + window_.reset([[UIWindow alloc] initWithFrame:bounds]); + [window_ makeKeyAndVisible]; + + // Add a label with the app name. + UILabel* label = [[[UILabel alloc] initWithFrame:bounds] autorelease]; + label.text = [[NSProcessInfo processInfo] processName]; + label.textAlignment = UITextAlignmentCenter; + [window_ addSubview:label]; + + // Queue up the test run. + [self performSelector:@selector(runTests) + withObject:nil + afterDelay:0.1]; + return YES; +} + +- (void)runTests { + int exitStatus = g_main_func(g_argc, g_argv); + + // If a test app is too fast, it will exit before Instruments has has a + // a chance to initialize and no test results will be seen. + // TODO(ios): crbug.com/137010 Figure out how much time is actually needed, + // and sleep only to make sure that much time has elapsed since launch. + [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; + window_.reset(); + + // Use the hidden selector to try and cleanly take down the app (otherwise + // things can think the app crashed even on a zero exit status). + UIApplication* application = [UIApplication sharedApplication]; + [application _terminateWithStatus:exitStatus]; + + exit(exitStatus); +} + +@end + +#pragma mark - + +MainHook::MainHook(MainType main_func, int argc, char* argv[]) { + static bool ran_hook = false; + if (!ran_hook) { + ran_hook = true; + + g_main_func = main_func; + g_argc = argc; + g_argv = argv; + + base::mac::ScopedNSAutoreleasePool pool; + int exit_status = UIApplicationMain(argc, argv, nil, + @"ChromeUnitTestDelegate"); + exit(exit_status); + } +}
diff --git a/src/base/test/mock_chrome_application_mac.h b/src/base/test/mock_chrome_application_mac.h new file mode 100644 index 0000000..5cd1d19 --- /dev/null +++ b/src/base/test/mock_chrome_application_mac.h
@@ -0,0 +1,33 @@ +// 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. + +#ifndef BASE_TEST_MOCK_CHROME_APPLICATION_MAC_H_ +#define BASE_TEST_MOCK_CHROME_APPLICATION_MAC_H_ + +#if defined(__OBJC__) + +#import <AppKit/AppKit.h> + +#include "base/mac/scoped_sending_event.h" +#include "base/message_pump_mac.h" + +// A basic implementation of CrAppProtocol and +// CrAppControlProtocol. This can be used in tests that need an +// NSApplication and use a runloop, or which need a ScopedSendingEvent +// when handling a nested event loop. +@interface MockCrApp : NSApplication<CrAppProtocol, + CrAppControlProtocol> { + @private + BOOL handlingSendEvent_; +} +@end + +#endif + +// To be used to instantiate MockCrApp from C++ code. +namespace mock_cr_app { +void RegisterMockCrApp(); +} // namespace mock_cr_app + +#endif // BASE_TEST_MOCK_CHROME_APPLICATION_MAC_H_
diff --git a/src/base/test/mock_chrome_application_mac.mm b/src/base/test/mock_chrome_application_mac.mm new file mode 100644 index 0000000..0890553 --- /dev/null +++ b/src/base/test/mock_chrome_application_mac.mm
@@ -0,0 +1,44 @@ +// 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/test/mock_chrome_application_mac.h" + +#include "base/auto_reset.h" +#include "base/logging.h" + +@implementation MockCrApp + ++ (NSApplication*)sharedApplication { + NSApplication* app = [super sharedApplication]; + DCHECK([app conformsToProtocol:@protocol(CrAppControlProtocol)]) + << "Existing NSApp (class " << [[app className] UTF8String] + << ") does not conform to required protocol."; + DCHECK(base::MessagePumpMac::UsingCrApp()) + << "MessagePumpMac::Create() was called before " + << "+[MockCrApp sharedApplication]"; + return app; +} + +- (void)sendEvent:(NSEvent*)event { + base::AutoReset<BOOL> scoper(&handlingSendEvent_, YES); + [super sendEvent:event]; +} + +- (void)setHandlingSendEvent:(BOOL)handlingSendEvent { + handlingSendEvent_ = handlingSendEvent; +} + +- (BOOL)isHandlingSendEvent { + return handlingSendEvent_; +} + +@end + +namespace mock_cr_app { + +void RegisterMockCrApp() { + [MockCrApp sharedApplication]; +} + +} // namespace mock_cr_app
diff --git a/src/base/test/mock_devices_changed_observer.cc b/src/base/test/mock_devices_changed_observer.cc new file mode 100644 index 0000000..c05f26a --- /dev/null +++ b/src/base/test/mock_devices_changed_observer.cc
@@ -0,0 +1,15 @@ +// 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/test/mock_devices_changed_observer.h" + +namespace base { + +MockDevicesChangedObserver::MockDevicesChangedObserver() { +} + +MockDevicesChangedObserver::~MockDevicesChangedObserver() { +} + +} // namespace base
diff --git a/src/base/test/mock_devices_changed_observer.h b/src/base/test/mock_devices_changed_observer.h new file mode 100644 index 0000000..1d0f3dd --- /dev/null +++ b/src/base/test/mock_devices_changed_observer.h
@@ -0,0 +1,34 @@ +// 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_TEST_MOCK_DEVICES_CHANGED_OBSERVER_H_ +#define BASE_TEST_MOCK_DEVICES_CHANGED_OBSERVER_H_ + +#include <string> + +#include "base/system_monitor/system_monitor.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace base { + +class MockDevicesChangedObserver + : public base::SystemMonitor::DevicesChangedObserver { + public: + MockDevicesChangedObserver(); + ~MockDevicesChangedObserver(); + + MOCK_METHOD1(OnDevicesChanged, + void(base::SystemMonitor::DeviceType device_type)); + MOCK_METHOD3(OnRemovableStorageAttached, + void(const std::string& id, + const string16& name, + const FilePath::StringType& location)); + MOCK_METHOD1(OnRemovableStorageDetached, void(const std::string& id)); + + DISALLOW_COPY_AND_ASSIGN(MockDevicesChangedObserver); +}; + +} // namespace base + +#endif // BASE_TEST_MOCK_DEVICES_CHANGED_OBSERVER_H_
diff --git a/src/base/test/mock_time_provider.cc b/src/base/test/mock_time_provider.cc new file mode 100644 index 0000000..9e5547f --- /dev/null +++ b/src/base/test/mock_time_provider.cc
@@ -0,0 +1,31 @@ +// 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/logging.h" +#include "base/test/mock_time_provider.h" + +using ::testing::DefaultValue; + +namespace base { + +MockTimeProvider* MockTimeProvider::instance_ = NULL; + +MockTimeProvider::MockTimeProvider() { + DCHECK(!instance_) << "Only one instance of MockTimeProvider can exist"; + DCHECK(!DefaultValue<Time>::IsSet()); + instance_ = this; + DefaultValue<Time>::Set(Time::FromInternalValue(0)); +} + +MockTimeProvider::~MockTimeProvider() { + instance_ = NULL; + DefaultValue<Time>::Clear(); +} + +// static +Time MockTimeProvider::StaticNow() { + return instance_->Now(); +} + +} // namespace base
diff --git a/src/base/test/mock_time_provider.h b/src/base/test/mock_time_provider.h new file mode 100644 index 0000000..81240f7 --- /dev/null +++ b/src/base/test/mock_time_provider.h
@@ -0,0 +1,66 @@ +// 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. + +// A helper class used to mock out calls to the static method base::Time::Now. +// +// Example usage: +// +// typedef base::Time(TimeProvider)(); +// class StopWatch { +// public: +// StopWatch(TimeProvider* time_provider); +// void Start(); +// base::TimeDelta Stop(); +// private: +// TimeProvider* time_provider_; +// ... +// } +// +// Normally, you would instantiate a StopWatch with the real Now function: +// +// StopWatch watch(&base::Time::Now); +// +// But when testing, you want to instantiate it with +// MockTimeProvider::StaticNow, which calls an internally mocked out member. +// This allows you to set expectations on the Now method. For example: +// +// TEST_F(StopWatchTest, BasicTest) { +// InSequence s; +// StrictMock<MockTimeProvider> mock_time; +// EXPECT_CALL(mock_time, Now()) +// .WillOnce(Return(Time::FromDoubleT(4))); +// EXPECT_CALL(mock_time, Now()) +// .WillOnce(Return(Time::FromDoubleT(10))); +// +// StopWatch sw(&MockTimeProvider::StaticNow); +// sw.Start(); // First call to Now. +// TimeDelta elapsed = sw.stop(); // Second call to Now. +// ASSERT_EQ(elapsed, TimeDelta::FromSeconds(6)); +// } + +#ifndef BASE_TEST_MOCK_TIME_PROVIDER_H_ +#define BASE_TEST_MOCK_TIME_PROVIDER_H_ + +#include "base/time.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace base { + +class MockTimeProvider { + public: + MockTimeProvider(); + ~MockTimeProvider(); + + MOCK_METHOD0(Now, Time()); + + static Time StaticNow(); + + private: + static MockTimeProvider* instance_; + DISALLOW_COPY_AND_ASSIGN(MockTimeProvider); +}; + +} // namespace base + +#endif // BASE_TEST_MOCK_TIME_PROVIDER_H_
diff --git a/src/base/test/multiprocess_test.cc b/src/base/test/multiprocess_test.cc new file mode 100644 index 0000000..27f9970 --- /dev/null +++ b/src/base/test/multiprocess_test.cc
@@ -0,0 +1,56 @@ +// 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/test/multiprocess_test.h" + +#include "base/base_switches.h" +#include "base/command_line.h" + +namespace base { + +MultiProcessTest::MultiProcessTest() { +} + +ProcessHandle MultiProcessTest::SpawnChild(const std::string& procname, + bool debug_on_start) { + FileHandleMappingVector empty_file_list; + return SpawnChildImpl(procname, empty_file_list, debug_on_start); +} + +#if defined(OS_POSIX) +ProcessHandle MultiProcessTest::SpawnChild( + const std::string& procname, + const FileHandleMappingVector& fds_to_map, + bool debug_on_start) { + return SpawnChildImpl(procname, fds_to_map, debug_on_start); +} +#endif + +CommandLine MultiProcessTest::MakeCmdLine(const std::string& procname, + bool debug_on_start) { + CommandLine cl(*CommandLine::ForCurrentProcess()); + cl.AppendSwitchASCII(switches::kTestChildProcess, procname); + if (debug_on_start) + cl.AppendSwitch(switches::kDebugOnStart); + return cl; +} + +#if !defined(OS_ANDROID) +ProcessHandle MultiProcessTest::SpawnChildImpl( + const std::string& procname, + const FileHandleMappingVector& fds_to_map, + bool debug_on_start) { + ProcessHandle handle = kNullProcessHandle; + base::LaunchOptions options; +#if defined(OS_WIN) + options.start_hidden = true; +#else + options.fds_to_remap = &fds_to_map; +#endif + base::LaunchProcess(MakeCmdLine(procname, debug_on_start), options, &handle); + return handle; +} +#endif // !defined(OS_ANDROID) + +} // namespace base
diff --git a/src/base/test/multiprocess_test.h b/src/base/test/multiprocess_test.h new file mode 100644 index 0000000..7c3af33 --- /dev/null +++ b/src/base/test/multiprocess_test.h
@@ -0,0 +1,85 @@ +// 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_TEST_MULTIPROCESS_TEST_H_ +#define BASE_TEST_MULTIPROCESS_TEST_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/process.h" +#include "base/process_util.h" +#include "build/build_config.h" +#include "testing/platform_test.h" + +class CommandLine; + +namespace base { + +// A MultiProcessTest is a test class which makes it easier to +// write a test which requires code running out of process. +// +// To create a multiprocess test simply follow these steps: +// +// 1) Derive your test from MultiProcessTest. Example: +// +// class MyTest : public MultiProcessTest { +// }; +// +// TEST_F(MyTest, TestCaseName) { +// ... +// } +// +// 2) Create a mainline function for the child processes and include +// testing/multiprocess_func_list.h. +// See the declaration of the MULTIPROCESS_TEST_MAIN macro +// in that file for an example. +// 3) Call SpawnChild("foo"), where "foo" is the name of +// the function you wish to run in the child processes. +// That's it! +class MultiProcessTest : public PlatformTest { + public: + MultiProcessTest(); + + protected: + // Run a child process. + // 'procname' is the name of a function which the child will + // execute. It must be exported from this library in order to + // run. + // + // Example signature: + // extern "C" int __declspec(dllexport) FooBar() { + // // do client work here + // } + // + // Returns the handle to the child, or NULL on failure + ProcessHandle SpawnChild(const std::string& procname, bool debug_on_start); + +#if defined(OS_POSIX) + // TODO(evan): see if we can delete this via more refactoring. + // SpawnChild() should just take a base::LaunchOptions so that we don't + // need multiple versions of it. + ProcessHandle SpawnChild(const std::string& procname, + const FileHandleMappingVector& fds_to_map, + bool debug_on_start); +#endif + + // Set up the command line used to spawn the child process. + virtual CommandLine MakeCmdLine(const std::string& procname, + bool debug_on_start); + + private: + // Shared implementation of SpawnChild. + // TODO: |fds_to_map| is unused on Windows; see above TODO about + // further refactoring. + ProcessHandle SpawnChildImpl(const std::string& procname, + const FileHandleMappingVector& fds_to_map, + bool debug_on_start); + + DISALLOW_COPY_AND_ASSIGN(MultiProcessTest); +}; + +} // namespace base + +#endif // BASE_TEST_MULTIPROCESS_TEST_H_
diff --git a/src/base/test/multiprocess_test_android.cc b/src/base/test/multiprocess_test_android.cc new file mode 100644 index 0000000..a61441f --- /dev/null +++ b/src/base/test/multiprocess_test_android.cc
@@ -0,0 +1,62 @@ +// 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/test/multiprocess_test.h" + +#include <unistd.h> + +#include "base/hash_tables.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process.h" +#include "testing/multiprocess_func_list.h" + +namespace base { + +// A very basic implementation for android. On Android tests can run in an APK +// and we don't have an executable to exec*. This implementation does the bare +// minimum to execute the method specified by procname (in the child process). +// - |debug_on_start| is ignored. +ProcessHandle MultiProcessTest::SpawnChildImpl( + const std::string& procname, + const FileHandleMappingVector& fds_to_remap, + bool debug_on_start) { + pid_t pid = fork(); + + if (pid < 0) { + PLOG(ERROR) << "fork"; + return kNullProcessHandle; + } + if (pid > 0) { + // Parent process. + return pid; + } + // Child process. + std::hash_set<int> fds_to_keep_open; + for (FileHandleMappingVector::const_iterator it = fds_to_remap.begin(); + it != fds_to_remap.end(); ++it) { + fds_to_keep_open.insert(it->first); + } + // Keep stdin, stdout and stderr open since this is not meant to spawn a + // daemon. + const int kFdForAndroidLogging = 3; // FD used by __android_log_write(). + for (int fd = kFdForAndroidLogging + 1; fd < getdtablesize(); ++fd) { + if (fds_to_keep_open.find(fd) == fds_to_keep_open.end()) { + HANDLE_EINTR(close(fd)); + } + } + for (FileHandleMappingVector::const_iterator it = fds_to_remap.begin(); + it != fds_to_remap.end(); ++it) { + int old_fd = it->first; + int new_fd = it->second; + if (dup2(old_fd, new_fd) < 0) { + PLOG(FATAL) << "dup2"; + } + HANDLE_EINTR(close(old_fd)); + } + _exit(multi_process_function_list::InvokeChildProcessTest(procname)); + return 0; +} + +} // namespace base
diff --git a/src/base/test/perf_test_suite.cc b/src/base/test/perf_test_suite.cc new file mode 100644 index 0000000..ebfe32f --- /dev/null +++ b/src/base/test/perf_test_suite.cc
@@ -0,0 +1,48 @@ +// Copyright (c) 2010 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/test/perf_test_suite.h" + +#include "base/command_line.h" +#include "base/debug/debugger.h" +#include "base/file_path.h" +#include "base/path_service.h" +#include "base/perftimer.h" +#include "base/process_util.h" +#include "base/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +PerfTestSuite::PerfTestSuite(int argc, char** argv) : TestSuite(argc, argv) { +} + +void PerfTestSuite::Initialize() { + TestSuite::Initialize(); + + // Initialize the perf timer log + FilePath log_path = + CommandLine::ForCurrentProcess()->GetSwitchValuePath("log-file"); + if (log_path.empty()) { + FilePath exe; + PathService::Get(base::FILE_EXE, &exe); + log_path = exe.ReplaceExtension(FILE_PATH_LITERAL("log")); + log_path = log_path.InsertBeforeExtension(FILE_PATH_LITERAL("_perf")); + } + ASSERT_TRUE(InitPerfLog(log_path)); + +#if !defined(OS_STARBOARD) + // Raise to high priority to have more precise measurements. Since we don't + // aim at 1% precision, it is not necessary to run at realtime level. + if (!base::debug::BeingDebugged()) + base::RaiseProcessToHighPriority(); +#endif +} + +void PerfTestSuite::Shutdown() { + TestSuite::Shutdown(); + FinalizePerfLog(); +} + +} // namespace base
diff --git a/src/base/test/perf_test_suite.h b/src/base/test/perf_test_suite.h new file mode 100644 index 0000000..85bfc41 --- /dev/null +++ b/src/base/test/perf_test_suite.h
@@ -0,0 +1,22 @@ +// 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. + +#ifndef BASE_TEST_PERF_TEST_SUITE_H_ +#define BASE_TEST_PERF_TEST_SUITE_H_ + +#include "base/test/test_suite.h" + +namespace base { + +class PerfTestSuite : public TestSuite { + public: + PerfTestSuite(int argc, char** argv); + + virtual void Initialize() OVERRIDE; + virtual void Shutdown() OVERRIDE; +}; + +} // namespace base + +#endif // BASE_TEST_PERF_TEST_SUITE_H_
diff --git a/src/base/test/run_all_perftests.cc b/src/base/test/run_all_perftests.cc new file mode 100644 index 0000000..6d9817c --- /dev/null +++ b/src/base/test/run_all_perftests.cc
@@ -0,0 +1,11 @@ +// 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/test/main_hook.h" +#include "base/test/perf_test_suite.h" + +int main(int argc, char** argv) { + MainHook hook(main, argc, argv); + return base::PerfTestSuite(argc, argv).Run(); +}
diff --git a/src/base/test/run_all_unittests.cc b/src/base/test/run_all_unittests.cc new file mode 100644 index 0000000..1aba309 --- /dev/null +++ b/src/base/test/run_all_unittests.cc
@@ -0,0 +1,26 @@ +// 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/test/main_hook.h" +#include "base/test/test_suite.h" +#include "build/build_config.h" + +#if !defined(OS_STARBOARD) +int main(int argc, char** argv) { + MainHook hook(main, argc, argv); + return base::TestSuite(argc, argv).Run(); +} +#else +#include "starboard/event.h" +#include "starboard/system.h" + +void SbEventHandle(const SbEvent* event) { + if (event->type == kSbEventTypeStart) { + SbEventStartData* data = static_cast<SbEventStartData*>(event->data); + MainHook hook(NULL, data->argument_count, data->argument_values); + SbSystemRequestStop( + base::TestSuite(data->argument_count, data->argument_values).Run()); + } +} +#endif
diff --git a/src/base/test/scoped_locale.cc b/src/base/test/scoped_locale.cc new file mode 100644 index 0000000..35b3fbe --- /dev/null +++ b/src/base/test/scoped_locale.cc
@@ -0,0 +1,23 @@ +// 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/test/scoped_locale.h" + +#include <locale.h> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +ScopedLocale::ScopedLocale(const std::string& locale) { + prev_locale_ = setlocale(LC_ALL, NULL); + EXPECT_TRUE(setlocale(LC_ALL, locale.c_str()) != NULL) << + "Failed to set locale: " << locale; +} + +ScopedLocale::~ScopedLocale() { + EXPECT_STREQ(prev_locale_.c_str(), setlocale(LC_ALL, prev_locale_.c_str())); +} + +} // namespace base
diff --git a/src/base/test/scoped_locale.h b/src/base/test/scoped_locale.h new file mode 100644 index 0000000..a9f9348 --- /dev/null +++ b/src/base/test/scoped_locale.h
@@ -0,0 +1,29 @@ +// 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. + +#ifndef BASE_TEST_SCOPED_LOCALE_H_ +#define BASE_TEST_SCOPED_LOCALE_H_ + +#include <string> + +#include "base/basictypes.h" + +namespace base { + +// Sets the given |locale| on construction, and restores the previous locale +// on destruction. +class ScopedLocale { + public: + explicit ScopedLocale(const std::string& locale); + ~ScopedLocale(); + + private: + std::string prev_locale_; + + DISALLOW_COPY_AND_ASSIGN(ScopedLocale); +}; + +} // namespace base + +#endif // BASE_TEST_SCOPED_LOCALE_H_
diff --git a/src/base/test/scoped_path_override.cc b/src/base/test/scoped_path_override.cc new file mode 100644 index 0000000..bb38c7f --- /dev/null +++ b/src/base/test/scoped_path_override.cc
@@ -0,0 +1,30 @@ +// 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/test/scoped_path_override.h" + +#include "base/logging.h" +#include "base/path_service.h" + +namespace base { + +ScopedPathOverride::ScopedPathOverride(int key) : key_(key) { + bool result = temp_dir_.CreateUniqueTempDir(); + CHECK(result); + result = PathService::Override(key, temp_dir_.path()); + CHECK(result); +} + +ScopedPathOverride::ScopedPathOverride(int key, const FilePath& dir) + : key_(key) { + bool result = PathService::Override(key, dir); + CHECK(result); +} + +ScopedPathOverride::~ScopedPathOverride() { + bool result = PathService::RemoveOverride(key_); + CHECK(result) << "The override seems to have been removed already!"; +} + +} // namespace base
diff --git a/src/base/test/scoped_path_override.h b/src/base/test/scoped_path_override.h new file mode 100644 index 0000000..3ac441c --- /dev/null +++ b/src/base/test/scoped_path_override.h
@@ -0,0 +1,36 @@ +// 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_TEST_SCOPED_PATH_OVERRIDE_H_ +#define BASE_TEST_SCOPED_PATH_OVERRIDE_H_ + +#include "base/basictypes.h" +#include "base/files/scoped_temp_dir.h" + +class FilePath; + +namespace base { + +// Sets a path override on construction, and removes it when the object goes out +// of scope. This class is intended to be used by tests that need to override +// paths to ensure their overrides are properly handled and reverted when the +// scope of the test is left. +class ScopedPathOverride { + public: + // Contructor that intializes the override to a scoped temp directory. + explicit ScopedPathOverride(int key); + // Constructor that would use a path provided by the user. + ScopedPathOverride(int key, const FilePath& dir); + ~ScopedPathOverride(); + + private: + int key_; + ScopedTempDir temp_dir_; + + DISALLOW_COPY_AND_ASSIGN(ScopedPathOverride); +}; + +} // namespace base + +#endif // BASE_TEST_SCOPED_PATH_OVERRIDE_H_
diff --git a/src/base/test/sequenced_task_runner_test_template.cc b/src/base/test/sequenced_task_runner_test_template.cc new file mode 100644 index 0000000..50487d2 --- /dev/null +++ b/src/base/test/sequenced_task_runner_test_template.cc
@@ -0,0 +1,259 @@ +// 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/test/sequenced_task_runner_test_template.h" + +#include <ostream> + +#include "base/location.h" + +namespace base { + +namespace internal { + +TaskEvent::TaskEvent(int i, Type type) + : i(i), type(type) { +} + +SequencedTaskTracker::SequencedTaskTracker() : next_post_i_(0) { +} + +void SequencedTaskTracker::PostWrappedNonNestableTask( + const scoped_refptr<SequencedTaskRunner>& task_runner, + const Closure& task) { + AutoLock event_lock(lock_); + const int post_i = next_post_i_++; + Closure wrapped_task = Bind(&SequencedTaskTracker::RunTask, this, + task, post_i); + task_runner->PostNonNestableTask(FROM_HERE, wrapped_task); + TaskPosted(post_i); +} + +void SequencedTaskTracker::PostWrappedNestableTask( + const scoped_refptr<SequencedTaskRunner>& task_runner, + const Closure& task) { + AutoLock event_lock(lock_); + const int post_i = next_post_i_++; + Closure wrapped_task = Bind(&SequencedTaskTracker::RunTask, this, + task, post_i); + task_runner->PostTask(FROM_HERE, wrapped_task); + TaskPosted(post_i); +} + +void SequencedTaskTracker::PostWrappedDelayedNonNestableTask( + const scoped_refptr<SequencedTaskRunner>& task_runner, + const Closure& task, + TimeDelta delay) { + AutoLock event_lock(lock_); + const int post_i = next_post_i_++; + Closure wrapped_task = Bind(&SequencedTaskTracker::RunTask, this, + task, post_i); + task_runner->PostNonNestableDelayedTask(FROM_HERE, wrapped_task, delay); + TaskPosted(post_i); +} + +void SequencedTaskTracker::PostNonNestableTasks( + const scoped_refptr<SequencedTaskRunner>& task_runner, + int task_count) { + for (int i = 0; i < task_count; ++i) { + PostWrappedNonNestableTask(task_runner, Closure()); + } +} + +void SequencedTaskTracker::RunTask(const Closure& task, int task_i) { + TaskStarted(task_i); + if (!task.is_null()) + task.Run(); + TaskEnded(task_i); +} + +void SequencedTaskTracker::TaskPosted(int i) { + // Caller must own |lock_|. + events_.push_back(TaskEvent(i, TaskEvent::POST)); +} + +void SequencedTaskTracker::TaskStarted(int i) { + AutoLock lock(lock_); + events_.push_back(TaskEvent(i, TaskEvent::START)); +} + +void SequencedTaskTracker::TaskEnded(int i) { + AutoLock lock(lock_); + events_.push_back(TaskEvent(i, TaskEvent::END)); +} + +const std::vector<TaskEvent>& +SequencedTaskTracker::GetTaskEvents() const { + return events_; +} + +SequencedTaskTracker::~SequencedTaskTracker() { +} + +void PrintTo(const TaskEvent& event, std::ostream* os) { + *os << "(i=" << event.i << ", type="; + switch (event.type) { + case TaskEvent::POST: *os << "POST"; break; + case TaskEvent::START: *os << "START"; break; + case TaskEvent::END: *os << "END"; break; + } + *os << ")"; +} + +namespace { + +// Returns the task ordinals for the task event type |type| in the order that +// they were recorded. +std::vector<int> GetEventTypeOrder(const std::vector<TaskEvent>& events, + TaskEvent::Type type) { + std::vector<int> tasks; + std::vector<TaskEvent>::const_iterator event; + for (event = events.begin(); event != events.end(); ++event) { + if (event->type == type) + tasks.push_back(event->i); + } + return tasks; +} + +// Returns all task events for task |task_i|. +std::vector<TaskEvent::Type> GetEventsForTask( + const std::vector<TaskEvent>& events, + int task_i) { + std::vector<TaskEvent::Type> task_event_orders; + std::vector<TaskEvent>::const_iterator event; + for (event = events.begin(); event != events.end(); ++event) { + if (event->i == task_i) + task_event_orders.push_back(event->type); + } + return task_event_orders; +} + +// Checks that the task events for each task in |events| occur in the order +// {POST, START, END}, and that there is only one instance of each event type +// per task. +::testing::AssertionResult CheckEventOrdersForEachTask( + const std::vector<TaskEvent>& events, + int task_count) { + std::vector<TaskEvent::Type> expected_order; + expected_order.push_back(TaskEvent::POST); + expected_order.push_back(TaskEvent::START); + expected_order.push_back(TaskEvent::END); + + // This is O(n^2), but it runs fast enough currently so is not worth + // optimizing. + for (int i = 0; i < task_count; ++i) { + const std::vector<TaskEvent::Type> task_events = + GetEventsForTask(events, i); + if (task_events != expected_order) { + return ::testing::AssertionFailure() + << "Events for task " << i << " are out of order; expected: " + << ::testing::PrintToString(expected_order) << "; actual: " + << ::testing::PrintToString(task_events); + } + } + return ::testing::AssertionSuccess(); +} + +// Checks that no two tasks were running at the same time. I.e. the only +// events allowed between the START and END of a task are the POSTs of other +// tasks. +::testing::AssertionResult CheckNoTaskRunsOverlap( + const std::vector<TaskEvent>& events) { + // If > -1, we're currently inside a START, END pair. + int current_task_i = -1; + + std::vector<TaskEvent>::const_iterator event; + for (event = events.begin(); event != events.end(); ++event) { + bool spurious_event_found = false; + + if (current_task_i == -1) { // Not inside a START, END pair. + switch (event->type) { + case TaskEvent::POST: + break; + case TaskEvent::START: + current_task_i = event->i; + break; + case TaskEvent::END: + spurious_event_found = true; + break; + } + + } else { // Inside a START, END pair. + bool interleaved_task_detected = false; + + switch (event->type) { + case TaskEvent::POST: + if (event->i == current_task_i) + spurious_event_found = true; + break; + case TaskEvent::START: + interleaved_task_detected = true; + break; + case TaskEvent::END: + if (event->i != current_task_i) + interleaved_task_detected = true; + else + current_task_i = -1; + break; + } + + if (interleaved_task_detected) { + return ::testing::AssertionFailure() + << "Found event " << ::testing::PrintToString(*event) + << " between START and END events for task " << current_task_i + << "; event dump: " << ::testing::PrintToString(events); + } + } + + if (spurious_event_found) { + const int event_i = event - events.begin(); + return ::testing::AssertionFailure() + << "Spurious event " << ::testing::PrintToString(*event) + << " at position " << event_i << "; event dump: " + << ::testing::PrintToString(events); + } + } + + return ::testing::AssertionSuccess(); +} + +} // namespace + +::testing::AssertionResult CheckNonNestableInvariants( + const std::vector<TaskEvent>& events, + int task_count) { + const std::vector<int> post_order = + GetEventTypeOrder(events, TaskEvent::POST); + const std::vector<int> start_order = + GetEventTypeOrder(events, TaskEvent::START); + const std::vector<int> end_order = + GetEventTypeOrder(events, TaskEvent::END); + + if (start_order != post_order) { + return ::testing::AssertionFailure() + << "Expected START order (which equals actual POST order): \n" + << ::testing::PrintToString(post_order) + << "\n Actual START order:\n" + << ::testing::PrintToString(start_order); + } + + if (end_order != post_order) { + return ::testing::AssertionFailure() + << "Expected END order (which equals actual POST order): \n" + << ::testing::PrintToString(post_order) + << "\n Actual END order:\n" + << ::testing::PrintToString(end_order); + } + + const ::testing::AssertionResult result = + CheckEventOrdersForEachTask(events, task_count); + if (!result) + return result; + + return CheckNoTaskRunsOverlap(events); +} + +} // namespace internal + +} // namespace base
diff --git a/src/base/test/sequenced_task_runner_test_template.h b/src/base/test/sequenced_task_runner_test_template.h new file mode 100644 index 0000000..0c8d135 --- /dev/null +++ b/src/base/test/sequenced_task_runner_test_template.h
@@ -0,0 +1,363 @@ +// 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. + +// This class defines tests that implementations of SequencedTaskRunner should +// pass in order to be conformant. See task_runner_test_template.h for a +// description of how to use the constructs in this file; these work the same. + +#ifndef BASE_SEQUENCED_TASK_RUNNER_TEST_TEMPLATE_H_ +#define BASE_SEQUENCED_TASK_RUNNER_TEST_TEMPLATE_H_ + +#include <cstddef> +#include <iosfwd> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/sequenced_task_runner.h" +#include "base/synchronization/lock.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace internal { + +struct TaskEvent { + enum Type { POST, START, END }; + TaskEvent(int i, Type type); + int i; + Type type; +}; + +// Utility class used in the tests below. +class SequencedTaskTracker : public RefCountedThreadSafe<SequencedTaskTracker> { + public: + SequencedTaskTracker(); + + // Posts the non-nestable task |task|, and records its post event. + void PostWrappedNonNestableTask( + const scoped_refptr<SequencedTaskRunner>& task_runner, + const Closure& task); + + // Posts the nestable task |task|, and records its post event. + void PostWrappedNestableTask( + const scoped_refptr<SequencedTaskRunner>& task_runner, + const Closure& task); + + // Posts the delayed non-nestable task |task|, and records its post event. + void PostWrappedDelayedNonNestableTask( + const scoped_refptr<SequencedTaskRunner>& task_runner, + const Closure& task, + TimeDelta delay); + + // Posts |task_count| non-nestable tasks. + void PostNonNestableTasks( + const scoped_refptr<SequencedTaskRunner>& task_runner, + int task_count); + + const std::vector<TaskEvent>& GetTaskEvents() const; + + private: + friend class RefCountedThreadSafe<SequencedTaskTracker>; + + ~SequencedTaskTracker(); + + // A task which runs |task|, recording the start and end events. + void RunTask(const Closure& task, int task_i); + + // Records a post event for task |i|. The owner is expected to be holding + // |lock_| (unlike |TaskStarted| and |TaskEnded|). + void TaskPosted(int i); + + // Records a start event for task |i|. + void TaskStarted(int i); + + // Records a end event for task |i|. + void TaskEnded(int i); + + // Protects events_ and next_post_i_. + Lock lock_; + + // The events as they occurred for each task (protected by lock_). + std::vector<TaskEvent> events_; + + // The ordinal to be used for the next task-posting task (protected by + // lock_). + int next_post_i_; + + DISALLOW_COPY_AND_ASSIGN(SequencedTaskTracker); +}; + +void PrintTo(const TaskEvent& event, std::ostream* os); + +// Checks the non-nestable task invariants for all tasks in |events|. +// +// The invariants are: +// 1) Events started and ended in the same order that they were posted. +// 2) Events for an individual tasks occur in the order {POST, START, END}, +// and there is only one instance of each event type for a task. +// 3) The only events between a task's START and END events are the POSTs of +// other tasks. I.e. tasks were run sequentially, not interleaved. +::testing::AssertionResult CheckNonNestableInvariants( + const std::vector<TaskEvent>& events, + int task_count); + +} // namespace internal + +template <typename TaskRunnerTestDelegate> +class SequencedTaskRunnerTest : public testing::Test { + protected: + SequencedTaskRunnerTest() + : task_tracker_(new internal::SequencedTaskTracker()) {} + + const scoped_refptr<internal::SequencedTaskTracker> task_tracker_; + TaskRunnerTestDelegate delegate_; +}; + +TYPED_TEST_CASE_P(SequencedTaskRunnerTest); + +// This test posts N non-nestable tasks in sequence, and expects them to run +// in FIFO order, with no part of any two tasks' execution +// overlapping. I.e. that each task starts only after the previously-posted +// one has finished. +TYPED_TEST_P(SequencedTaskRunnerTest, SequentialNonNestable) { + const int kTaskCount = 1000; + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + this->task_tracker_->PostWrappedNonNestableTask( + task_runner, Bind(&PlatformThread::Sleep, TimeDelta::FromSeconds(1))); + for (int i = 1; i < kTaskCount; ++i) { + this->task_tracker_->PostWrappedNonNestableTask(task_runner, Closure()); + } + + this->delegate_.StopTaskRunner(); + + EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), + kTaskCount)); +} + +// This test posts N nestable tasks in sequence. It has the same expectations +// as SequentialNonNestable because even though the tasks are nestable, they +// will not be run nestedly in this case. +TYPED_TEST_P(SequencedTaskRunnerTest, SequentialNestable) { + const int kTaskCount = 1000; + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + this->task_tracker_->PostWrappedNestableTask( + task_runner, + Bind(&PlatformThread::Sleep, TimeDelta::FromSeconds(1))); + for (int i = 1; i < kTaskCount; ++i) { + this->task_tracker_->PostWrappedNestableTask(task_runner, Closure()); + } + + this->delegate_.StopTaskRunner(); + + EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), + kTaskCount)); +} + +// This test posts non-nestable tasks in order of increasing delay, and checks +// that that the tasks are run in FIFO order and that there is no execution +// overlap whatsoever between any two tasks. +TYPED_TEST_P(SequencedTaskRunnerTest, SequentialDelayedNonNestable) { + // TODO(akalin): Remove this check (http://crbug.com/149144). + if (!this->delegate_.TaskRunnerHandlesNonZeroDelays()) { + DLOG(INFO) << "This SequencedTaskRunner doesn't handle " + "non-zero delays; skipping"; + return; + } + + const int kTaskCount = 20; + const int kDelayIncrementMs = 50; + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + for (int i = 0; i < kTaskCount; ++i) { + this->task_tracker_->PostWrappedDelayedNonNestableTask( + task_runner, + Closure(), + TimeDelta::FromMilliseconds(kDelayIncrementMs * i)); + } + + this->delegate_.StopTaskRunner(); + + EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), + kTaskCount)); +} + +// This test posts a fast, non-nestable task from within each of a number of +// slow, non-nestable tasks and checks that they all run in the sequence they +// were posted in and that there is no execution overlap whatsoever. +TYPED_TEST_P(SequencedTaskRunnerTest, NonNestablePostFromNonNestableTask) { + const int kParentCount = 10; + const int kChildrenPerParent = 10; + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + for (int i = 0; i < kParentCount; ++i) { + Closure task = Bind( + &internal::SequencedTaskTracker::PostNonNestableTasks, + this->task_tracker_, + task_runner, + kChildrenPerParent); + this->task_tracker_->PostWrappedNonNestableTask(task_runner, task); + } + + this->delegate_.StopTaskRunner(); + + EXPECT_TRUE(CheckNonNestableInvariants( + this->task_tracker_->GetTaskEvents(), + kParentCount * (kChildrenPerParent + 1))); +} + +// This test posts a delayed task, and checks that the task is run later than +// the specified time. +TYPED_TEST_P(SequencedTaskRunnerTest, DelayedTaskBasic) { + // TODO(akalin): Remove this check (http://crbug.com/149144). + if (!this->delegate_.TaskRunnerHandlesNonZeroDelays()) { + DLOG(INFO) << "This SequencedTaskRunner doesn't handle " + "non-zero delays; skipping"; + return; + } + + const int kTaskCount = 1; + const TimeDelta kDelay = TimeDelta::FromMilliseconds(100); + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + Time time_before_run = Time::Now(); + this->task_tracker_->PostWrappedDelayedNonNestableTask( + task_runner, Closure(), kDelay); + this->delegate_.StopTaskRunner(); + Time time_after_run = Time::Now(); + + EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), + kTaskCount)); + EXPECT_LE(kDelay, time_after_run - time_before_run); +} + +// This test posts two tasks with the same delay, and checks that the tasks are +// 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. +TYPED_TEST_P(SequencedTaskRunnerTest, DelayedTasksSameDelay) { + // TODO(akalin): Remove this check (http://crbug.com/149144). + if (!this->delegate_.TaskRunnerHandlesNonZeroDelays()) { + DLOG(INFO) << "This SequencedTaskRunner doesn't handle " + "non-zero delays; skipping"; + return; + } + + const int kTaskCount = 2; + const TimeDelta kDelay = TimeDelta::FromMilliseconds(100); + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + this->task_tracker_->PostWrappedDelayedNonNestableTask( + task_runner, Closure(), kDelay); + this->task_tracker_->PostWrappedDelayedNonNestableTask( + task_runner, Closure(), kDelay); + this->delegate_.StopTaskRunner(); + + EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), + kTaskCount)); +} + +// This test posts a normal task and a delayed task, and checks that the +// delayed task runs after the normal task even if the normal task takes +// a long time to run. +TYPED_TEST_P(SequencedTaskRunnerTest, DelayedTaskAfterLongTask) { + // TODO(akalin): Remove this check (http://crbug.com/149144). + if (!this->delegate_.TaskRunnerHandlesNonZeroDelays()) { + DLOG(INFO) << "This SequencedTaskRunner doesn't handle " + "non-zero delays; skipping"; + return; + } + + const int kTaskCount = 2; + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + this->task_tracker_->PostWrappedNonNestableTask( + task_runner, base::Bind(&PlatformThread::Sleep, + TimeDelta::FromMilliseconds(50))); + this->task_tracker_->PostWrappedDelayedNonNestableTask( + task_runner, Closure(), TimeDelta::FromMilliseconds(10)); + this->delegate_.StopTaskRunner(); + + EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), + kTaskCount)); +} + +// Test that a pile of normal tasks and a delayed task run in the +// time-to-run order. +TYPED_TEST_P(SequencedTaskRunnerTest, DelayedTaskAfterManyLongTasks) { + // TODO(akalin): Remove this check (http://crbug.com/149144). + if (!this->delegate_.TaskRunnerHandlesNonZeroDelays()) { + DLOG(INFO) << "This SequencedTaskRunner doesn't handle " + "non-zero delays; skipping"; + return; + } + + const int kTaskCount = 11; + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + for (int i = 0; i < kTaskCount - 1; i++) { + this->task_tracker_->PostWrappedNonNestableTask( + task_runner, base::Bind(&PlatformThread::Sleep, + TimeDelta::FromMilliseconds(50))); + } + this->task_tracker_->PostWrappedDelayedNonNestableTask( + task_runner, Closure(), TimeDelta::FromMilliseconds(10)); + this->delegate_.StopTaskRunner(); + + EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), + kTaskCount)); +} + + +// TODO(francoisk777@gmail.com) Add a test, similiar to the above, which runs +// some tasked nestedly (which should be implemented in the test +// delegate). Also add, to the the test delegate, a predicate which checks +// whether the implementation supports nested tasks. +// + +REGISTER_TYPED_TEST_CASE_P(SequencedTaskRunnerTest, + SequentialNonNestable, + SequentialNestable, + SequentialDelayedNonNestable, + NonNestablePostFromNonNestableTask, + DelayedTaskBasic, + DelayedTasksSameDelay, + DelayedTaskAfterLongTask, + DelayedTaskAfterManyLongTasks); + +} // namespace base + +#endif // BASE_TASK_RUNNER_TEST_TEMPLATE_H_
diff --git a/src/base/test/sequenced_worker_pool_owner.cc b/src/base/test/sequenced_worker_pool_owner.cc new file mode 100644 index 0000000..3d124fa --- /dev/null +++ b/src/base/test/sequenced_worker_pool_owner.cc
@@ -0,0 +1,57 @@ +// 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/test/sequenced_worker_pool_owner.h" + +#include "base/location.h" +#include "base/message_loop.h" + +namespace base { + +SequencedWorkerPoolOwner::SequencedWorkerPoolOwner( + size_t max_threads, + const std::string& thread_name_prefix) + : constructor_message_loop_(MessageLoop::current()), + pool_(new SequencedWorkerPool( + max_threads, thread_name_prefix, + ALLOW_THIS_IN_INITIALIZER_LIST(this))), + has_work_call_count_(0) {} + +SequencedWorkerPoolOwner::~SequencedWorkerPoolOwner() { + pool_ = NULL; + MessageLoop::current()->Run(); +} + +const scoped_refptr<SequencedWorkerPool>& SequencedWorkerPoolOwner::pool() { + return pool_; +} + +void SequencedWorkerPoolOwner::SetWillWaitForShutdownCallback( + const Closure& callback) { + will_wait_for_shutdown_callback_ = callback; +} + +int SequencedWorkerPoolOwner::has_work_call_count() const { + AutoLock lock(has_work_lock_); + return has_work_call_count_; +} + +void SequencedWorkerPoolOwner::OnHasWork() { + AutoLock lock(has_work_lock_); + ++has_work_call_count_; +} + +void SequencedWorkerPoolOwner::WillWaitForShutdown() { + if (!will_wait_for_shutdown_callback_.is_null()) { + will_wait_for_shutdown_callback_.Run(); + } +} + +void SequencedWorkerPoolOwner::OnDestruct() { + constructor_message_loop_->PostTask( + FROM_HERE, + constructor_message_loop_->QuitClosure()); +} + +} // namespace base
diff --git a/src/base/test/sequenced_worker_pool_owner.h b/src/base/test/sequenced_worker_pool_owner.h new file mode 100644 index 0000000..c6c8d67 --- /dev/null +++ b/src/base/test/sequenced_worker_pool_owner.h
@@ -0,0 +1,61 @@ +// 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_THREADING_SEQUENCED_WORKER_POOL_UNITTEST_H_ +#define BASE_THREADING_SEQUENCED_WORKER_POOL_UNITTEST_H_ + +#include <cstddef> +#include <string> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "base/threading/sequenced_worker_pool.h" + +class MessageLoop; + +namespace base { + +// Wrapper around SequencedWorkerPool for testing that blocks destruction +// until the pool is actually destroyed. This is so that a +// SequencedWorkerPool from one test doesn't outlive its test and cause +// strange races with other tests that touch global stuff (like histograms and +// logging). However, this requires that nothing else on this thread holds a +// ref to the pool when the SequencedWorkerPoolOwner is destroyed. +class SequencedWorkerPoolOwner : public SequencedWorkerPool::TestingObserver { + public: + SequencedWorkerPoolOwner(size_t max_threads, + const std::string& thread_name_prefix); + + virtual ~SequencedWorkerPoolOwner(); + + // Don't change the returned pool's testing observer. + const scoped_refptr<SequencedWorkerPool>& pool(); + + // The given callback will be called on WillWaitForShutdown(). + void SetWillWaitForShutdownCallback(const Closure& callback); + + int has_work_call_count() const; + + private: + // SequencedWorkerPool::TestingObserver implementation. + virtual void OnHasWork() OVERRIDE; + virtual void WillWaitForShutdown() OVERRIDE; + virtual void OnDestruct() OVERRIDE; + + MessageLoop* const constructor_message_loop_; + scoped_refptr<SequencedWorkerPool> pool_; + Closure will_wait_for_shutdown_callback_; + + mutable Lock has_work_lock_; + int has_work_call_count_; + + DISALLOW_COPY_AND_ASSIGN(SequencedWorkerPoolOwner); +}; + +} // namespace base + +#endif // BASE_THREADING_SEQUENCED_WORKER_POOL_UNITTEST_H_
diff --git a/src/base/test/task_runner_test_template.cc b/src/base/test/task_runner_test_template.cc new file mode 100644 index 0000000..239a99f --- /dev/null +++ b/src/base/test/task_runner_test_template.cc
@@ -0,0 +1,40 @@ +// 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/test/task_runner_test_template.h" + +namespace base { + +namespace internal { + +TaskTracker::TaskTracker() {} + +TaskTracker::~TaskTracker() {} + +Closure TaskTracker::WrapTask(const Closure& task, int i) { + return Bind(&TaskTracker::RunTask, this, task, i); +} + +void TaskTracker::RunTask(const Closure& task, int i) { + AutoLock lock(task_run_counts_lock_); + if (!task.is_null()) { + task.Run(); + } + ++task_run_counts_[i]; +} + +std::map<int, int> TaskTracker::GetTaskRunCounts() const { + AutoLock lock(task_run_counts_lock_); + return task_run_counts_; +} + +void ExpectRunsTasksOnCurrentThread( + bool expected_value, + const scoped_refptr<TaskRunner>& task_runner) { + EXPECT_EQ(expected_value, task_runner->RunsTasksOnCurrentThread()); +} + +} // namespace internal + +} // namespace base
diff --git a/src/base/test/task_runner_test_template.h b/src/base/test/task_runner_test_template.h new file mode 100644 index 0000000..437a7d9 --- /dev/null +++ b/src/base/test/task_runner_test_template.h
@@ -0,0 +1,215 @@ +// 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. + +// This class defines tests that implementations of TaskRunner should +// pass in order to be conformant. Here's how you use it to test your +// implementation. +// +// Say your class is called MyTaskRunner. Then you need to define a +// class called MyTaskRunnerTestDelegate in my_task_runner_unittest.cc +// like this: +// +// class MyTaskRunnerTestDelegate { +// public: +// // Tasks posted to the task runner after this and before +// // StopTaskRunner() is called is called should run successfully. +// void StartTaskRunner() { +// ... +// } +// +// // Should return the task runner implementation. Only called +// // after StartTaskRunner and before StopTaskRunner. +// scoped_refptr<MyTaskRunner> GetTaskRunner() { +// ... +// } +// +// // Stop the task runner and make sure all tasks posted before +// // this is called are run. +// void StopTaskRunner() { +// ... +// } +// +// // Returns whether or not the task runner obeys non-zero delays. +// bool TaskRunnerHandlesNonZeroDelays() const { +// return true; +// } +// }; +// +// The TaskRunnerTest test harness will have a member variable of +// this delegate type and will call its functions in the various +// tests. +// +// Then you simply #include this file as well as gtest.h and add the +// following statement to my_task_runner_unittest.cc: +// +// INSTANTIATE_TYPED_TEST_CASE_P( +// MyTaskRunner, TaskRunnerTest, MyTaskRunnerTestDelegate); +// +// Easy! + +#ifndef BASE_TEST_TASK_RUNNER_TEST_TEMPLATE_H_ +#define BASE_TEST_TASK_RUNNER_TEST_TEMPLATE_H_ + +#include <cstddef> +#include <map> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "base/threading/thread.h" +#include "base/tracked_objects.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace internal { + +// Utility class that keeps track of how many times particular tasks +// are run. +class TaskTracker : public RefCountedThreadSafe<TaskTracker> { + public: + TaskTracker(); + + // Returns a closure that runs the given task and increments the run + // count of |i| by one. |task| may be null. It is guaranteed that + // only one task wrapped by a given tracker will be run at a time. + Closure WrapTask(const Closure& task, int i); + + std::map<int, int> GetTaskRunCounts() const; + + private: + friend class RefCountedThreadSafe<TaskTracker>; + + ~TaskTracker(); + + void RunTask(const Closure& task, int i); + + mutable Lock task_run_counts_lock_; + std::map<int, int> task_run_counts_; + + DISALLOW_COPY_AND_ASSIGN(TaskTracker); +}; + +} // namespace internal + +template <typename TaskRunnerTestDelegate> +class TaskRunnerTest : public testing::Test { + protected: + TaskRunnerTest() : task_tracker_(new internal::TaskTracker()) {} + + const scoped_refptr<internal::TaskTracker> task_tracker_; + TaskRunnerTestDelegate delegate_; +}; + +TYPED_TEST_CASE_P(TaskRunnerTest); + +// We can't really test much, since TaskRunner provides very few +// guarantees. + +// Post a bunch of tasks to the task runner. They should all +// complete. +TYPED_TEST_P(TaskRunnerTest, Basic) { + std::map<int, int> expected_task_run_counts; + + this->delegate_.StartTaskRunner(); + scoped_refptr<TaskRunner> task_runner = this->delegate_.GetTaskRunner(); + // Post each ith task i+1 times. + for (int i = 0; i < 20; ++i) { + const Closure& ith_task = this->task_tracker_->WrapTask(Closure(), i); + for (int j = 0; j < i + 1; ++j) { + task_runner->PostTask(FROM_HERE, ith_task); + ++expected_task_run_counts[i]; + } + } + this->delegate_.StopTaskRunner(); + + EXPECT_EQ(expected_task_run_counts, + this->task_tracker_->GetTaskRunCounts()); +} + +// Post a bunch of delayed tasks to the task runner. They should all +// complete. +TYPED_TEST_P(TaskRunnerTest, Delayed) { + if (!this->delegate_.TaskRunnerHandlesNonZeroDelays()) { + DLOG(INFO) << "This TaskRunner doesn't handle non-zero delays; skipping"; + return; + } + + std::map<int, int> expected_task_run_counts; + + this->delegate_.StartTaskRunner(); + scoped_refptr<TaskRunner> task_runner = this->delegate_.GetTaskRunner(); + // Post each ith task i+1 times with delays from 0-i. + for (int i = 0; i < 20; ++i) { + const Closure& ith_task = this->task_tracker_->WrapTask(Closure(), i); + for (int j = 0; j < i + 1; ++j) { + task_runner->PostDelayedTask( + FROM_HERE, ith_task, base::TimeDelta::FromMilliseconds(j)); + ++expected_task_run_counts[i]; + } + } + this->delegate_.StopTaskRunner(); + + EXPECT_EQ(expected_task_run_counts, + this->task_tracker_->GetTaskRunCounts()); +} + +namespace internal { + +// Calls RunsTasksOnCurrentThread() on |task_runner| and expects it to +// equal |expected_value|. +void ExpectRunsTasksOnCurrentThread( + bool expected_value, + const scoped_refptr<TaskRunner>& task_runner); + +} // namespace internal + +// Post a bunch of tasks to the task runner as well as to a separate +// thread, each checking the value of RunsTasksOnCurrentThread(), +// which should return true for the tasks posted on the task runner +// and false for the tasks posted on the separate thread. +TYPED_TEST_P(TaskRunnerTest, RunsTasksOnCurrentThread) { + std::map<int, int> expected_task_run_counts; + + Thread thread("Non-task-runner thread"); + ASSERT_TRUE(thread.Start()); + this->delegate_.StartTaskRunner(); + + scoped_refptr<TaskRunner> task_runner = this->delegate_.GetTaskRunner(); + // Post each ith task i+1 times on the task runner and i+1 times on + // the non-task-runner thread. + for (int i = 0; i < 20; ++i) { + const Closure& ith_task_runner_task = + this->task_tracker_->WrapTask( + Bind(&internal::ExpectRunsTasksOnCurrentThread, + true, task_runner), + i); + const Closure& ith_non_task_runner_task = + this->task_tracker_->WrapTask( + Bind(&internal::ExpectRunsTasksOnCurrentThread, + false, task_runner), + i); + for (int j = 0; j < i + 1; ++j) { + task_runner->PostTask(FROM_HERE, ith_task_runner_task); + thread.message_loop()->PostTask(FROM_HERE, ith_non_task_runner_task); + expected_task_run_counts[i] += 2; + } + } + + this->delegate_.StopTaskRunner(); + thread.Stop(); + + EXPECT_EQ(expected_task_run_counts, + this->task_tracker_->GetTaskRunCounts()); +} + +REGISTER_TYPED_TEST_CASE_P( + TaskRunnerTest, Basic, Delayed, RunsTasksOnCurrentThread); + +} // namespace base + +#endif //#define BASE_TEST_TASK_RUNNER_TEST_TEMPLATE_H_
diff --git a/src/base/test/test_file_util.h b/src/base/test/test_file_util.h new file mode 100644 index 0000000..6c21410 --- /dev/null +++ b/src/base/test/test_file_util.h
@@ -0,0 +1,75 @@ +// 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_TEST_TEST_FILE_UTIL_H_ +#define BASE_TEST_TEST_FILE_UTIL_H_ + +// File utility functions used only by tests. + +#include <string> + +#include "base/compiler_specific.h" +#include "base/file_path.h" + +class FilePath; + +namespace file_util { + +// Wrapper over file_util::Delete. On Windows repeatedly invokes Delete in case +// of failure to workaround Windows file locking semantics. Returns true on +// success. +bool DieFileDie(const FilePath& file, bool recurse); + +// Clear a specific file from the system cache. After this call, trying +// to access this file will result in a cold load from the hard drive. +bool EvictFileFromSystemCache(const FilePath& file); + +// Like CopyFileNoCache but recursively copies all files and subdirectories +// in the given input directory to the output directory. Any files in the +// destination that already exist will be overwritten. +// +// Returns true on success. False means there was some error copying, so the +// state of the destination is unknown. +bool CopyRecursiveDirNoCache(const FilePath& source_dir, + const FilePath& dest_dir); + +#if defined(OS_WIN) +// Returns true if the volume supports Alternate Data Streams. +bool VolumeSupportsADS(const FilePath& path); + +// Returns true if the ZoneIdentifier is correctly set to "Internet" (3). +// Note that this function must be called from the same process as +// the one that set the zone identifier. I.e. don't use it in UI/automation +// based tests. +bool HasInternetZoneIdentifier(const FilePath& full_path); +#endif // defined(OS_WIN) + +// In general it's not reliable to convert a FilePath to a wstring and we use +// string16 elsewhere for Unicode strings, but in tests it is frequently +// convenient to be able to compare paths to literals like L"foobar". +BASE_EXPORT std::wstring FilePathAsWString(const FilePath& path); +BASE_EXPORT FilePath WStringAsFilePath(const std::wstring& path); + +// For testing, make the file unreadable or unwritable. +// In POSIX, this does not apply to the root user. +bool MakeFileUnreadable(const FilePath& path) WARN_UNUSED_RESULT; +bool MakeFileUnwritable(const FilePath& path) WARN_UNUSED_RESULT; + +// Saves the current permissions for a path, and restores it on destruction. +class PermissionRestorer { + public: + explicit PermissionRestorer(const FilePath& path); + ~PermissionRestorer(); + + private: + const FilePath path_; + void* info_; // The opaque stored permission information. + size_t length_; // The length of the stored permission information. + + DISALLOW_COPY_AND_ASSIGN(PermissionRestorer); +}; + +} // namespace file_util + +#endif // BASE_TEST_TEST_FILE_UTIL_H_
diff --git a/src/base/test/test_file_util_linux.cc b/src/base/test/test_file_util_linux.cc new file mode 100644 index 0000000..993750e --- /dev/null +++ b/src/base/test/test_file_util_linux.cc
@@ -0,0 +1,28 @@ +// 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/test/test_file_util.h" + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/file_path.h" + +namespace file_util { + +bool EvictFileFromSystemCache(const FilePath& file) { + int fd = open(file.value().c_str(), O_RDONLY); + if (fd < 0) + return false; + if (fdatasync(fd) != 0) + return false; + if (posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED) != 0) + return false; + close(fd); + return true; +} + +} // namespace file_util
diff --git a/src/base/test/test_file_util_mac.cc b/src/base/test/test_file_util_mac.cc new file mode 100644 index 0000000..7145e51 --- /dev/null +++ b/src/base/test/test_file_util_mac.cc
@@ -0,0 +1,49 @@ +// 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/test/test_file_util.h" + +#include <sys/mman.h> +#include <errno.h> +#include "base/logging.h" +#include "base/file_util.h" + +namespace file_util { + +bool EvictFileFromSystemCache(const FilePath& file) { + // There aren't any really direct ways to purge a file from the UBC. From + // talking with Amit Singh, the safest is to mmap the file with MAP_FILE (the + // default) + MAP_SHARED, then do an msync to invalidate the memory. The next + // open should then have to load the file from disk. + + int64 length; + if (!file_util::GetFileSize(file, &length)) { + DLOG(ERROR) << "failed to get size of " << file.value(); + return false; + } + + // When a file is empty, we do not need to evict it from cache. + // In fact, an attempt to map it to memory will result in error. + if (length == 0) { + DLOG(WARNING) << "file size is zero, will not attempt to map to memory"; + return true; + } + + file_util::MemoryMappedFile mapped_file; + if (!mapped_file.Initialize(file)) { + DLOG(WARNING) << "failed to memory map " << file.value(); + return false; + } + + if (msync(const_cast<uint8*>(mapped_file.data()), mapped_file.length(), + MS_INVALIDATE) != 0) { + DLOG(WARNING) << "failed to invalidate memory map of " << file.value() + << ", errno: " << errno; + return false; + } + + return true; +} + +} // namespace file_util
diff --git a/src/base/test/test_file_util_posix.cc b/src/base/test/test_file_util_posix.cc new file mode 100644 index 0000000..5ebf335 --- /dev/null +++ b/src/base/test/test_file_util_posix.cc
@@ -0,0 +1,191 @@ +// 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/test/test_file_util.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <string> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" + +namespace file_util { + +namespace { + +// Deny |permission| on the file |path|. +bool DenyFilePermission(const FilePath& path, mode_t permission) { + struct stat stat_buf; + if (stat(path.value().c_str(), &stat_buf) != 0) + return false; + stat_buf.st_mode &= ~permission; + + int rv = HANDLE_EINTR(chmod(path.value().c_str(), stat_buf.st_mode)); + return rv == 0; +} + +// Gets a blob indicating the permission information for |path|. +// |length| is the length of the blob. Zero on failure. +// Returns the blob pointer, or NULL on failure. +void* GetPermissionInfo(const FilePath& path, size_t* length) { + DCHECK(length); + *length = 0; + + struct stat stat_buf; + if (stat(path.value().c_str(), &stat_buf) != 0) + return NULL; + + *length = sizeof(mode_t); + mode_t* mode = new mode_t; + *mode = stat_buf.st_mode & ~S_IFMT; // Filter out file/path kind. + + return mode; +} + +// Restores the permission information for |path|, given the blob retrieved +// using |GetPermissionInfo()|. +// |info| is the pointer to the blob. +// |length| is the length of the blob. +// Either |info| or |length| may be NULL/0, in which case nothing happens. +bool RestorePermissionInfo(const FilePath& path, void* info, size_t length) { + if (!info || (length == 0)) + return false; + + DCHECK_EQ(sizeof(mode_t), length); + mode_t* mode = reinterpret_cast<mode_t*>(info); + + int rv = HANDLE_EINTR(chmod(path.value().c_str(), *mode)); + + delete mode; + + return rv == 0; +} + +} // namespace + +bool DieFileDie(const FilePath& file, bool recurse) { + // There is no need to workaround Windows problems on POSIX. + // Just pass-through. + return file_util::Delete(file, recurse); +} + +// Mostly a verbatim copy of CopyDirectory +bool CopyRecursiveDirNoCache(const FilePath& source_dir, + const FilePath& dest_dir) { + char top_dir[PATH_MAX]; + if (base::strlcpy(top_dir, source_dir.value().c_str(), + arraysize(top_dir)) >= arraysize(top_dir)) { + return false; + } + + // This function does not properly handle destinations within the source + FilePath real_to_path = dest_dir; + if (PathExists(real_to_path)) { + if (!AbsolutePath(&real_to_path)) + return false; + } else { + real_to_path = real_to_path.DirName(); + if (!AbsolutePath(&real_to_path)) + return false; + } + if (real_to_path.value().compare(0, source_dir.value().size(), + source_dir.value()) == 0) + return false; + + bool success = true; + int traverse_type = FileEnumerator::FILES | + FileEnumerator::SHOW_SYM_LINKS | FileEnumerator::DIRECTORIES; + FileEnumerator traversal(source_dir, true, traverse_type); + + // dest_dir may not exist yet, start the loop with dest_dir + FileEnumerator::FindInfo info; + FilePath current = source_dir; + if (stat(source_dir.value().c_str(), &info.stat) < 0) { + DLOG(ERROR) << "CopyRecursiveDirNoCache() couldn't stat source directory: " + << source_dir.value() << " errno = " << errno; + success = false; + } + + while (success && !current.empty()) { + // |current| is the source path, including source_dir, so paste + // the suffix after source_dir onto dest_dir to create the target_path. + std::string suffix(¤t.value().c_str()[source_dir.value().size()]); + // Strip the leading '/' (if any). + if (!suffix.empty()) { + DCHECK_EQ('/', suffix[0]); + suffix.erase(0, 1); + } + const FilePath target_path = dest_dir.Append(suffix); + + if (S_ISDIR(info.stat.st_mode)) { + if (mkdir(target_path.value().c_str(), info.stat.st_mode & 01777) != 0 && + errno != EEXIST) { + DLOG(ERROR) << "CopyRecursiveDirNoCache() couldn't create directory: " + << target_path.value() << " errno = " << errno; + success = false; + } + } else if (S_ISREG(info.stat.st_mode)) { + if (CopyFile(current, target_path)) { + success = EvictFileFromSystemCache(target_path); + DCHECK(success); + } else { + DLOG(ERROR) << "CopyRecursiveDirNoCache() couldn't create file: " + << target_path.value(); + success = false; + } + } else { + DLOG(WARNING) << "CopyRecursiveDirNoCache() skipping non-regular file: " + << current.value(); + } + + current = traversal.Next(); + traversal.GetFindInfo(&info); + } + + return success; +} + +#if !defined(OS_LINUX) && !defined(OS_MACOSX) +bool EvictFileFromSystemCache(const FilePath& file) { + // There doesn't seem to be a POSIX way to cool the disk cache. + NOTIMPLEMENTED(); + return false; +} +#endif + +std::wstring FilePathAsWString(const FilePath& path) { + return UTF8ToWide(path.value()); +} +FilePath WStringAsFilePath(const std::wstring& path) { + return FilePath(WideToUTF8(path)); +} + +bool MakeFileUnreadable(const FilePath& path) { + return DenyFilePermission(path, S_IRUSR | S_IRGRP | S_IROTH); +} + +bool MakeFileUnwritable(const FilePath& path) { + return DenyFilePermission(path, S_IWUSR | S_IWGRP | S_IWOTH); +} + +PermissionRestorer::PermissionRestorer(const FilePath& path) + : path_(path), info_(NULL), length_(0) { + info_ = GetPermissionInfo(path_, &length_); + DCHECK(info_ != NULL); + DCHECK_NE(0u, length_); +} + +PermissionRestorer::~PermissionRestorer() { + if (!RestorePermissionInfo(path_, info_, length_)) + NOTREACHED(); +} + +} // namespace file_util
diff --git a/src/base/test/test_file_util_shell.cc b/src/base/test/test_file_util_shell.cc new file mode 100644 index 0000000..9831b66 --- /dev/null +++ b/src/base/test/test_file_util_shell.cc
@@ -0,0 +1,46 @@ +// 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/test/test_file_util.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" + +namespace file_util { + +// Deny |permission| on the file |path|. +bool DenyFilePermission(const FilePath& path, mode_t permission) { + struct stat stat_buf; + if (stat(path.value().c_str(), &stat_buf) != 0) + return false; + stat_buf.st_mode &= ~permission; + + int rv = HANDLE_EINTR(chmod(path.value().c_str(), stat_buf.st_mode)); + return rv == 0; +} + +std::wstring FilePathAsWString(const FilePath& path) { + return UTF8ToWide(path.value()); +} +FilePath WStringAsFilePath(const std::wstring& path) { + return FilePath(WideToUTF8(path)); +} + +bool MakeFileUnreadable(const FilePath& path) { + return DenyFilePermission(path, S_IRUSR | S_IRGRP | S_IROTH); +} + +bool MakeFileUnwritable(const FilePath& path) { + return DenyFilePermission(path, S_IWUSR | S_IWGRP | S_IWOTH); +} + +} // namespace file_util \ No newline at end of file
diff --git a/src/base/test/test_file_util_starboard.cc b/src/base/test/test_file_util_starboard.cc new file mode 100644 index 0000000..ef4cdad --- /dev/null +++ b/src/base/test/test_file_util_starboard.cc
@@ -0,0 +1,134 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/test/test_file_util.h" + +#include <string> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "starboard/directory.h" +#include "starboard/file.h" +#include "starboard/system.h" + +namespace file_util { + +bool DieFileDie(const FilePath& file, bool recurse) { + // There is no need to workaround Windows problems on Starboard. Just + // pass-through. + return file_util::Delete(file, recurse); +} + +// Mostly a verbatim copy of CopyDirectory +bool CopyRecursiveDirNoCache(const FilePath& source_dir, + const FilePath& dest_dir) { + char top_dir[PATH_MAX]; + if (base::strlcpy(top_dir, source_dir.value().c_str(), arraysize(top_dir)) >= + arraysize(top_dir)) { + return false; + } + + // This function does not properly handle destinations within the source + FilePath real_to_path = dest_dir; + if (PathExists(real_to_path)) { + if (!AbsolutePath(&real_to_path)) + return false; + } else { + real_to_path = real_to_path.DirName(); + if (!AbsolutePath(&real_to_path)) + return false; + } + if (real_to_path.value().compare(0, source_dir.value().size(), + source_dir.value()) == 0) + return false; + + bool success = true; + int traverse_type = FileEnumerator::FILES | FileEnumerator::DIRECTORIES; + FileEnumerator traversal(source_dir, true, traverse_type); + + // dest_dir may not exist yet, start the loop with dest_dir + FileEnumerator::FindInfo info; + FilePath current = source_dir; + if (!SbFileGetPathInfo(source_dir.value().c_str(), &info.sb_info)) { + DLOG(ERROR) << "CopyRecursiveDirNoCache() couldn't stat source directory: " + << source_dir.value() << " error = " << SbSystemGetLastError(); + success = false; + } + + while (success && !current.empty()) { + // |current| is the source path, including source_dir, so paste + // the suffix after source_dir onto dest_dir to create the target_path. + std::string suffix(¤t.value().c_str()[source_dir.value().size()]); + // Strip the leading '/' (if any). + if (!suffix.empty()) { + DCHECK_EQ('/', suffix[0]); + suffix.erase(0, 1); + } + const FilePath target_path = dest_dir.Append(suffix); + + if (info.sb_info.is_directory) { + if (SbDirectoryCreate(target_path.value().c_str())) { + DLOG(ERROR) << "CopyRecursiveDirNoCache() couldn't create directory: " + << target_path.value() + << " error = " << SbSystemGetLastError(); + success = false; + } + } else { + if (CopyFile(current, target_path)) { + success = true; + } else { + DLOG(ERROR) << "CopyRecursiveDirNoCache() couldn't create file: " + << target_path.value(); + success = false; + } + } + + current = traversal.Next(); + traversal.GetFindInfo(&info); + } + + return success; +} + +std::wstring FilePathAsWString(const FilePath& path) { + return UTF8ToWide(path.value()); +} + +FilePath WStringAsFilePath(const std::wstring& path) { + return FilePath(WideToUTF8(path)); +} + +bool MakeFileUnreadable(const FilePath& path) { + NOTIMPLEMENTED(); + return true; +} + +bool MakeFileUnwritable(const FilePath& path) { + NOTIMPLEMENTED(); + return true; +} + +PermissionRestorer::PermissionRestorer(const FilePath& path) + : path_(path), info_(NULL), length_(0) { + NOTIMPLEMENTED(); +} + +PermissionRestorer::~PermissionRestorer() { + NOTIMPLEMENTED(); +} + +} // namespace file_util
diff --git a/src/base/test/test_file_util_win.cc b/src/base/test/test_file_util_win.cc new file mode 100644 index 0000000..41d69ea --- /dev/null +++ b/src/base/test/test_file_util_win.cc
@@ -0,0 +1,343 @@ +// 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/test/test_file_util.h" + +#include <aclapi.h> +#include <shlwapi.h> +#include <windows.h> + +#include <vector> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/string_split.h" +#include "base/win/scoped_handle.h" +#include "base/threading/platform_thread.h" + +namespace file_util { + +static const ptrdiff_t kOneMB = 1024 * 1024; + +namespace { + +struct PermissionInfo { + PSECURITY_DESCRIPTOR security_descriptor; + ACL dacl; +}; + +// Deny |permission| on the file |path|, for the current user. +bool DenyFilePermission(const FilePath& path, DWORD permission) { + 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; + } + + EXPLICIT_ACCESS change; + change.grfAccessPermissions = permission; + change.grfAccessMode = 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; +} + +// Gets a blob indicating the permission information for |path|. +// |length| is the length of the blob. Zero on failure. +// Returns the blob pointer, or NULL on failure. +void* GetPermissionInfo(const FilePath& path, size_t* length) { + DCHECK(length != NULL); + *length = 0; + PACL dacl = NULL; + PSECURITY_DESCRIPTOR security_descriptor; + if (GetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()), + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, NULL, NULL, &dacl, + NULL, &security_descriptor) != ERROR_SUCCESS) { + return NULL; + } + DCHECK(dacl != NULL); + + *length = sizeof(PSECURITY_DESCRIPTOR) + dacl->AclSize; + PermissionInfo* info = reinterpret_cast<PermissionInfo*>(new char[*length]); + info->security_descriptor = security_descriptor; + memcpy(&info->dacl, dacl, dacl->AclSize); + + return info; +} + +// Restores the permission information for |path|, given the blob retrieved +// using |GetPermissionInfo()|. +// |info| is the pointer to the blob. +// |length| is the length of the blob. +// Either |info| or |length| may be NULL/0, in which case nothing happens. +bool RestorePermissionInfo(const FilePath& path, void* info, size_t length) { + if (!info || !length) + return false; + + PermissionInfo* perm = reinterpret_cast<PermissionInfo*>(info); + + DWORD rc = SetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()), + SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, + NULL, NULL, &perm->dacl, NULL); + LocalFree(perm->security_descriptor); + + char* char_array = reinterpret_cast<char*>(info); + delete [] char_array; + + return rc == ERROR_SUCCESS; +} + +} // namespace + +bool DieFileDie(const FilePath& file, bool recurse) { + // It turns out that to not induce flakiness a long timeout is needed. + const int kIterations = 25; + const base::TimeDelta kTimeout = base::TimeDelta::FromSeconds(10) / + kIterations; + + if (!file_util::PathExists(file)) + return true; + + // Sometimes Delete fails, so try a few more times. Divide the timeout + // into short chunks, so that if a try succeeds, we won't delay the test + // for too long. + for (int i = 0; i < kIterations; ++i) { + if (file_util::Delete(file, recurse)) + return true; + base::PlatformThread::Sleep(kTimeout); + } + return false; +} + +bool EvictFileFromSystemCache(const FilePath& file) { + // Request exclusive access to the file and overwrite it with no buffering. + base::win::ScopedHandle file_handle( + CreateFile(file.value().c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, NULL)); + if (!file_handle) + return false; + + // Get some attributes to restore later. + BY_HANDLE_FILE_INFORMATION bhi = {0}; + CHECK(::GetFileInformationByHandle(file_handle, &bhi)); + + // Execute in chunks. It could be optimized. We want to do few of these since + // these operations will be slow without the cache. + + // Allocate a buffer for the reads and the writes. + char* buffer = reinterpret_cast<char*>(VirtualAlloc(NULL, + kOneMB, + MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE)); + + // If the file size isn't a multiple of kOneMB, we'll need special + // processing. + bool file_is_aligned = true; + int total_bytes = 0; + DWORD bytes_read, bytes_written; + for (;;) { + bytes_read = 0; + ::ReadFile(file_handle, buffer, kOneMB, &bytes_read, NULL); + if (bytes_read == 0) + break; + + if (bytes_read < kOneMB) { + // Zero out the remaining part of the buffer. + // WriteFile will fail if we provide a buffer size that isn't a + // sector multiple, so we'll have to write the entire buffer with + // padded zeros and then use SetEndOfFile to truncate the file. + ZeroMemory(buffer + bytes_read, kOneMB - bytes_read); + file_is_aligned = false; + } + + // Move back to the position we just read from. + // Note that SetFilePointer will also fail if total_bytes isn't sector + // aligned, but that shouldn't happen here. + DCHECK((total_bytes % kOneMB) == 0); + SetFilePointer(file_handle, total_bytes, NULL, FILE_BEGIN); + if (!::WriteFile(file_handle, buffer, kOneMB, &bytes_written, NULL) || + bytes_written != kOneMB) { + BOOL freed = VirtualFree(buffer, 0, MEM_RELEASE); + DCHECK(freed); + NOTREACHED(); + return false; + } + + total_bytes += bytes_read; + + // If this is false, then we just processed the last portion of the file. + if (!file_is_aligned) + break; + } + + BOOL freed = VirtualFree(buffer, 0, MEM_RELEASE); + DCHECK(freed); + + if (!file_is_aligned) { + // The size of the file isn't a multiple of 1 MB, so we'll have + // to open the file again, this time without the FILE_FLAG_NO_BUFFERING + // flag and use SetEndOfFile to mark EOF. + file_handle.Set(NULL); + file_handle.Set(CreateFile(file.value().c_str(), GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, 0, NULL)); + CHECK_NE(SetFilePointer(file_handle, total_bytes, NULL, FILE_BEGIN), + INVALID_SET_FILE_POINTER); + CHECK(::SetEndOfFile(file_handle)); + } + + // Restore the file attributes. + CHECK(::SetFileTime(file_handle, &bhi.ftCreationTime, &bhi.ftLastAccessTime, + &bhi.ftLastWriteTime)); + + return true; +} + +// Like CopyFileNoCache but recursively copies all files and subdirectories +// in the given input directory to the output directory. +bool CopyRecursiveDirNoCache(const FilePath& source_dir, + const FilePath& dest_dir) { + // Try to create the directory if it doesn't already exist. + if (!CreateDirectory(dest_dir)) { + if (GetLastError() != ERROR_ALREADY_EXISTS) + return false; + } + + std::vector<std::wstring> files_copied; + + FilePath src(source_dir.AppendASCII("*")); + + WIN32_FIND_DATA fd; + HANDLE fh = FindFirstFile(src.value().c_str(), &fd); + if (fh == INVALID_HANDLE_VALUE) + return false; + + do { + std::wstring cur_file(fd.cFileName); + if (cur_file == L"." || cur_file == L"..") + continue; // Skip these special entries. + + FilePath cur_source_path = source_dir.Append(cur_file); + FilePath cur_dest_path = dest_dir.Append(cur_file); + + if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + // Recursively copy a subdirectory. We stripped "." and ".." already. + if (!CopyRecursiveDirNoCache(cur_source_path, cur_dest_path)) { + FindClose(fh); + return false; + } + } else { + // Copy the file. + if (!::CopyFile(cur_source_path.value().c_str(), + cur_dest_path.value().c_str(), false)) { + FindClose(fh); + return false; + } + + // We don't check for errors from this function, often, we are copying + // files that are in the repository, and they will have read-only set. + // This will prevent us from evicting from the cache, but these don't + // matter anyway. + EvictFileFromSystemCache(cur_dest_path); + } + } while (FindNextFile(fh, &fd)); + + FindClose(fh); + return true; +} + +// Checks if the volume supports Alternate Data Streams. This is required for +// the Zone Identifier implementation. +bool VolumeSupportsADS(const FilePath& path) { + wchar_t drive[MAX_PATH] = {0}; + wcscpy_s(drive, MAX_PATH, path.value().c_str()); + + if (!PathStripToRootW(drive)) + return false; + + DWORD fs_flags = 0; + if (!GetVolumeInformationW(drive, NULL, 0, 0, NULL, &fs_flags, NULL, 0)) + return false; + + if (fs_flags & FILE_NAMED_STREAMS) + return true; + + return false; +} + +// Return whether the ZoneIdentifier is correctly set to "Internet" (3) +// Only returns a valid result when called from same process as the +// one that (was supposed to have) set the zone identifier. +bool HasInternetZoneIdentifier(const FilePath& full_path) { + FilePath zone_path(full_path.value() + L":Zone.Identifier"); + std::string zone_path_contents; + if (!file_util::ReadFileToString(zone_path, &zone_path_contents)) + return false; + + std::vector<std::string> lines; + // This call also trims whitespaces, including carriage-returns (\r). + base::SplitString(zone_path_contents, '\n', &lines); + + switch (lines.size()) { + case 3: + // optional empty line at end of file: + if (lines[2] != "") + return false; + // fall through: + case 2: + return lines[0] == "[ZoneTransfer]" && lines[1] == "ZoneId=3"; + default: + return false; + } +} + +std::wstring FilePathAsWString(const FilePath& path) { + return path.value(); +} +FilePath WStringAsFilePath(const std::wstring& path) { + return FilePath(path); +} + +bool MakeFileUnreadable(const FilePath& path) { + return DenyFilePermission(path, GENERIC_READ); +} + +bool MakeFileUnwritable(const FilePath& path) { + return DenyFilePermission(path, GENERIC_WRITE); +} + +PermissionRestorer::PermissionRestorer(const FilePath& path) + : path_(path), info_(NULL), length_(0) { + info_ = GetPermissionInfo(path_, &length_); + DCHECK(info_ != NULL); + DCHECK_NE(0u, length_); +} + +PermissionRestorer::~PermissionRestorer() { + if (!RestorePermissionInfo(path_, info_, length_)) + NOTREACHED(); +} + +} // namespace file_util
diff --git a/src/base/test/test_listener_ios.h b/src/base/test/test_listener_ios.h new file mode 100644 index 0000000..c312250 --- /dev/null +++ b/src/base/test/test_listener_ios.h
@@ -0,0 +1,17 @@ +// 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_TEST_TEST_LISTENER_IOS_H_ +#define BASE_TEST_TEST_LISTENER_IOS_H_ + +namespace base { +namespace test_listener_ios { + +// Register an IOSRunLoopListener. +void RegisterTestEndListener(); + +} // namespace test_listener_ios +} // namespace base + +#endif // BASE_TEST_TEST_LISTENER_IOS_H_
diff --git a/src/base/test/test_listener_ios.mm b/src/base/test/test_listener_ios.mm new file mode 100644 index 0000000..12cf5bb --- /dev/null +++ b/src/base/test/test_listener_ios.mm
@@ -0,0 +1,45 @@ +// 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/test/test_listener_ios.h" + +#import <Foundation/Foundation.h> + +#include "base/mac/scoped_nsautorelease_pool.h" +#include "testing/gtest/include/gtest/gtest.h" + +// The iOS watchdog timer will kill an app that doesn't spin the main event +// loop often enough. This uses a Gtest TestEventListener to spin the current +// loop after each test finishes. However, if any individual test takes too +// long, it is still possible that the app will get killed. + +namespace { + +class IOSRunLoopListener : public testing::EmptyTestEventListener { + public: + virtual void OnTestEnd(const testing::TestInfo& test_info); +}; + +void IOSRunLoopListener::OnTestEnd(const testing::TestInfo& test_info) { + base::mac::ScopedNSAutoreleasePool scoped_pool; + + // At the end of the test, spin the default loop for a moment. + NSDate* stop_date = [NSDate dateWithTimeIntervalSinceNow:0.001]; + [[NSRunLoop currentRunLoop] runUntilDate:stop_date]; +} + +} // namespace + + +namespace base { +namespace test_listener_ios { + +void RegisterTestEndListener() { + testing::TestEventListeners& listeners = + testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new IOSRunLoopListener); +} + +} // namespace test_listener_ios +} // namespace base
diff --git a/src/base/test/test_reg_util_win.cc b/src/base/test/test_reg_util_win.cc new file mode 100644 index 0000000..e23c6e9 --- /dev/null +++ b/src/base/test/test_reg_util_win.cc
@@ -0,0 +1,65 @@ +// 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/test/test_reg_util_win.h" + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace registry_util { + +const wchar_t RegistryOverrideManager::kTempTestKeyPath[] = + L"Software\\Chromium\\TempTestKeys"; + +RegistryOverrideManager::ScopedRegistryKeyOverride::ScopedRegistryKeyOverride( + HKEY override, + const std::wstring& temp_name) + : override_(override), + temp_name_(temp_name) { + DCHECK(!temp_name_.empty()); + std::wstring key_path(RegistryOverrideManager::kTempTestKeyPath); + key_path += L"\\" + temp_name_; + EXPECT_EQ(ERROR_SUCCESS, + temp_key_.Create(HKEY_CURRENT_USER, key_path.c_str(), KEY_ALL_ACCESS)); + EXPECT_EQ(ERROR_SUCCESS, + ::RegOverridePredefKey(override_, temp_key_.Handle())); +} + +RegistryOverrideManager:: + ScopedRegistryKeyOverride::~ScopedRegistryKeyOverride() { + ::RegOverridePredefKey(override_, NULL); + // The temp key will be deleted via a call to DeleteAllTempKeys(). +} + +RegistryOverrideManager::RegistryOverrideManager() { + DeleteAllTempKeys(); +} + +RegistryOverrideManager::~RegistryOverrideManager() { + RemoveAllOverrides(); +} + +void RegistryOverrideManager::OverrideRegistry(HKEY override, + const std::wstring& temp_name) { + overrides_.push_back(new ScopedRegistryKeyOverride(override, temp_name)); +} + +void RegistryOverrideManager::RemoveAllOverrides() { + while (!overrides_.empty()) { + delete overrides_.back(); + overrides_.pop_back(); + } + + DeleteAllTempKeys(); +} + +// static +void RegistryOverrideManager::DeleteAllTempKeys() { + base::win::RegKey key; + if (key.Open(HKEY_CURRENT_USER, L"", KEY_ALL_ACCESS) == ERROR_SUCCESS) { + key.DeleteKey(kTempTestKeyPath); + } +} + +} // namespace registry_util
diff --git a/src/base/test/test_reg_util_win.h b/src/base/test/test_reg_util_win.h new file mode 100644 index 0000000..b71831f --- /dev/null +++ b/src/base/test/test_reg_util_win.h
@@ -0,0 +1,63 @@ +// 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. + +#ifndef BASE_TEST_TEST_REG_UTIL_H_ +#define BASE_TEST_TEST_REG_UTIL_H_ + +// Registry utility functions used only by tests. + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/win/registry.h" + +namespace registry_util { + +// Allows a test to easily override registry hives so that it can start from a +// known good state, or make sure to not leave any side effects once the test +// completes. +class RegistryOverrideManager { + public: + // All overridden hives will be descendents of this registry path under the + // main HKCU hive. + static const wchar_t kTempTestKeyPath[]; + + RegistryOverrideManager(); + ~RegistryOverrideManager(); + + // Override the given registry hive using a temporary key named by temp_name + // under the temporary test key path. + void OverrideRegistry(HKEY override, const std::wstring& temp_name); + + // Deletes all temporary test keys used by the overrides. + static void DeleteAllTempKeys(); + + // Removes all overrides and deletes all temporary test keys used by the + // overrides. + void RemoveAllOverrides(); + + private: + // Keeps track of one override. + class ScopedRegistryKeyOverride { + public: + ScopedRegistryKeyOverride(HKEY override, const std::wstring& temp_name); + ~ScopedRegistryKeyOverride(); + + private: + HKEY override_; + base::win::RegKey temp_key_; + std::wstring temp_name_; + + DISALLOW_COPY_AND_ASSIGN(ScopedRegistryKeyOverride); + }; + + std::vector<ScopedRegistryKeyOverride*> overrides_; + + DISALLOW_COPY_AND_ASSIGN(RegistryOverrideManager); +}; + +} // namespace registry_util + +#endif // BASE_TEST_TEST_REG_UTIL_H_
diff --git a/src/base/test/test_shortcut_win.cc b/src/base/test/test_shortcut_win.cc new file mode 100644 index 0000000..f84c0d0 --- /dev/null +++ b/src/base/test/test_shortcut_win.cc
@@ -0,0 +1,155 @@ +// 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/test/test_shortcut_win.h" + +#include <windows.h> +#include <shlobj.h> +#include <propkey.h> +#include <propvarutil.h> + +#include "base/file_path.h" +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "base/win/scoped_comptr.h" +#include "base/win/windows_version.h" +#include "testing/gtest/include/gtest/gtest.h" + +// propsys.lib is required for PropvariantTo*(). +#pragma comment(lib, "propsys.lib") + +namespace base { +namespace win { + +namespace { + +// Validates |actual_path|'s LongPathName case-insensitively matches +// |expected_path|'s LongPathName. +void ValidatePathsAreEqual(const FilePath& expected_path, + const FilePath& actual_path) { + wchar_t long_expected_path_chars[MAX_PATH] = {0}; + wchar_t long_actual_path_chars[MAX_PATH] = {0}; + + // If |expected_path| is empty confirm immediately that |actual_path| is also + // empty. + if (expected_path.empty()) { + EXPECT_TRUE(actual_path.empty()); + return; + } + + // Proceed with LongPathName matching which will also confirm the paths exist. + EXPECT_NE(0U, ::GetLongPathName( + expected_path.value().c_str(), long_expected_path_chars, MAX_PATH)) + << "Failed to get LongPathName of " << expected_path.value(); + EXPECT_NE(0U, ::GetLongPathName( + actual_path.value().c_str(), long_actual_path_chars, MAX_PATH)) + << "Failed to get LongPathName of " << actual_path.value(); + + FilePath long_expected_path(long_expected_path_chars); + FilePath long_actual_path(long_actual_path_chars); + EXPECT_FALSE(long_expected_path.empty()); + EXPECT_FALSE(long_actual_path.empty()); + + EXPECT_EQ(long_expected_path, long_actual_path); +} + +} // namespace + +void ValidateShortcut(const FilePath& shortcut_path, + const ShortcutProperties& properties) { + ScopedComPtr<IShellLink> i_shell_link; + ScopedComPtr<IPersistFile> i_persist_file; + + wchar_t read_target[MAX_PATH] = {0}; + wchar_t read_working_dir[MAX_PATH] = {0}; + wchar_t read_arguments[MAX_PATH] = {0}; + wchar_t read_description[MAX_PATH] = {0}; + wchar_t read_icon[MAX_PATH] = {0}; + int read_icon_index = 0; + + HRESULT hr; + + // Initialize the shell interfaces. + EXPECT_TRUE(SUCCEEDED(hr = i_shell_link.CreateInstance( + CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER))); + if (FAILED(hr)) + return; + + EXPECT_TRUE(SUCCEEDED(hr = i_persist_file.QueryFrom(i_shell_link))); + if (FAILED(hr)) + return; + + // Load the shortcut. + EXPECT_TRUE(SUCCEEDED(hr = i_persist_file->Load( + shortcut_path.value().c_str(), 0))) << "Failed to load shortcut at " + << shortcut_path.value(); + if (FAILED(hr)) + return; + + if (properties.options & ShortcutProperties::PROPERTIES_TARGET) { + EXPECT_TRUE(SUCCEEDED( + i_shell_link->GetPath(read_target, MAX_PATH, NULL, SLGP_SHORTPATH))); + ValidatePathsAreEqual(properties.target, FilePath(read_target)); + } + + if (properties.options & ShortcutProperties::PROPERTIES_WORKING_DIR) { + EXPECT_TRUE(SUCCEEDED( + i_shell_link->GetWorkingDirectory(read_working_dir, MAX_PATH))); + ValidatePathsAreEqual(properties.working_dir, FilePath(read_working_dir)); + } + + if (properties.options & ShortcutProperties::PROPERTIES_ARGUMENTS) { + EXPECT_TRUE(SUCCEEDED( + i_shell_link->GetArguments(read_arguments, MAX_PATH))); + EXPECT_EQ(properties.arguments, read_arguments); + } + + if (properties.options & ShortcutProperties::PROPERTIES_DESCRIPTION) { + EXPECT_TRUE(SUCCEEDED( + i_shell_link->GetDescription(read_description, MAX_PATH))); + EXPECT_EQ(properties.description, read_description); + } + + if (properties.options & ShortcutProperties::PROPERTIES_ICON) { + EXPECT_TRUE(SUCCEEDED( + i_shell_link->GetIconLocation(read_icon, MAX_PATH, &read_icon_index))); + ValidatePathsAreEqual(properties.icon, FilePath(read_icon)); + EXPECT_EQ(properties.icon_index, read_icon_index); + } + + if (GetVersion() >= VERSION_WIN7) { + ScopedComPtr<IPropertyStore> property_store; + // Note that, as mentioned on MSDN at http://goo.gl/M8h9g, if a property is + // not set, GetValue will return S_OK and the PROPVARIANT will be set to + // VT_EMPTY. + PROPVARIANT pv_app_id, pv_dual_mode; + EXPECT_TRUE(SUCCEEDED(hr = property_store.QueryFrom(i_shell_link))); + if (FAILED(hr)) + return; + EXPECT_EQ(S_OK, property_store->GetValue(PKEY_AppUserModel_ID, &pv_app_id)); + EXPECT_EQ(S_OK, property_store->GetValue(PKEY_AppUserModel_IsDualMode, + &pv_dual_mode)); + + // Note, as mentioned on MSDN at + // http://msdn.microsoft.com/library/windows/desktop/bb776559.aspx, if + // |pv_app_id| is a VT_EMPTY it is successfully converted to the empty + // string as desired. + wchar_t read_app_id[MAX_PATH] = {0}; + PropVariantToString(pv_app_id, read_app_id, MAX_PATH); + if (properties.options & ShortcutProperties::PROPERTIES_APP_ID) + EXPECT_EQ(properties.app_id, read_app_id); + + // Note, as mentioned on MSDN at + // http://msdn.microsoft.com/library/windows/desktop/bb776531.aspx, if + // |pv_dual_mode| is a VT_EMPTY it is successfully converted to false as + // desired. + BOOL read_dual_mode; + PropVariantToBoolean(pv_dual_mode, &read_dual_mode); + if (properties.options & ShortcutProperties::PROPERTIES_DUAL_MODE) + EXPECT_EQ(properties.dual_mode, static_cast<bool>(read_dual_mode)); + } +} + +} // namespace win +} // namespace base
diff --git a/src/base/test/test_shortcut_win.h b/src/base/test/test_shortcut_win.h new file mode 100644 index 0000000..ef75713 --- /dev/null +++ b/src/base/test/test_shortcut_win.h
@@ -0,0 +1,25 @@ +// 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_TEST_TEST_SHORTCUT_WIN_H_ +#define BASE_TEST_TEST_SHORTCUT_WIN_H_ + +#include "base/file_path.h" +#include "base/win/shortcut.h" + +// Windows shortcut functions used only by tests. + +namespace base { +namespace win { + +// Validates that a shortcut exists at |shortcut_path| with the expected +// |properties|. +// Logs gtest failures on failed verifications. +void ValidateShortcut(const FilePath& shortcut_path, + const ShortcutProperties& properties); + +} // namespace win +} // namespace base + +#endif // BASE_TEST_TEST_SHORTCUT_WIN_H_
diff --git a/src/base/test/test_suite.cc b/src/base/test/test_suite.cc new file mode 100644 index 0000000..6e0444d --- /dev/null +++ b/src/base/test/test_suite.cc
@@ -0,0 +1,304 @@ +// 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/test/test_suite.h" + +#include "base/at_exit.h" +#include "base/base_paths.h" +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/debug/debug_on_start_win.h" +#include "base/debug/debugger.h" +#include "base/debug/stack_trace.h" +#include "base/file_path.h" +#include "base/i18n/icu_util.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/process_util.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_timeouts.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +#if defined(OS_MACOSX) +#include "base/mac/scoped_nsautorelease_pool.h" +#if defined(OS_IOS) +#include "base/test/test_listener_ios.h" +#else +#include "base/test/mock_chrome_application_mac.h" +#endif // OS_IOS +#endif // OS_MACOSX + +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) +#include "base/test/test_support_android.h" +#endif + +#if defined(OS_IOS) +#include "base/test/test_support_ios.h" +#endif + +#if defined(TOOLKIT_GTK) +#include <gtk/gtk.h> +#endif + +namespace { + +class MaybeTestDisabler : public testing::EmptyTestEventListener { + public: + virtual void OnTestStart(const testing::TestInfo& test_info) OVERRIDE { + ASSERT_FALSE(TestSuite::IsMarkedMaybe(test_info)) + << "Probably the OS #ifdefs don't include all of the necessary " + "platforms.\nPlease ensure that no tests have the MAYBE_ prefix " + "after the code is preprocessed."; + } +}; + +class TestClientInitializer : public testing::EmptyTestEventListener { + public: + TestClientInitializer() + : old_command_line_(CommandLine::NO_PROGRAM) { + } + + virtual void OnTestStart(const testing::TestInfo& test_info) OVERRIDE { + old_command_line_ = *CommandLine::ForCurrentProcess(); + } + + virtual void OnTestEnd(const testing::TestInfo& test_info) OVERRIDE { + *CommandLine::ForCurrentProcess() = old_command_line_; + } + + private: + CommandLine old_command_line_; + + DISALLOW_COPY_AND_ASSIGN(TestClientInitializer); +}; + +} // namespace + +const char TestSuite::kStrictFailureHandling[] = "strict_failure_handling"; + +TestSuite::TestSuite(int argc, char** argv) : initialized_command_line_(false) { + PreInitialize(argc, argv, true); +} + +TestSuite::TestSuite(int argc, char** argv, bool create_at_exit_manager) + : initialized_command_line_(false) { + PreInitialize(argc, argv, create_at_exit_manager); +} + +TestSuite::~TestSuite() { + if (initialized_command_line_) + CommandLine::Reset(); +} + +void TestSuite::PreInitialize(int argc, char** argv, + bool create_at_exit_manager) { +#if defined(OS_WIN) + testing::GTEST_FLAG(catch_exceptions) = false; +#endif + base::EnableTerminationOnHeapCorruption(); + initialized_command_line_ = CommandLine::Init(argc, argv); + testing::InitGoogleTest(&argc, argv); +#if defined (__LB_LINUX__) + setlocale(LC_ALL, ""); +#elif defined(OS_LINUX) && defined(USE_AURA) + // When calling native char conversion functions (e.g wrctomb) we need to + // have the locale set. In the absence of such a call the "C" locale is the + // default. In the gtk code (below) gtk_init() implicitly sets a locale. + setlocale(LC_ALL, ""); +#elif defined(TOOLKIT_GTK) + gtk_init_check(&argc, &argv); +#endif // defined(TOOLKIT_GTK) + + // On Android, AtExitManager is created in + // testing/android/native_test_wrapper.cc before main() is called. +#if defined(__LB_SHELL__) || defined(COBALT) + if (create_at_exit_manager) { + at_exit_manager_.reset(new base::ShadowingAtExitManager); + } +#elif !defined(OS_ANDROID) + if (create_at_exit_manager) + at_exit_manager_.reset(new base::AtExitManager); +#endif + + // Don't add additional code to this function. Instead add it to + // Initialize(). See bug 6436. +} + + +// static +bool TestSuite::IsMarkedFlaky(const testing::TestInfo& test) { + return strncmp(test.name(), "FLAKY_", 6) == 0; +} + +// static +bool TestSuite::IsMarkedMaybe(const testing::TestInfo& test) { + return strncmp(test.name(), "MAYBE_", 6) == 0; +} + +// static +bool TestSuite::ShouldIgnoreFailure(const testing::TestInfo& test) { + if (CommandLine::ForCurrentProcess()->HasSwitch(kStrictFailureHandling)) + return false; + return IsMarkedFlaky(test); +} + +// static +bool TestSuite::NonIgnoredFailures(const testing::TestInfo& test) { + return test.should_run() && test.result()->Failed() && + !ShouldIgnoreFailure(test); +} + +int TestSuite::GetTestCount(TestMatch test_match) { + testing::UnitTest* instance = testing::UnitTest::GetInstance(); + int count = 0; + + for (int i = 0; i < instance->total_test_case_count(); ++i) { + const testing::TestCase& test_case = *instance->GetTestCase(i); + for (int j = 0; j < test_case.total_test_count(); ++j) { + if (test_match(*test_case.GetTestInfo(j))) { + count++; + } + } + } + + return count; +} + +void TestSuite::CatchMaybeTests() { + testing::TestEventListeners& listeners = + testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new MaybeTestDisabler); +} + +void TestSuite::ResetCommandLine() { + testing::TestEventListeners& listeners = + testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new TestClientInitializer); +} + +// Don't add additional code to this method. Instead add it to +// Initialize(). See bug 6436. +int TestSuite::Run() { +#if defined(OS_MACOSX) + base::mac::ScopedNSAutoreleasePool scoped_pool; +#endif + + Initialize(); + std::string client_func = + CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kTestChildProcess); + + // Check to see if we are being run as a client process. + if (!client_func.empty()) + return multi_process_function_list::InvokeChildProcessTest(client_func); +#if defined(OS_IOS) + base::test_listener_ios::RegisterTestEndListener(); +#endif + int result = RUN_ALL_TESTS(); + + // If there are failed tests, see if we should ignore the failures. + if (result != 0 && GetTestCount(&TestSuite::NonIgnoredFailures) == 0) + result = 0; + + // Display the number of flaky tests. + int flaky_count = GetTestCount(&TestSuite::IsMarkedFlaky); + if (flaky_count) { + printf(" YOU HAVE %d FLAKY %s\n\n", flaky_count, + flaky_count == 1 ? "TEST" : "TESTS"); + } + +#if defined(OS_MACOSX) + // This MUST happen before Shutdown() since Shutdown() tears down + // objects (such as NotificationService::current()) that Cocoa + // objects use to remove themselves as observers. + scoped_pool.Recycle(); +#endif + + Shutdown(); + + return result; +} + +// static +void TestSuite::UnitTestAssertHandler(const std::string& str) { + RAW_LOG(FATAL, str.c_str()); +} + +void TestSuite::SuppressErrorDialogs() { +#if defined(OS_WIN) + UINT new_flags = SEM_FAILCRITICALERRORS | + SEM_NOGPFAULTERRORBOX | + SEM_NOOPENFILEERRORBOX; + + // Preserve existing error mode, as discussed at + // http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx + UINT existing_flags = SetErrorMode(new_flags); + SetErrorMode(existing_flags | new_flags); + +#if defined(_DEBUG) && defined(_HAS_EXCEPTIONS) && (_HAS_EXCEPTIONS == 1) + // Suppress the "Debug Assertion Failed" dialog. + // TODO(hbono): remove this code when gtest has it. + // http://groups.google.com/d/topic/googletestframework/OjuwNlXy5ac/discussion + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); +#endif // defined(_DEBUG) && defined(_HAS_EXCEPTIONS) && (_HAS_EXCEPTIONS == 1) +#endif // defined(OS_WIN) +} + +void TestSuite::Initialize() { +#if defined(OS_MACOSX) && !defined(OS_IOS) + // Some of the app unit tests spin runloops. + mock_cr_app::RegisterMockCrApp(); +#endif + +#if defined(OS_IOS) + InitIOSTestMessageLoop(); +#endif // OS_IOS + +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) + InitAndroidTest(); +#else + // Initialize logging. + FilePath exe; + PathService::Get(base::FILE_EXE, &exe); + FilePath log_filename = exe.ReplaceExtension(FILE_PATH_LITERAL("log")); + logging::InitLogging( + log_filename.value().c_str(), + logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG, + logging::LOCK_LOG_FILE, + logging::DELETE_OLD_LOG_FILE, + logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS); + // We want process and thread IDs because we may have multiple processes. + // Note: temporarily enabled timestamps in an effort to catch bug 6361. + logging::SetLogItems(true, true, true, true); +#endif // else defined(OS_ANDROID) + + CHECK(base::debug::EnableInProcessStackDumping()); +#if defined(OS_WIN) + // Make sure we run with high resolution timer to minimize differences + // between production code and test code. + base::Time::EnableHighResolutionTimer(true); +#endif // defined(OS_WIN) + + // In some cases, we do not want to see standard error dialogs. + if (!base::debug::BeingDebugged() && + !CommandLine::ForCurrentProcess()->HasSwitch("show-error-dialogs")) { + SuppressErrorDialogs(); + base::debug::SetSuppressDebugUI(true); + logging::SetLogAssertHandler(UnitTestAssertHandler); + } + + icu_util::Initialize(); + + CatchMaybeTests(); + ResetCommandLine(); + + TestTimeouts::Initialize(); +} + +void TestSuite::Shutdown() { +}
diff --git a/src/base/test/test_suite.h b/src/base/test/test_suite.h new file mode 100644 index 0000000..1758f86 --- /dev/null +++ b/src/base/test/test_suite.h
@@ -0,0 +1,100 @@ +// 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_TEST_TEST_SUITE_H_ +#define BASE_TEST_TEST_SUITE_H_ + +// Defines a basic test suite framework for running gtest based tests. You can +// instantiate this class in your main function and call its Run method to run +// any gtest based tests that are linked into your executable. + +#include <string> + +#include "base/at_exit.h" +#include "base/memory/scoped_ptr.h" + +namespace testing { +class TestInfo; +} + +namespace base { + +class TestSuite { + public: + // Match function used by the GetTestCount method. + typedef bool (*TestMatch)(const testing::TestInfo&); + + TestSuite(int argc, char** argv); + virtual ~TestSuite(); + + // Returns true if the test is marked as flaky. + static bool IsMarkedFlaky(const testing::TestInfo& test); + + // Returns true if the test is marked as failing. + static bool IsMarkedFailing(const testing::TestInfo& test); + + // Returns true if the test is marked as "MAYBE_". + // When using different prefixes depending on platform, we use MAYBE_ and + // preprocessor directives to replace MAYBE_ with the target prefix. + static bool IsMarkedMaybe(const testing::TestInfo& test); + + // Returns true if the test failure should be ignored. + static bool ShouldIgnoreFailure(const testing::TestInfo& test); + + // Returns true if the test failed and the failure shouldn't be ignored. + static bool NonIgnoredFailures(const testing::TestInfo& test); + + // Returns the number of tests where the match function returns true. + int GetTestCount(TestMatch test_match); + + void CatchMaybeTests(); + + void ResetCommandLine(); + + int Run(); + + // A command-line flag that makes a test failure always result in a non-zero + // process exit code. + static const char kStrictFailureHandling[]; + + protected: + // This constructor is only accessible to specialized test suite + // implementations which need to control the creation of an AtExitManager + // instance for the duration of the test. + TestSuite(int argc, char** argv, bool create_at_exit_manager); + + // By default fatal log messages (e.g. from DCHECKs) result in error dialogs + // which gum up buildbots. Use a minimalistic assert handler which just + // terminates the process. + static void UnitTestAssertHandler(const std::string& str); + + // Disable crash dialogs so that it doesn't gum up the buildbot + virtual void SuppressErrorDialogs(); + + // Override these for custom initialization and shutdown handling. Use these + // instead of putting complex code in your constructor/destructor. + + virtual void Initialize(); + virtual void Shutdown(); + + // Make sure that we setup an AtExitManager so Singleton objects will be + // destroyed. + scoped_ptr<base::AtExitManager> at_exit_manager_; + + private: + // Basic initialization for the test suite happens here. + void PreInitialize(int argc, char** argv, bool create_at_exit_manager); + + bool initialized_command_line_; + + DISALLOW_COPY_AND_ASSIGN(TestSuite); +}; + +} // namespace base + +// TODO(brettw) remove this. This is a temporary hack to allow WebKit to compile +// until we can update it to use "base::" (preventing a two-sided patch). +using base::TestSuite; + +#endif // BASE_TEST_TEST_SUITE_H_
diff --git a/src/base/test/test_support_android.cc b/src/base/test/test_support_android.cc new file mode 100644 index 0000000..8b512f4 --- /dev/null +++ b/src/base/test/test_support_android.cc
@@ -0,0 +1,194 @@ +// 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 <stdarg.h> +#include <string.h> + +#include "base/android/path_utils.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "base/message_loop.h" +#include "base/message_pump_android.h" +#include "base/path_service.h" +#include "base/synchronization/waitable_event.h" + +namespace { + +struct RunState { + RunState(base::MessagePump::Delegate* delegate, int run_depth) + : delegate(delegate), + run_depth(run_depth), + should_quit(false) { + } + + base::MessagePump::Delegate* delegate; + + // Used to count how many Run() invocations are on the stack. + int run_depth; + + // Used to flag that the current Run() invocation should return ASAP. + bool should_quit; +}; + +RunState* g_state = NULL; + +// A singleton WaitableEvent wrapper so we avoid a busy loop in +// MessagePumpForUIStub. Other platforms use the native event loop which blocks +// when there are no pending messages. +class Waitable { + public: + static Waitable* GetInstance() { + return Singleton<Waitable>::get(); + } + + // Signals that there are more work to do. + void Signal() { + waitable_event_.Signal(); + } + + // Blocks until more work is scheduled. + void Block() { + waitable_event_.Wait(); + } + + void Quit() { + g_state->should_quit = true; + Signal(); + } + + private: + friend struct DefaultSingletonTraits<Waitable>; + + Waitable() + : waitable_event_(false, false) { + } + + base::WaitableEvent waitable_event_; + + DISALLOW_COPY_AND_ASSIGN(Waitable); +}; + +// The MessagePumpForUI implementation for test purpose. +class MessagePumpForUIStub : public base::MessagePumpForUI { + virtual void Start(base::MessagePump::Delegate* delegate) OVERRIDE { + NOTREACHED() << "The Start() method shouldn't be called in test, using" + " Run() method should be used."; + } + + virtual void Run(base::MessagePump::Delegate* delegate) OVERRIDE { + // The following was based on message_pump_glib.cc, except we're using a + // WaitableEvent since there are no native message loop to use. + RunState state(delegate, g_state ? g_state->run_depth + 1 : 1); + + RunState* previous_state = g_state; + g_state = &state; + + bool more_work_is_plausible = true; + + for (;;) { + if (!more_work_is_plausible) { + Waitable::GetInstance()->Block(); + if (g_state->should_quit) + break; + } + + more_work_is_plausible = g_state->delegate->DoWork(); + if (g_state->should_quit) + break; + + base::TimeTicks delayed_work_time; + more_work_is_plausible |= + g_state->delegate->DoDelayedWork(&delayed_work_time); + if (g_state->should_quit) + break; + + if (more_work_is_plausible) + continue; + + more_work_is_plausible = g_state->delegate->DoIdleWork(); + if (g_state->should_quit) + break; + + more_work_is_plausible |= !delayed_work_time.is_null(); + } + + g_state = previous_state; + } + + virtual void Quit() OVERRIDE { + Waitable::GetInstance()->Quit(); + } + + virtual void ScheduleWork() OVERRIDE { + Waitable::GetInstance()->Signal(); + } + + virtual void ScheduleDelayedWork( + const base::TimeTicks& delayed_work_time) OVERRIDE { + Waitable::GetInstance()->Signal(); + } + + protected: + virtual ~MessagePumpForUIStub() {} +}; + +base::MessagePump* CreateMessagePumpForUIStub() { + return new MessagePumpForUIStub(); +}; + +// Provides the test path for DIR_MODULE and DIR_ANDROID_APP_DATA. +bool GetTestProviderPath(int key, FilePath* result) { + switch (key) { + case base::DIR_MODULE: { + return base::android::GetExternalStorageDirectory(result); + } + case base::DIR_ANDROID_APP_DATA: { + // For tests, app data is put in external storage. + return base::android::GetExternalStorageDirectory(result); + } + default: + return false; + } +} + +void InitPathProvider(int key) { + FilePath path; + // If failed to override the key, that means the way has not been registered. + if (GetTestProviderPath(key, &path) && !PathService::Override(key, path)) + PathService::RegisterProvider(&GetTestProviderPath, key, key + 1); +} + +} // namespace + +namespace base { + +void InitAndroidTestLogging() { + logging::InitLogging(NULL, + logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG, + logging::DONT_LOCK_LOG_FILE, + logging::DELETE_OLD_LOG_FILE, + logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS); + // To view log output with IDs and timestamps use "adb logcat -v threadtime". + logging::SetLogItems(false, // Process ID + false, // Thread ID + false, // Timestamp + false); // Tick count +} + +void InitAndroidTestPaths() { + InitPathProvider(DIR_MODULE); + InitPathProvider(DIR_ANDROID_APP_DATA); +} + +void InitAndroidTestMessageLoop() { + MessageLoop::InitMessagePumpForUIFactory(&CreateMessagePumpForUIStub); +} + +void InitAndroidTest() { + InitAndroidTestLogging(); + InitAndroidTestPaths(); + InitAndroidTestMessageLoop(); +} +} // namespace base
diff --git a/src/base/test/test_support_android.h b/src/base/test/test_support_android.h new file mode 100644 index 0000000..062785e --- /dev/null +++ b/src/base/test/test_support_android.h
@@ -0,0 +1,26 @@ +// 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_TEST_TEST_SUPPORT_ANDROID_H_ +#define BASE_TEST_TEST_SUPPORT_ANDROID_H_ + +#include "base/base_export.h" + +namespace base { + +// Init logging for tests on Android. Logs will be output into Android's logcat. +BASE_EXPORT void InitAndroidTestLogging(); + +// Init path providers for tests on Android. +BASE_EXPORT void InitAndroidTestPaths(); + +// Init the message loop for tests on Android. +BASE_EXPORT void InitAndroidTestMessageLoop(); + +// Do all of the initializations above. +BASE_EXPORT void InitAndroidTest(); + +} // namespace base + +#endif // BASE_TEST_TEST_SUPPORT_ANDROID_H_
diff --git a/src/base/test/test_support_ios.h b/src/base/test/test_support_ios.h new file mode 100644 index 0000000..35b5b19 --- /dev/null +++ b/src/base/test/test_support_ios.h
@@ -0,0 +1,15 @@ +// 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_TEST_TEST_SUPPORT_IOS_H_ +#define BASE_TEST_TEST_SUPPORT_IOS_H_ + +namespace base { + +// Init the message loop for tests on iOS. +void InitIOSTestMessageLoop(); + +} // namespace base + +#endif // BASE_TEST_TEST_SUPPORT_IOS_H_
diff --git a/src/base/test/test_support_ios.mm b/src/base/test/test_support_ios.mm new file mode 100644 index 0000000..01b5158 --- /dev/null +++ b/src/base/test/test_support_ios.mm
@@ -0,0 +1,23 @@ +// 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/message_loop.h" +#include "base/message_pump_default.h" + +namespace { + +base::MessagePump* CreateMessagePumpForUIForTests() { + // A default MessagePump will do quite nicely in tests. + return new base::MessagePumpDefault(); +} + +} // namespace + +namespace base { + +void InitIOSTestMessageLoop() { + MessageLoop::InitMessagePumpForUIFactory(&CreateMessagePumpForUIForTests); +} + +} // namespace base
diff --git a/src/base/test/test_switches.cc b/src/base/test/test_switches.cc new file mode 100644 index 0000000..79fbad1 --- /dev/null +++ b/src/base/test/test_switches.cc
@@ -0,0 +1,12 @@ +// 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/test/test_switches.h" + +// Time (in milliseconds) that the tests should wait before timing out. +// TODO(phajdan.jr): Clean up the switch names. +const char switches::kTestLargeTimeout[] = "test-large-timeout"; +const char switches::kTestTinyTimeout[] = "test-tiny-timeout"; +const char switches::kUiTestActionTimeout[] = "ui-test-action-timeout"; +const char switches::kUiTestActionMaxTimeout[] = "ui-test-action-max-timeout";
diff --git a/src/base/test/test_switches.h b/src/base/test/test_switches.h new file mode 100644 index 0000000..a5665d4 --- /dev/null +++ b/src/base/test/test_switches.h
@@ -0,0 +1,19 @@ +// 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_TEST_TEST_SWITCHES_H_ +#define BASE_TEST_TEST_SWITCHES_H_ + +namespace switches { + +// All switches in alphabetical order. The switches should be documented +// alongside the definition of their values in the .cc file. +extern const char kTestLargeTimeout[]; +extern const char kTestTinyTimeout[]; +extern const char kUiTestActionTimeout[]; +extern const char kUiTestActionMaxTimeout[]; + +} // namespace switches + +#endif // BASE_TEST_TEST_SWITCHES_H_
diff --git a/src/base/test/test_timeouts.cc b/src/base/test/test_timeouts.cc new file mode 100644 index 0000000..b3fb968 --- /dev/null +++ b/src/base/test/test_timeouts.cc
@@ -0,0 +1,87 @@ +// 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/test/test_timeouts.h" + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/string_number_conversions.h" +#include "base/test/test_switches.h" + +namespace { + +#ifdef ADDRESS_SANITIZER +static const int kTimeoutMultiplier = 2; +#else +static const int kTimeoutMultiplier = 1; +#endif + +// Sets value to the greatest of: +// 1) value's current value multiplied by kTimeoutMultiplier (assuming +// InitializeTimeout is called only once per value). +// 2) min_value. +// 3) the numerical value given by switch_name on the command line multiplied +// by kTimeoutMultiplier. +void InitializeTimeout(const char* switch_name, int min_value, int* value) { + DCHECK(value); + if (CommandLine::ForCurrentProcess()->HasSwitch(switch_name)) { + std::string string_value( + CommandLine::ForCurrentProcess()->GetSwitchValueASCII(switch_name)); + int timeout; + base::StringToInt(string_value, &timeout); + *value = std::max(*value, timeout); + } + *value *= kTimeoutMultiplier; + *value = std::max(*value, min_value); +} + +// Sets value to the greatest of: +// 1) value's current value multiplied by kTimeoutMultiplier. +// 2) 0 +// 3) the numerical value given by switch_name on the command line multiplied +// by kTimeoutMultiplier. +void InitializeTimeout(const char* switch_name, int* value) { + InitializeTimeout(switch_name, 0, value); +} + +} // namespace + +// static +bool TestTimeouts::initialized_ = false; + +// The timeout values should increase in the order they appear in this block. +// static +int TestTimeouts::tiny_timeout_ms_ = 100; +int TestTimeouts::action_timeout_ms_ = 10000; +#ifndef NDEBUG +int TestTimeouts::action_max_timeout_ms_ = 45000; +int TestTimeouts::large_test_timeout_ms_ = 10 * 60 * 1000; +#else +int TestTimeouts::action_max_timeout_ms_ = 30000; +int TestTimeouts::large_test_timeout_ms_ = 10 * 45 * 1000; +#endif // NDEBUG + +// static +void TestTimeouts::Initialize() { + if (initialized_) { + NOTREACHED(); + return; + } + initialized_ = true; + + // Note that these timeouts MUST be initialized in the correct order as + // per the CHECKS below. + InitializeTimeout(switches::kTestTinyTimeout, &tiny_timeout_ms_); + InitializeTimeout(switches::kUiTestActionTimeout, tiny_timeout_ms_, + &action_timeout_ms_); + InitializeTimeout(switches::kUiTestActionMaxTimeout, action_timeout_ms_, + &action_max_timeout_ms_); + InitializeTimeout(switches::kTestLargeTimeout, action_max_timeout_ms_, + &large_test_timeout_ms_); + + // The timeout values should be increasing in the right order. + CHECK(tiny_timeout_ms_ <= action_timeout_ms_); + CHECK(action_timeout_ms_ <= action_max_timeout_ms_); + CHECK(action_max_timeout_ms_ <= large_test_timeout_ms_); +}
diff --git a/src/base/test/test_timeouts.h b/src/base/test/test_timeouts.h new file mode 100644 index 0000000..0c4587e --- /dev/null +++ b/src/base/test/test_timeouts.h
@@ -0,0 +1,58 @@ +// 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_TEST_TEST_TIMEOUTS_H_ +#define BASE_TEST_TEST_TIMEOUTS_H_ + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/time.h" + +// Returns common timeouts to use in tests. Makes it possible to adjust +// the timeouts for different environments (like Valgrind). +class TestTimeouts { + public: + // Initializes the timeouts. Non thread-safe. Should be called exactly once + // by the test suite. + static void Initialize(); + + // Timeout for actions that are expected to finish "almost instantly". + static base::TimeDelta tiny_timeout() { + DCHECK(initialized_); + return base::TimeDelta::FromMilliseconds(tiny_timeout_ms_); + } + + // Timeout to wait for something to happen. If you are not sure + // which timeout to use, this is the one you want. + static base::TimeDelta action_timeout() { + DCHECK(initialized_); + return base::TimeDelta::FromMilliseconds(action_timeout_ms_); + } + + // Timeout longer than the above, but still suitable to use + // multiple times in a single test. Use if the timeout above + // is not sufficient. + static base::TimeDelta action_max_timeout() { + DCHECK(initialized_); + return base::TimeDelta::FromMilliseconds(action_max_timeout_ms_); + } + + // Timeout for a large test that may take a few minutes to run. + static base::TimeDelta large_test_timeout() { + DCHECK(initialized_); + return base::TimeDelta::FromMilliseconds(large_test_timeout_ms_); + } + + private: + static bool initialized_; + + static int tiny_timeout_ms_; + static int action_timeout_ms_; + static int action_max_timeout_ms_; + static int large_test_timeout_ms_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(TestTimeouts); +}; + +#endif // BASE_TEST_TEST_TIMEOUTS_H_
diff --git a/src/base/test/thread_test_helper.cc b/src/base/test/thread_test_helper.cc new file mode 100644 index 0000000..a91517d --- /dev/null +++ b/src/base/test/thread_test_helper.cc
@@ -0,0 +1,38 @@ +// 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/test/thread_test_helper.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +ThreadTestHelper::ThreadTestHelper(MessageLoopProxy* target_thread) + : test_result_(false), + target_thread_(target_thread), + done_event_(false, false) { +} + +bool ThreadTestHelper::Run() { + if (!target_thread_->PostTask( + FROM_HERE, base::Bind(&ThreadTestHelper::RunInThread, this))) { + return false; + } + base::ThreadRestrictions::ScopedAllowWait allow_wait; + done_event_.Wait(); + return test_result_; +} + +void ThreadTestHelper::RunTest() { set_test_result(true); } + +ThreadTestHelper::~ThreadTestHelper() {} + +void ThreadTestHelper::RunInThread() { + RunTest(); + done_event_.Signal(); +} + +} // namespace base
diff --git a/src/base/test/thread_test_helper.h b/src/base/test/thread_test_helper.h new file mode 100644 index 0000000..961982c --- /dev/null +++ b/src/base/test/thread_test_helper.h
@@ -0,0 +1,48 @@ +// 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. + +#ifndef BASE_TEST_THREAD_TEST_HELPER_H_ +#define BASE_TEST_THREAD_TEST_HELPER_H_ + +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop_proxy.h" +#include "base/synchronization/waitable_event.h" + +namespace base { + +// Helper class that executes code on a given thread while blocking on the +// invoking thread. To use, derive from this class and overwrite RunTest. An +// alternative use of this class is to use it directly. It will then block +// until all pending tasks on a given thread have been executed. +class ThreadTestHelper : public RefCountedThreadSafe<ThreadTestHelper> { + public: + explicit ThreadTestHelper(MessageLoopProxy* target_thread); + + // True if RunTest() was successfully executed on the target thread. + bool Run() WARN_UNUSED_RESULT; + + virtual void RunTest(); + + protected: + friend class RefCountedThreadSafe<ThreadTestHelper>; + + virtual ~ThreadTestHelper(); + + // Use this method to store the result of RunTest(). + void set_test_result(bool test_result) { test_result_ = test_result; } + + private: + void RunInThread(); + + bool test_result_; + scoped_refptr<MessageLoopProxy> target_thread_; + WaitableEvent done_event_; + + DISALLOW_COPY_AND_ASSIGN(ThreadTestHelper); +}; + +} // namespace base + +#endif // BASE_TEST_THREAD_TEST_HELPER_H_
diff --git a/src/base/test/time_helpers.cc b/src/base/test/time_helpers.cc new file mode 100644 index 0000000..3914dc2 --- /dev/null +++ b/src/base/test/time_helpers.cc
@@ -0,0 +1,144 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/test/time_helpers.h" + +#include <unicode/ucal.h> +#include <unicode/uloc.h> +#include <unicode/ustring.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "base/time.h" + +// Negative statuses are considered warnings, so this only fails if the status +// value is greater than zero. +#define UCHECK(status) DCHECK_GE(U_ZERO_ERROR, status) << __FUNCTION__ << ": " + +namespace base { +namespace test { +namespace time_helpers { + +namespace { +base::Time UDateToTime(UDate udate) { + return base::Time::UnixEpoch() + + base::TimeDelta::FromMicroseconds(static_cast<int64_t>( + udate * base::Time::kMicrosecondsPerMillisecond)); +} + +const char* ToMonthString(int month) { + switch (month) { + case 1: + return "Jan"; + case 2: + return "Feb"; + case 3: + return "Mar"; + case 4: + return "Apr"; + case 5: + return "May"; + case 6: + return "Jun"; + case 7: + return "Jul"; + case 8: + return "Aug"; + case 9: + return "Sep"; + case 10: + return "Oct"; + case 11: + return "Nov"; + case 12: + return "Dec"; + } + + return "UNK"; +} +} // namespace + +base::Time FieldsToTime(TimeZone timezone, + int year, + int month, + int date, + int hour, + int minute, + int second) { + UErrorCode status = U_ZERO_ERROR; + UChar timezone_buffer[32] = {0}; + UChar* timezone_uname = NULL; + switch (timezone) { + case kTimeZonePacific: + u_strFromUTF8(timezone_buffer, arraysize(timezone_buffer), NULL, + "America/Los_Angeles", -1, &status); + timezone_uname = timezone_buffer; + break; + case kTimeZoneGMT: + u_strFromUTF8(timezone_buffer, arraysize(timezone_buffer), NULL, + "Etc/GMT", -1, &status); + timezone_uname = timezone_buffer; + case kTimeZoneLocal: + // Leave NULL. + break; + } + + UCHECK(status); + UCalendar* calendar = + ucal_open(timezone_uname, -1, uloc_getDefault(), UCAL_DEFAULT, &status); + DCHECK(calendar); + UCHECK(status); + ucal_setMillis(calendar, 0, &status); // Clear the calendar. + UCHECK(status); + ucal_setDateTime(calendar, year, month - 1 + UCAL_JANUARY, date, hour, minute, + second, &status); + UCHECK(status); + UDate udate = ucal_getMillis(calendar, &status); + UCHECK(status); + ucal_close(calendar); + return UDateToTime(udate); +} + +base::Time TestDateToTime(TimeZone timezone) { + return FieldsToTime(timezone, + 2007, // year + 10, // month + 15, // date + 12, // hour + 45, // minute + 0); // second +} + +std::string TimeFormatUTC(base::Time time) { + Time::Exploded exploded; + time.UTCExplode(&exploded); + return base::StringPrintf("%s %02d %02d:%02d:%02d %04d", + ToMonthString(exploded.month), + exploded.day_of_month, exploded.hour, + exploded.minute, exploded.second, exploded.year); +} + +std::string TimeFormatLocal(base::Time time) { + Time::Exploded exploded; + time.LocalExplode(&exploded); + return base::StringPrintf("%s %02d %02d:%02d:%02d %04d", + ToMonthString(exploded.month), + exploded.day_of_month, exploded.hour, + exploded.minute, exploded.second, exploded.year); +} + +} // namespace time_helpers +} // namespace test +} // namespace base
diff --git a/src/base/test/time_helpers.h b/src/base/test/time_helpers.h new file mode 100644 index 0000000..dad3ec6 --- /dev/null +++ b/src/base/test/time_helpers.h
@@ -0,0 +1,68 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// These functions use ICU to generate test values based on specific and local +// time zones. + +#ifndef BASE_TEST_TIME_HELPERS_H_ +#define BASE_TEST_TIME_HELPERS_H_ + +#include <string> + +#include "base/time.h" + +namespace base { +namespace test { +namespace time_helpers { + +// Some TimeZone values that can be used in tests. +enum TimeZone { + kTimeZoneGMT, + kTimeZoneLocal, + kTimeZonePacific, +}; + +// Converts a set of fields in a timezone to a time. +// |timezone| is one of the enumerated timezones. +// |year| is a full 4-digit year +// |month| is a 1-based month (1 = January) +// |date| is a 1-based day-of-month (first day of month is 1) +// |hour| is the 0-based 24-hour clock hour-of-the-day +// (midnight is 0, noon is 12, 3pm is 15) +// |minute| is the 0-based clock minute-of-the-hour (0-59) +// |second| is the 0-based clock second-of-the-minute (0-59) +base::Time FieldsToTime(TimeZone timezone, + int year, + int month, + int date, + int hour, + int minute, + int second); + +// Defines a standard date to use in tests. +// Mon, Oct 15 12:45:00 PDT 2007 +base::Time TestDateToTime(TimeZone timezone); + +// Formats the given time into a UTC something parsable by +// base::Time::FromString(). +std::string TimeFormatUTC(base::Time time); + +// Formats the given time into a local something parsable by +// base::Time::FromString(). +std::string TimeFormatLocal(base::Time time); + +} // namespace time_helpers +} // namespace test +} // namespace base +#endif // BASE_TEST_TIME_HELPERS_H_
diff --git a/src/base/test/trace_event_analyzer.cc b/src/base/test/trace_event_analyzer.cc new file mode 100644 index 0000000..d4ebb04 --- /dev/null +++ b/src/base/test/trace_event_analyzer.cc
@@ -0,0 +1,966 @@ +// 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/test/trace_event_analyzer.h" + +#include <algorithm> +#include <math.h> +#include <set> + +#include "base/json/json_reader.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" + +namespace trace_analyzer { + +// TraceEvent + +TraceEvent::TraceEvent() + : thread(0, 0), + timestamp(0), + phase(TRACE_EVENT_PHASE_BEGIN), + other_event(NULL) { +} + +TraceEvent::~TraceEvent() { +} + +bool TraceEvent::SetFromJSON(const base::Value* event_value) { + if (event_value->GetType() != base::Value::TYPE_DICTIONARY) { + LOG(ERROR) << "Value must be TYPE_DICTIONARY"; + return false; + } + const base::DictionaryValue* dictionary = + static_cast<const base::DictionaryValue*>(event_value); + + std::string phase_str; + const base::DictionaryValue* args = NULL; + + if (!dictionary->GetString("ph", &phase_str)) { + LOG(ERROR) << "ph is missing from TraceEvent JSON"; + return false; + } + + phase = *phase_str.data(); + + bool require_origin = (phase != TRACE_EVENT_PHASE_METADATA); + bool require_id = (phase == TRACE_EVENT_PHASE_ASYNC_BEGIN || + phase == TRACE_EVENT_PHASE_ASYNC_STEP || + phase == TRACE_EVENT_PHASE_ASYNC_END); + + if (require_origin && !dictionary->GetInteger("pid", &thread.process_id)) { + LOG(ERROR) << "pid is missing from TraceEvent JSON"; + return false; + } + if (require_origin && !dictionary->GetInteger("tid", &thread.thread_id)) { + LOG(ERROR) << "tid is missing from TraceEvent JSON"; + return false; + } + if (require_origin && !dictionary->GetDouble("ts", ×tamp)) { + LOG(ERROR) << "ts is missing from TraceEvent JSON"; + return false; + } + if (!dictionary->GetString("cat", &category)) { + LOG(ERROR) << "cat is missing from TraceEvent JSON"; + return false; + } + if (!dictionary->GetString("name", &name)) { + LOG(ERROR) << "name is missing from TraceEvent JSON"; + return false; + } + if (!dictionary->GetDictionary("args", &args)) { + LOG(ERROR) << "args is missing from TraceEvent JSON"; + return false; + } + if (require_id && !dictionary->GetString("id", &id)) { + LOG(ERROR) << "id is missing from ASYNC_BEGIN/ASYNC_END TraceEvent JSON"; + return false; + } + + // For each argument, copy the type and create a trace_analyzer::TraceValue. + base::DictionaryValue::key_iterator keyi = args->begin_keys(); + for (; keyi != args->end_keys(); ++keyi) { + std::string str; + bool boolean = false; + int int_num = 0; + double double_num = 0.0; + const Value* value = NULL; + if (args->GetWithoutPathExpansion(*keyi, &value)) { + if (value->GetAsString(&str)) + arg_strings[*keyi] = str; + else if (value->GetAsInteger(&int_num)) + arg_numbers[*keyi] = static_cast<double>(int_num); + else if (value->GetAsBoolean(&boolean)) + arg_numbers[*keyi] = static_cast<double>(boolean ? 1 : 0); + else if (value->GetAsDouble(&double_num)) + arg_numbers[*keyi] = double_num; + else { + LOG(ERROR) << "Value type of argument is not supported: " << + static_cast<int>(value->GetType()); + return false; // Invalid trace event JSON format. + } + } + } + + return true; +} + +double TraceEvent::GetAbsTimeToOtherEvent() const { + return fabs(other_event->timestamp - timestamp); +} + +bool TraceEvent::GetArgAsString(const std::string& name, + std::string* arg) const { + std::map<std::string, std::string>::const_iterator i = arg_strings.find(name); + if (i != arg_strings.end()) { + *arg = i->second; + return true; + } + return false; +} + +bool TraceEvent::GetArgAsNumber(const std::string& name, + double* arg) const { + std::map<std::string, double>::const_iterator i = arg_numbers.find(name); + if (i != arg_numbers.end()) { + *arg = i->second; + return true; + } + return false; +} + +bool TraceEvent::HasStringArg(const std::string& name) const { + return (arg_strings.find(name) != arg_strings.end()); +} + +bool TraceEvent::HasNumberArg(const std::string& name) const { + return (arg_numbers.find(name) != arg_numbers.end()); +} + +std::string TraceEvent::GetKnownArgAsString(const std::string& name) const { + std::string arg_string; + if (GetArgAsString(name, &arg_string)) + return arg_string; + NOTREACHED(); + return ""; +} + +double TraceEvent::GetKnownArgAsDouble(const std::string& name) const { + double arg_double; + if (GetArgAsNumber(name, &arg_double)) + return arg_double; + NOTREACHED(); + return 0; +} + +int TraceEvent::GetKnownArgAsInt(const std::string& name) const { + double arg_double; + if (GetArgAsNumber(name, &arg_double)) + return static_cast<int>(arg_double); + NOTREACHED(); + return 0; +} + +bool TraceEvent::GetKnownArgAsBool(const std::string& name) const { + double arg_double; + if (GetArgAsNumber(name, &arg_double)) + return (arg_double != 0.0); + NOTREACHED(); + return false; +} + +// QueryNode + +QueryNode::QueryNode(const Query& query) : query_(query) { +} + +QueryNode::~QueryNode() { +} + +// Query + +Query::Query(TraceEventMember member) + : type_(QUERY_EVENT_MEMBER), + operator_(OP_INVALID), + member_(member), + number_(0), + is_pattern_(false) { +} + +Query::Query(TraceEventMember member, const std::string& arg_name) + : type_(QUERY_EVENT_MEMBER), + operator_(OP_INVALID), + member_(member), + number_(0), + string_(arg_name), + is_pattern_(false) { +} + +Query::Query(const Query& query) + : type_(query.type_), + operator_(query.operator_), + left_(query.left_), + right_(query.right_), + member_(query.member_), + number_(query.number_), + string_(query.string_), + is_pattern_(query.is_pattern_) { +} + +Query::~Query() { +} + +Query Query::String(const std::string& str) { + return Query(str); +} + +Query Query::Double(double num) { + return Query(num); +} + +Query Query::Int(int32 num) { + return Query(static_cast<double>(num)); +} + +Query Query::Uint(uint32 num) { + return Query(static_cast<double>(num)); +} + +Query Query::Bool(bool boolean) { + return Query(boolean ? 1.0 : 0.0); +} + +Query Query::Phase(char phase) { + return Query(static_cast<double>(phase)); +} + +Query Query::Pattern(const std::string& pattern) { + Query query(pattern); + query.is_pattern_ = true; + return query; +} + +bool Query::Evaluate(const TraceEvent& event) const { + // First check for values that can convert to bool. + + // double is true if != 0: + double bool_value = 0.0; + bool is_bool = GetAsDouble(event, &bool_value); + if (is_bool) + return (bool_value != 0.0); + + // string is true if it is non-empty: + std::string str_value; + bool is_str = GetAsString(event, &str_value); + if (is_str) + return !str_value.empty(); + + DCHECK(type_ == QUERY_BOOLEAN_OPERATOR) + << "Invalid query: missing boolean expression"; + DCHECK(left_.get() && (right_.get() || is_unary_operator())); + + if (is_comparison_operator()) { + DCHECK(left().is_value() && right().is_value()) + << "Invalid query: comparison operator used between event member and " + "value."; + bool compare_result = false; + if (CompareAsDouble(event, &compare_result)) + return compare_result; + else if (CompareAsString(event, &compare_result)) + return compare_result; + return false; + } + // It's a logical operator. + switch (operator_) { + case OP_AND: + return left().Evaluate(event) && right().Evaluate(event); + case OP_OR: + return left().Evaluate(event) || right().Evaluate(event); + case OP_NOT: + return !left().Evaluate(event); + default: + NOTREACHED(); + } + + NOTREACHED(); + return false; +} + +bool Query::CompareAsDouble(const TraceEvent& event, bool* result) const { + double lhs, rhs; + if (!left().GetAsDouble(event, &lhs) || !right().GetAsDouble(event, &rhs)) + return false; + switch (operator_) { + case OP_EQ: + *result = (lhs == rhs); + return true; + case OP_NE: + *result = (lhs != rhs); + return true; + case OP_LT: + *result = (lhs < rhs); + return true; + case OP_LE: + *result = (lhs <= rhs); + return true; + case OP_GT: + *result = (lhs > rhs); + return true; + case OP_GE: + *result = (lhs >= rhs); + return true; + default: + NOTREACHED(); + return false; + } + return true; +} + +bool Query::CompareAsString(const TraceEvent& event, bool* result) const { + std::string lhs, rhs; + if (!left().GetAsString(event, &lhs) || !right().GetAsString(event, &rhs)) + return false; + switch (operator_) { + case OP_EQ: + if (right().is_pattern_) + *result = MatchPattern(lhs, rhs); + else if (left().is_pattern_) + *result = MatchPattern(rhs, lhs); + else + *result = (lhs == rhs); + return true; + case OP_NE: + if (right().is_pattern_) + *result = !MatchPattern(lhs, rhs); + else if (left().is_pattern_) + *result = !MatchPattern(rhs, lhs); + else + *result = (lhs != rhs); + return true; + case OP_LT: + *result = (lhs < rhs); + return true; + case OP_LE: + *result = (lhs <= rhs); + return true; + case OP_GT: + *result = (lhs > rhs); + return true; + case OP_GE: + *result = (lhs >= rhs); + return true; + default: + NOTREACHED(); + return false; + } + return true; +} + +bool Query::EvaluateArithmeticOperator(const TraceEvent& event, + double* num) const { + DCHECK(type_ == QUERY_ARITHMETIC_OPERATOR); + DCHECK(left_.get() && (right_.get() || is_unary_operator())); + + double lhs = 0, rhs = 0; + if (!left().GetAsDouble(event, &lhs)) + return false; + if (!is_unary_operator() && !right().GetAsDouble(event, &rhs)) + return false; + + switch (operator_) { + case OP_ADD: + *num = lhs + rhs; + return true; + case OP_SUB: + *num = lhs - rhs; + return true; + case OP_MUL: + *num = lhs * rhs; + return true; + case OP_DIV: + *num = lhs / rhs; + return true; + case OP_MOD: + *num = static_cast<double>(static_cast<int64>(lhs) % + static_cast<int64>(rhs)); + return true; + case OP_NEGATE: + *num = -lhs; + return true; + default: + NOTREACHED(); + return false; + } +} + +bool Query::GetAsDouble(const TraceEvent& event, double* num) const { + switch (type_) { + case QUERY_ARITHMETIC_OPERATOR: + return EvaluateArithmeticOperator(event, num); + case QUERY_EVENT_MEMBER: + return GetMemberValueAsDouble(event, num); + case QUERY_NUMBER: + *num = number_; + return true; + default: + return false; + } +} + +bool Query::GetAsString(const TraceEvent& event, std::string* str) const { + switch (type_) { + case QUERY_EVENT_MEMBER: + return GetMemberValueAsString(event, str); + case QUERY_STRING: + *str = string_; + return true; + default: + return false; + } +} + +bool Query::GetMemberValueAsDouble(const TraceEvent& event, + double* num) const { + DCHECK(type_ == QUERY_EVENT_MEMBER); + + // This could be a request for a member of |event| or a member of |event|'s + // associated event. Store the target event in the_event: + const TraceEvent* the_event = (member_ < OTHER_PID) ? + &event : event.other_event; + + // Request for member of associated event, but there is no associated event. + if (!the_event) + return false; + + switch (member_) { + case EVENT_PID: + case OTHER_PID: + *num = static_cast<double>(the_event->thread.process_id); + return true; + case EVENT_TID: + case OTHER_TID: + *num = static_cast<double>(the_event->thread.thread_id); + return true; + case EVENT_TIME: + case OTHER_TIME: + *num = the_event->timestamp; + return true; + case EVENT_DURATION: + if (the_event->has_other_event()) { + *num = the_event->GetAbsTimeToOtherEvent(); + return true; + } + return false; + case EVENT_PHASE: + case OTHER_PHASE: + *num = static_cast<double>(the_event->phase); + return true; + case EVENT_HAS_STRING_ARG: + case OTHER_HAS_STRING_ARG: + *num = (the_event->HasStringArg(string_) ? 1.0 : 0.0); + return true; + case EVENT_HAS_NUMBER_ARG: + case OTHER_HAS_NUMBER_ARG: + *num = (the_event->HasNumberArg(string_) ? 1.0 : 0.0); + return true; + case EVENT_ARG: + case OTHER_ARG: { + // Search for the argument name and return its value if found. + std::map<std::string, double>::const_iterator num_i = + the_event->arg_numbers.find(string_); + if (num_i == the_event->arg_numbers.end()) + return false; + *num = num_i->second; + return true; + } + case EVENT_HAS_OTHER: + // return 1.0 (true) if the other event exists + *num = event.other_event ? 1.0 : 0.0; + return true; + default: + return false; + } +} + +bool Query::GetMemberValueAsString(const TraceEvent& event, + std::string* str) const { + DCHECK(type_ == QUERY_EVENT_MEMBER); + + // This could be a request for a member of |event| or a member of |event|'s + // associated event. Store the target event in the_event: + const TraceEvent* the_event = (member_ < OTHER_PID) ? + &event : event.other_event; + + // Request for member of associated event, but there is no associated event. + if (!the_event) + return false; + + switch (member_) { + case EVENT_CATEGORY: + case OTHER_CATEGORY: + *str = the_event->category; + return true; + case EVENT_NAME: + case OTHER_NAME: + *str = the_event->name; + return true; + case EVENT_ID: + case OTHER_ID: + *str = the_event->id; + return true; + case EVENT_ARG: + case OTHER_ARG: { + // Search for the argument name and return its value if found. + std::map<std::string, std::string>::const_iterator str_i = + the_event->arg_strings.find(string_); + if (str_i == the_event->arg_strings.end()) + return false; + *str = str_i->second; + return true; + } + default: + return false; + } +} + +Query::Query(const std::string& str) + : type_(QUERY_STRING), + operator_(OP_INVALID), + member_(EVENT_INVALID), + number_(0), + string_(str), + is_pattern_(false) { +} + +Query::Query(double num) + : type_(QUERY_NUMBER), + operator_(OP_INVALID), + member_(EVENT_INVALID), + number_(num), + is_pattern_(false) { +} +const Query& Query::left() const { + return left_->query(); +} + +const Query& Query::right() const { + return right_->query(); +} + +Query Query::operator==(const Query& rhs) const { + return Query(*this, rhs, OP_EQ); +} + +Query Query::operator!=(const Query& rhs) const { + return Query(*this, rhs, OP_NE); +} + +Query Query::operator<(const Query& rhs) const { + return Query(*this, rhs, OP_LT); +} + +Query Query::operator<=(const Query& rhs) const { + return Query(*this, rhs, OP_LE); +} + +Query Query::operator>(const Query& rhs) const { + return Query(*this, rhs, OP_GT); +} + +Query Query::operator>=(const Query& rhs) const { + return Query(*this, rhs, OP_GE); +} + +Query Query::operator&&(const Query& rhs) const { + return Query(*this, rhs, OP_AND); +} + +Query Query::operator||(const Query& rhs) const { + return Query(*this, rhs, OP_OR); +} + +Query Query::operator!() const { + return Query(*this, OP_NOT); +} + +Query Query::operator+(const Query& rhs) const { + return Query(*this, rhs, OP_ADD); +} + +Query Query::operator-(const Query& rhs) const { + return Query(*this, rhs, OP_SUB); +} + +Query Query::operator*(const Query& rhs) const { + return Query(*this, rhs, OP_MUL); +} + +Query Query::operator/(const Query& rhs) const { + return Query(*this, rhs, OP_DIV); +} + +Query Query::operator%(const Query& rhs) const { + return Query(*this, rhs, OP_MOD); +} + +Query Query::operator-() const { + return Query(*this, OP_NEGATE); +} + + +Query::Query(const Query& left, const Query& right, Operator binary_op) + : operator_(binary_op), + left_(new QueryNode(left)), + right_(new QueryNode(right)), + member_(EVENT_INVALID), + number_(0) { + type_ = (binary_op < OP_ADD ? + QUERY_BOOLEAN_OPERATOR : QUERY_ARITHMETIC_OPERATOR); +} + +Query::Query(const Query& left, Operator unary_op) + : operator_(unary_op), + left_(new QueryNode(left)), + member_(EVENT_INVALID), + number_(0) { + type_ = (unary_op < OP_ADD ? + QUERY_BOOLEAN_OPERATOR : QUERY_ARITHMETIC_OPERATOR); +} + +namespace { + +// Search |events| for |query| and add matches to |output|. +size_t FindMatchingEvents(const std::vector<TraceEvent>& events, + const Query& query, + TraceEventVector* output) { + for (size_t i = 0; i < events.size(); ++i) { + if (query.Evaluate(events[i])) + output->push_back(&events[i]); + } + return output->size(); +} + +bool ParseEventsFromJson(const std::string& json, + std::vector<TraceEvent>* output) { + scoped_ptr<base::Value> root; + root.reset(base::JSONReader::Read(json)); + + ListValue* root_list = NULL; + if (!root.get() || !root->GetAsList(&root_list)) + return false; + + for (size_t i = 0; i < root_list->GetSize(); ++i) { + Value* item = NULL; + if (root_list->Get(i, &item)) { + TraceEvent event; + if (event.SetFromJSON(item)) + output->push_back(event); + else + return false; + } + } + + return true; +} + +} // namespace + +// TraceAnalyzer + +TraceAnalyzer::TraceAnalyzer() : allow_assocation_changes_(true) { +} + +TraceAnalyzer::~TraceAnalyzer() { +} + +// static +TraceAnalyzer* TraceAnalyzer::Create(const std::string& json_events) { + scoped_ptr<TraceAnalyzer> analyzer(new TraceAnalyzer()); + if (analyzer->SetEvents(json_events)) + return analyzer.release(); + return NULL; +} + +bool TraceAnalyzer::SetEvents(const std::string& json_events) { + raw_events_.clear(); + if (!ParseEventsFromJson(json_events, &raw_events_)) + return false; + std::stable_sort(raw_events_.begin(), raw_events_.end()); + ParseMetadata(); + return true; +} + +void TraceAnalyzer::AssociateBeginEndEvents() { + using trace_analyzer::Query; + + Query begin(Query::EventPhaseIs(TRACE_EVENT_PHASE_BEGIN)); + Query end(Query::EventPhaseIs(TRACE_EVENT_PHASE_END)); + Query match(Query::EventName() == Query::OtherName() && + Query::EventCategory() == Query::OtherCategory() && + Query::EventTid() == Query::OtherTid() && + Query::EventPid() == Query::OtherPid()); + + AssociateEvents(begin, end, match); +} + +void TraceAnalyzer::AssociateAsyncBeginEndEvents() { + using trace_analyzer::Query; + + Query begin( + Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_BEGIN) || + Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP)); + Query end(Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_END) || + Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP)); + Query match(Query::EventName() == Query::OtherName() && + Query::EventCategory() == Query::OtherCategory() && + Query::EventId() == Query::OtherId()); + + AssociateEvents(begin, end, match); +} + +void TraceAnalyzer::AssociateEvents(const Query& first, + const Query& second, + const Query& match) { + DCHECK(allow_assocation_changes_) << "AssociateEvents not allowed after " + "FindEvents"; + + // Search for matching begin/end event pairs. When a matching end is found, + // it is associated with the begin event. + std::vector<TraceEvent*> begin_stack; + for (size_t event_index = 0; event_index < raw_events_.size(); + ++event_index) { + + TraceEvent& this_event = raw_events_[event_index]; + + if (second.Evaluate(this_event)) { + // Search stack for matching begin, starting from end. + for (int stack_index = static_cast<int>(begin_stack.size()) - 1; + stack_index >= 0; --stack_index) { + TraceEvent& begin_event = *begin_stack[stack_index]; + + // Temporarily set other to test against the match query. + const TraceEvent* other_backup = begin_event.other_event; + begin_event.other_event = &this_event; + if (match.Evaluate(begin_event)) { + // Found a matching begin/end pair. + // Erase the matching begin event index from the stack. + begin_stack.erase(begin_stack.begin() + stack_index); + break; + } + + // Not a match, restore original other and continue. + begin_event.other_event = other_backup; + } + } + // Even if this_event is a |second| event that has matched an earlier + // |first| event, it can still also be a |first| event and be associated + // with a later |second| event. + if (first.Evaluate(this_event)) { + begin_stack.push_back(&this_event); + } + } +} + +void TraceAnalyzer::MergeAssociatedEventArgs() { + for (size_t i = 0; i < raw_events_.size(); ++i) { + // Merge all associated events with the first event. + const TraceEvent* other = raw_events_[i].other_event; + // Avoid looping by keeping set of encountered TraceEvents. + std::set<const TraceEvent*> encounters; + encounters.insert(&raw_events_[i]); + while (other && encounters.find(other) == encounters.end()) { + encounters.insert(other); + raw_events_[i].arg_numbers.insert( + other->arg_numbers.begin(), + other->arg_numbers.end()); + raw_events_[i].arg_strings.insert( + other->arg_strings.begin(), + other->arg_strings.end()); + other = other->other_event; + } + } +} + +size_t TraceAnalyzer::FindEvents(const Query& query, TraceEventVector* output) { + allow_assocation_changes_ = false; + output->clear(); + return FindMatchingEvents(raw_events_, query, output); +} + +const TraceEvent* TraceAnalyzer::FindFirstOf(const Query& query) { + TraceEventVector output; + if (FindEvents(query, &output) > 0) + return output.front(); + return NULL; +} + +const TraceEvent* TraceAnalyzer::FindLastOf(const Query& query) { + TraceEventVector output; + if (FindEvents(query, &output) > 0) + return output.back(); + return NULL; +} + +const std::string& TraceAnalyzer::GetThreadName( + const TraceEvent::ProcessThreadID& thread) { + // If thread is not found, just add and return empty string. + return thread_names_[thread]; +} + +void TraceAnalyzer::ParseMetadata() { + for (size_t i = 0; i < raw_events_.size(); ++i) { + TraceEvent& this_event = raw_events_[i]; + // Check for thread name metadata. + if (this_event.phase != TRACE_EVENT_PHASE_METADATA || + this_event.name != "thread_name") + continue; + std::map<std::string, std::string>::const_iterator string_it = + this_event.arg_strings.find("name"); + if (string_it != this_event.arg_strings.end()) + thread_names_[this_event.thread] = string_it->second; + } +} + +// TraceEventVector utility functions. + +bool GetRateStats(const TraceEventVector& events, + RateStats* stats, + const RateStatsOptions* options) { + CHECK(stats); + // Need at least 3 events to calculate rate stats. + const size_t kMinEvents = 3; + if (events.size() < kMinEvents) { + LOG(ERROR) << "Not enough events: " << events.size(); + return false; + } + + std::vector<double> deltas; + size_t num_deltas = events.size() - 1; + for (size_t i = 0; i < num_deltas; ++i) { + double delta = events.at(i + 1)->timestamp - events.at(i)->timestamp; + if (delta < 0.0) { + LOG(ERROR) << "Events are out of order"; + return false; + } + deltas.push_back(delta); + } + + std::sort(deltas.begin(), deltas.end()); + + if (options) { + if (options->trim_min + options->trim_max > events.size() - kMinEvents) { + LOG(ERROR) << "Attempt to trim too many events"; + return false; + } + deltas.erase(deltas.begin(), deltas.begin() + options->trim_min); + deltas.erase(deltas.end() - options->trim_max, deltas.end()); + } + + num_deltas = deltas.size(); + double delta_sum = 0.0; + for (size_t i = 0; i < num_deltas; ++i) + delta_sum += deltas[i]; + + stats->min_us = *std::min_element(deltas.begin(), deltas.end()); + stats->max_us = *std::max_element(deltas.begin(), deltas.end()); + stats->mean_us = delta_sum / static_cast<double>(num_deltas); + + double sum_mean_offsets_squared = 0.0; + for (size_t i = 0; i < num_deltas; ++i) { + double offset = fabs(deltas[i] - stats->mean_us); + sum_mean_offsets_squared += offset * offset; + } + stats->standard_deviation_us = + sum_mean_offsets_squared / static_cast<double>(num_deltas - 1); + + return true; +} + +bool FindFirstOf(const TraceEventVector& events, + const Query& query, + size_t position, + size_t* return_index) { + CHECK(return_index); + for (size_t i = position; i < events.size(); ++i) { + if (query.Evaluate(*events.at(i))) { + *return_index = i; + return true; + } + } + return false; +} + +bool FindLastOf(const TraceEventVector& events, + const Query& query, + size_t position, + size_t* return_index) { + CHECK(return_index); + if (events.empty()) + return false; + position = (position < events.size()) ? position : events.size() - 1; + for (;;) { + if (query.Evaluate(*events.at(position))) { + *return_index = position; + return true; + } + if (position == 0) + return false; + --position; + } + return false; +} + +bool FindClosest(const TraceEventVector& events, + const Query& query, + size_t position, + size_t* return_closest, + size_t* return_second_closest) { + CHECK(return_closest); + if (events.empty() || position >= events.size()) + return false; + size_t closest = events.size(); + size_t second_closest = events.size(); + for (size_t i = 0; i < events.size(); ++i) { + if (!query.Evaluate(*events.at(i))) + continue; + if (closest == events.size()) { + closest = i; + continue; + } + if (fabs(events.at(i)->timestamp - events.at(position)->timestamp) < + fabs(events.at(closest)->timestamp - events.at(position)->timestamp)) { + second_closest = closest; + closest = i; + } else if (second_closest == events.size()) { + second_closest = i; + } + } + + if (closest < events.size() && + (!return_second_closest || second_closest < events.size())) { + *return_closest = closest; + if (return_second_closest) + *return_second_closest = second_closest; + return true; + } + + return false; +} + +size_t CountMatches(const TraceEventVector& events, + const Query& query, + size_t begin_position, + size_t end_position) { + if (begin_position >= events.size()) + return 0u; + end_position = (end_position < events.size()) ? end_position : events.size(); + size_t count = 0u; + for (size_t i = begin_position; i < end_position; ++i) { + if (query.Evaluate(*events.at(i))) + ++count; + } + return count; +} + +} // namespace trace_analyzer
diff --git a/src/base/test/trace_event_analyzer.h b/src/base/test/trace_event_analyzer.h new file mode 100644 index 0000000..ddd1a00 --- /dev/null +++ b/src/base/test/trace_event_analyzer.h
@@ -0,0 +1,684 @@ +// 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. + +// Use trace_analyzer::Query and trace_analyzer::TraceAnalyzer to search for +// specific trace events that were generated by the trace_event.h API. +// +// Basic procedure: +// - Get trace events JSON string from base::debug::TraceLog. +// - Create TraceAnalyzer with JSON string. +// - Call TraceAnalyzer::AssociateBeginEndEvents (optional). +// - Call TraceAnalyzer::AssociateEvents (zero or more times). +// - Call TraceAnalyzer::FindEvents with queries to find specific events. +// +// A Query is a boolean expression tree that evaluates to true or false for a +// given trace event. Queries can be combined into a tree using boolean, +// arithmetic and comparison operators that refer to data of an individual trace +// event. +// +// The events are returned as trace_analyzer::TraceEvent objects. +// TraceEvent contains a single trace event's data, as well as a pointer to +// a related trace event. The related trace event is typically the matching end +// of a begin event or the matching begin of an end event. +// +// The following examples use this basic setup code to construct TraceAnalyzer +// with the json trace string retrieved from TraceLog and construct an event +// vector for retrieving events: +// +// TraceAnalyzer analyzer(json_events); +// TraceEventVector events; +// +// EXAMPLE 1: Find events named "my_event". +// +// analyzer.FindEvents(Query(EVENT_NAME) == "my_event", &events); +// +// EXAMPLE 2: Find begin events named "my_event" with duration > 1 second. +// +// Query q = (Query(EVENT_NAME) == Query::String("my_event") && +// Query(EVENT_PHASE) == Query::Phase(TRACE_EVENT_PHASE_BEGIN) && +// Query(EVENT_DURATION) > Query::Double(1000000.0)); +// analyzer.FindEvents(q, &events); +// +// EXAMPLE 3: Associating event pairs across threads. +// +// If the test needs to analyze something that starts and ends on different +// threads, the test needs to use INSTANT events. The typical procedure is to +// specify the same unique ID as a TRACE_EVENT argument on both the start and +// finish INSTANT events. Then use the following procedure to associate those +// events. +// +// Step 1: instrument code with custom begin/end trace events. +// [Thread 1 tracing code] +// TRACE_EVENT_INSTANT1("test_latency", "timing1_begin", "id", 3); +// [Thread 2 tracing code] +// TRACE_EVENT_INSTANT1("test_latency", "timing1_end", "id", 3); +// +// Step 2: associate these custom begin/end pairs. +// Query begin(Query(EVENT_NAME) == Query::String("timing1_begin")); +// Query end(Query(EVENT_NAME) == Query::String("timing1_end")); +// Query match(Query(EVENT_ARG, "id") == Query(OTHER_ARG, "id")); +// analyzer.AssociateEvents(begin, end, match); +// +// Step 3: search for "timing1_begin" events with existing other event. +// Query q = (Query(EVENT_NAME) == Query::String("timing1_begin") && +// Query(EVENT_HAS_OTHER)); +// analyzer.FindEvents(q, &events); +// +// Step 4: analyze events, such as checking durations. +// for (size_t i = 0; i < events.size(); ++i) { +// double duration; +// EXPECT_TRUE(events[i].GetAbsTimeToOtherEvent(&duration)); +// EXPECT_LT(duration, 1000000.0/60.0); // expect less than 1/60 second. +// } + + +#ifndef BASE_TEST_TRACE_EVENT_ANALYZER_H_ +#define BASE_TEST_TRACE_EVENT_ANALYZER_H_ + +#include <map> + +#include "base/debug/trace_event.h" +#include "base/memory/ref_counted.h" + +namespace base { +class Value; +} + +namespace trace_analyzer { +class QueryNode; + +// trace_analyzer::TraceEvent is a more convenient form of the +// base::debug::TraceEvent class to make tracing-based tests easier to write. +struct TraceEvent { + // ProcessThreadID contains a Process ID and Thread ID. + struct ProcessThreadID { + ProcessThreadID() : process_id(0), thread_id(0) {} + ProcessThreadID(int process_id, int thread_id) + : process_id(process_id), thread_id(thread_id) {} + bool operator< (const ProcessThreadID& rhs) const { + if (process_id != rhs.process_id) + return process_id < rhs.process_id; + return thread_id < rhs.thread_id; + } + int process_id; + int thread_id; + }; + + TraceEvent(); + ~TraceEvent(); + + bool SetFromJSON(const base::Value* event_value) WARN_UNUSED_RESULT; + + bool operator< (const TraceEvent& rhs) const { + return timestamp < rhs.timestamp; + } + + bool has_other_event() const { return other_event != NULL; } + + // Returns absolute duration in microseconds between this event and other + // event. Must have already verified that other_event exists by + // Query(EVENT_HAS_OTHER) or by calling has_other_event(). + double GetAbsTimeToOtherEvent() const; + + // Return the argument value if it exists and it is a string. + bool GetArgAsString(const std::string& name, std::string* arg) const; + // Return the argument value if it exists and it is a number. + bool GetArgAsNumber(const std::string& name, double* arg) const; + + // Check if argument exists and is string. + bool HasStringArg(const std::string& name) const; + // Check if argument exists and is number (double, int or bool). + bool HasNumberArg(const std::string& name) const; + + // Get known existing arguments as specific types. + // Useful when you have already queried the argument with + // Query(HAS_NUMBER_ARG) or Query(HAS_STRING_ARG). + std::string GetKnownArgAsString(const std::string& name) const; + double GetKnownArgAsDouble(const std::string& name) const; + int GetKnownArgAsInt(const std::string& name) const; + bool GetKnownArgAsBool(const std::string& name) const; + + // Process ID and Thread ID. + ProcessThreadID thread; + + // Time since epoch in microseconds. + // Stored as double to match its JSON representation. + double timestamp; + + char phase; + + std::string category; + + std::string name; + + std::string id; + + // All numbers and bool values from TraceEvent args are cast to double. + // bool becomes 1.0 (true) or 0.0 (false). + std::map<std::string, double> arg_numbers; + + std::map<std::string, std::string> arg_strings; + + // The other event associated with this event (or NULL). + const TraceEvent* other_event; +}; + +typedef std::vector<const TraceEvent*> TraceEventVector; + +class Query { + public: + Query(const Query& query); + + ~Query(); + + //////////////////////////////////////////////////////////////// + // Query literal values + + // Compare with the given string. + static Query String(const std::string& str); + + // Compare with the given number. + static Query Double(double num); + static Query Int(int32 num); + static Query Uint(uint32 num); + + // Compare with the given bool. + static Query Bool(bool boolean); + + // Compare with the given phase. + static Query Phase(char phase); + + // Compare with the given string pattern. Only works with == and != operators. + // Example: Query(EVENT_NAME) == Query::Pattern("MyEvent*") + static Query Pattern(const std::string& pattern); + + //////////////////////////////////////////////////////////////// + // Query event members + + static Query EventPid() { return Query(EVENT_PID); } + + static Query EventTid() { return Query(EVENT_TID); } + + // Return the timestamp of the event in microseconds since epoch. + static Query EventTime() { return Query(EVENT_TIME); } + + // Return the absolute time between event and other event in microseconds. + // Only works if Query::EventHasOther() == true. + static Query EventDuration() { return Query(EVENT_DURATION); } + + static Query EventPhase() { return Query(EVENT_PHASE); } + + static Query EventCategory() { return Query(EVENT_CATEGORY); } + + static Query EventName() { return Query(EVENT_NAME); } + + static Query EventId() { return Query(EVENT_ID); } + + static Query EventPidIs(int process_id) { + return Query(EVENT_PID) == Query::Int(process_id); + } + + static Query EventTidIs(int thread_id) { + return Query(EVENT_TID) == Query::Int(thread_id); + } + + static Query EventThreadIs(const TraceEvent::ProcessThreadID& thread) { + return EventPidIs(thread.process_id) && EventTidIs(thread.thread_id); + } + + static Query EventTimeIs(double timestamp) { + return Query(EVENT_TIME) == Query::Double(timestamp); + } + + static Query EventDurationIs(double duration) { + return Query(EVENT_DURATION) == Query::Double(duration); + } + + static Query EventPhaseIs(char phase) { + return Query(EVENT_PHASE) == Query::Phase(phase); + } + + static Query EventCategoryIs(const std::string& category) { + return Query(EVENT_CATEGORY) == Query::String(category); + } + + static Query EventNameIs(const std::string& name) { + return Query(EVENT_NAME) == Query::String(name); + } + + static Query EventIdIs(const std::string& id) { + return Query(EVENT_ID) == Query::String(id); + } + + // Evaluates to true if arg exists and is a string. + static Query EventHasStringArg(const std::string& arg_name) { + return Query(EVENT_HAS_STRING_ARG, arg_name); + } + + // Evaluates to true if arg exists and is a number. + // Number arguments include types double, int and bool. + static Query EventHasNumberArg(const std::string& arg_name) { + return Query(EVENT_HAS_NUMBER_ARG, arg_name); + } + + // Evaluates to arg value (string or number). + static Query EventArg(const std::string& arg_name) { + return Query(EVENT_ARG, arg_name); + } + + // Return true if associated event exists. + static Query EventHasOther() { return Query(EVENT_HAS_OTHER); } + + // Access the associated other_event's members: + + static Query OtherPid() { return Query(OTHER_PID); } + + static Query OtherTid() { return Query(OTHER_TID); } + + static Query OtherTime() { return Query(OTHER_TIME); } + + static Query OtherPhase() { return Query(OTHER_PHASE); } + + static Query OtherCategory() { return Query(OTHER_CATEGORY); } + + static Query OtherName() { return Query(OTHER_NAME); } + + static Query OtherId() { return Query(OTHER_ID); } + + static Query OtherPidIs(int process_id) { + return Query(OTHER_PID) == Query::Int(process_id); + } + + static Query OtherTidIs(int thread_id) { + return Query(OTHER_TID) == Query::Int(thread_id); + } + + static Query OtherThreadIs(const TraceEvent::ProcessThreadID& thread) { + return OtherPidIs(thread.process_id) && OtherTidIs(thread.thread_id); + } + + static Query OtherTimeIs(double timestamp) { + return Query(OTHER_TIME) == Query::Double(timestamp); + } + + static Query OtherPhaseIs(char phase) { + return Query(OTHER_PHASE) == Query::Phase(phase); + } + + static Query OtherCategoryIs(const std::string& category) { + return Query(OTHER_CATEGORY) == Query::String(category); + } + + static Query OtherNameIs(const std::string& name) { + return Query(OTHER_NAME) == Query::String(name); + } + + static Query OtherIdIs(const std::string& id) { + return Query(OTHER_ID) == Query::String(id); + } + + // Evaluates to true if arg exists and is a string. + static Query OtherHasStringArg(const std::string& arg_name) { + return Query(OTHER_HAS_STRING_ARG, arg_name); + } + + // Evaluates to true if arg exists and is a number. + // Number arguments include types double, int and bool. + static Query OtherHasNumberArg(const std::string& arg_name) { + return Query(OTHER_HAS_NUMBER_ARG, arg_name); + } + + // Evaluates to arg value (string or number). + static Query OtherArg(const std::string& arg_name) { + return Query(OTHER_ARG, arg_name); + } + + //////////////////////////////////////////////////////////////// + // Common queries: + + // Find BEGIN events that have a corresponding END event. + static Query MatchBeginWithEnd() { + return (Query(EVENT_PHASE) == Query::Phase(TRACE_EVENT_PHASE_BEGIN)) && + Query(EVENT_HAS_OTHER); + } + + // Find ASYNC_BEGIN events that have a corresponding ASYNC_END event. + static Query MatchAsyncBeginWithNext() { + return (Query(EVENT_PHASE) == + Query::Phase(TRACE_EVENT_PHASE_ASYNC_BEGIN)) && + Query(EVENT_HAS_OTHER); + } + + // Find BEGIN events of given |name| which also have associated END events. + static Query MatchBeginName(const std::string& name) { + return (Query(EVENT_NAME) == name) && MatchBeginWithEnd(); + } + + // Match given Process ID and Thread ID. + static Query MatchThread(const TraceEvent::ProcessThreadID& thread) { + return (Query(EVENT_PID) == Query::Int(thread.process_id)) && + (Query(EVENT_TID) == Query::Int(thread.thread_id)); + } + + // Match event pair that spans multiple threads. + static Query MatchCrossThread() { + return (Query(EVENT_PID) != Query(OTHER_PID)) || + (Query(EVENT_TID) != Query(OTHER_TID)); + } + + //////////////////////////////////////////////////////////////// + // Operators: + + // Boolean operators: + Query operator==(const Query& rhs) const; + Query operator!=(const Query& rhs) const; + Query operator< (const Query& rhs) const; + Query operator<=(const Query& rhs) const; + Query operator> (const Query& rhs) const; + Query operator>=(const Query& rhs) const; + Query operator&&(const Query& rhs) const; + Query operator||(const Query& rhs) const; + Query operator!() const; + + // Arithmetic operators: + // Following operators are applied to double arguments: + Query operator+(const Query& rhs) const; + Query operator-(const Query& rhs) const; + Query operator*(const Query& rhs) const; + Query operator/(const Query& rhs) const; + Query operator-() const; + // Mod operates on int64 args (doubles are casted to int64 beforehand): + Query operator%(const Query& rhs) const; + + // Return true if the given event matches this query tree. + // This is a recursive method that walks the query tree. + bool Evaluate(const TraceEvent& event) const; + + private: + enum TraceEventMember { + EVENT_INVALID, + EVENT_PID, + EVENT_TID, + EVENT_TIME, + EVENT_DURATION, + EVENT_PHASE, + EVENT_CATEGORY, + EVENT_NAME, + EVENT_ID, + EVENT_HAS_STRING_ARG, + EVENT_HAS_NUMBER_ARG, + EVENT_ARG, + EVENT_HAS_OTHER, + OTHER_PID, + OTHER_TID, + OTHER_TIME, + OTHER_PHASE, + OTHER_CATEGORY, + OTHER_NAME, + OTHER_ID, + OTHER_HAS_STRING_ARG, + OTHER_HAS_NUMBER_ARG, + OTHER_ARG, + }; + + enum Operator { + OP_INVALID, + // Boolean operators: + OP_EQ, + OP_NE, + OP_LT, + OP_LE, + OP_GT, + OP_GE, + OP_AND, + OP_OR, + OP_NOT, + // Arithmetic operators: + OP_ADD, + OP_SUB, + OP_MUL, + OP_DIV, + OP_MOD, + OP_NEGATE + }; + + enum QueryType { + QUERY_BOOLEAN_OPERATOR, + QUERY_ARITHMETIC_OPERATOR, + QUERY_EVENT_MEMBER, + QUERY_NUMBER, + QUERY_STRING + }; + + // Compare with the given member. + Query(TraceEventMember member); + + // Compare with the given member argument value. + Query(TraceEventMember member, const std::string& arg_name); + + // Compare with the given string. + Query(const std::string& str); + + // Compare with the given number. + Query(double num); + + // Construct a boolean Query that returns (left <binary_op> right). + Query(const Query& left, const Query& right, Operator binary_op); + + // Construct a boolean Query that returns (<binary_op> left). + Query(const Query& left, Operator unary_op); + + // Try to compare left_ against right_ based on operator_. + // If either left or right does not convert to double, false is returned. + // Otherwise, true is returned and |result| is set to the comparison result. + bool CompareAsDouble(const TraceEvent& event, bool* result) const; + + // Try to compare left_ against right_ based on operator_. + // If either left or right does not convert to string, false is returned. + // Otherwise, true is returned and |result| is set to the comparison result. + bool CompareAsString(const TraceEvent& event, bool* result) const; + + // Attempt to convert this Query to a double. On success, true is returned + // and the double value is stored in |num|. + bool GetAsDouble(const TraceEvent& event, double* num) const; + + // Attempt to convert this Query to a string. On success, true is returned + // and the string value is stored in |str|. + bool GetAsString(const TraceEvent& event, std::string* str) const; + + // Evaluate this Query as an arithmetic operator on left_ and right_. + bool EvaluateArithmeticOperator(const TraceEvent& event, + double* num) const; + + // For QUERY_EVENT_MEMBER Query: attempt to get the double value of the Query. + bool GetMemberValueAsDouble(const TraceEvent& event, double* num) const; + + // For QUERY_EVENT_MEMBER Query: attempt to get the string value of the Query. + bool GetMemberValueAsString(const TraceEvent& event, std::string* num) const; + + // Does this Query represent a value? + bool is_value() const { return type_ != QUERY_BOOLEAN_OPERATOR; } + + bool is_unary_operator() const { + return operator_ == OP_NOT || operator_ == OP_NEGATE; + } + + bool is_comparison_operator() const { + return operator_ != OP_INVALID && operator_ < OP_AND; + } + + const Query& left() const; + const Query& right() const; + + QueryType type_; + Operator operator_; + scoped_refptr<QueryNode> left_; + scoped_refptr<QueryNode> right_; + TraceEventMember member_; + double number_; + std::string string_; + bool is_pattern_; +}; + +// Implementation detail: +// QueryNode allows Query to store a ref-counted query tree. +class QueryNode : public base::RefCounted<QueryNode> { + public: + explicit QueryNode(const Query& query); + const Query& query() const { return query_; } + + private: + friend class base::RefCounted<QueryNode>; + ~QueryNode(); + + Query query_; +}; + +// TraceAnalyzer helps tests search for trace events. +class TraceAnalyzer { + public: + ~TraceAnalyzer(); + + // Use trace events from JSON string generated by tracing API. + // Returns non-NULL if the JSON is successfully parsed. + static TraceAnalyzer* Create(const std::string& json_events) + WARN_UNUSED_RESULT; + + // Associate BEGIN and END events with each other. This allows Query(OTHER_*) + // to access the associated event and enables Query(EVENT_DURATION). + // An end event will match the most recent begin event with the same name, + // category, process ID and thread ID. This matches what is shown in + // about:tracing. After association, the BEGIN event will point to the + // matching END event, but the END event will not point to the BEGIN event. + void AssociateBeginEndEvents(); + + // Associate ASYNC_BEGIN, ASYNC_STEP and ASYNC_END events with each other. + // An ASYNC_END event will match the most recent ASYNC_BEGIN or ASYNC_STEP + // event with the same name, category, and ID. This creates a singly linked + // list of ASYNC_BEGIN->ASYNC_STEP...->ASYNC_END. + void AssociateAsyncBeginEndEvents(); + + // AssociateEvents can be used to customize event associations by setting the + // other_event member of TraceEvent. This should be used to associate two + // INSTANT events. + // + // The assumptions are: + // - |first| events occur before |second| events. + // - the closest matching |second| event is the correct match. + // + // |first| - Eligible |first| events match this query. + // |second| - Eligible |second| events match this query. + // |match| - This query is run on the |first| event. The OTHER_* EventMember + // queries will point to an eligible |second| event. The query + // should evaluate to true if the |first|/|second| pair is a match. + // + // When a match is found, the pair will be associated by having the first + // event's other_event member point to the other. AssociateEvents does not + // clear previous associations, so it is possible to associate multiple pairs + // of events by calling AssociateEvents more than once with different queries. + // + // NOTE: AssociateEvents will overwrite existing other_event associations if + // the queries pass for events that already had a previous association. + // + // After calling any Find* method, it is not allowed to call AssociateEvents + // again. + void AssociateEvents(const Query& first, + const Query& second, + const Query& match); + + // For each event, copy its arguments to the other_event argument map. If + // argument name already exists, it will not be overwritten. + void MergeAssociatedEventArgs(); + + // Find all events that match query and replace output vector. + size_t FindEvents(const Query& query, TraceEventVector* output); + + // Find first event that matches query or NULL if not found. + const TraceEvent* FindFirstOf(const Query& query); + + // Find last event that matches query or NULL if not found. + const TraceEvent* FindLastOf(const Query& query); + + const std::string& GetThreadName(const TraceEvent::ProcessThreadID& thread); + + private: + TraceAnalyzer(); + + bool SetEvents(const std::string& json_events) WARN_UNUSED_RESULT; + + // Read metadata (thread names, etc) from events. + void ParseMetadata(); + + std::map<TraceEvent::ProcessThreadID, std::string> thread_names_; + std::vector<TraceEvent> raw_events_; + bool allow_assocation_changes_; + + DISALLOW_COPY_AND_ASSIGN(TraceAnalyzer); +}; + +// Utility functions for TraceEventVector. + +struct RateStats { + double min_us; + double max_us; + double mean_us; + double standard_deviation_us; +}; + +struct RateStatsOptions { + RateStatsOptions() : trim_min(0u), trim_max(0u) {} + // After the times between events are sorted, the number of specified elements + // will be trimmed before calculating the RateStats. This is useful in cases + // where extreme outliers are tolerable and should not skew the overall + // average. + size_t trim_min; // Trim this many minimum times. + size_t trim_max; // Trim this many maximum times. +}; + +// Calculate min/max/mean and standard deviation from the times between +// adjacent events. +bool GetRateStats(const TraceEventVector& events, + RateStats* stats, + const RateStatsOptions* options); + +// Starting from |position|, find the first event that matches |query|. +// Returns true if found, false otherwise. +bool FindFirstOf(const TraceEventVector& events, + const Query& query, + size_t position, + size_t* return_index); + +// Starting from |position|, find the last event that matches |query|. +// Returns true if found, false otherwise. +bool FindLastOf(const TraceEventVector& events, + const Query& query, + size_t position, + size_t* return_index); + +// Find the closest events to |position| in time that match |query|. +// return_second_closest may be NULL. Closeness is determined by comparing +// with the event timestamp. +// Returns true if found, false otherwise. If both return parameters are +// requested, both must be found for a successful result. +bool FindClosest(const TraceEventVector& events, + const Query& query, + size_t position, + size_t* return_closest, + size_t* return_second_closest); + +// Count matches, inclusive of |begin_position|, exclusive of |end_position|. +size_t CountMatches(const TraceEventVector& events, + const Query& query, + size_t begin_position, + size_t end_position); + +// Count all matches. +static inline size_t CountMatches(const TraceEventVector& events, + const Query& query) { + return CountMatches(events, query, 0u, events.size()); +} + +} // namespace trace_analyzer + +#endif // BASE_TEST_TRACE_EVENT_ANALYZER_H_
diff --git a/src/base/test/trace_event_analyzer_unittest.cc b/src/base/test/trace_event_analyzer_unittest.cc new file mode 100644 index 0000000..eca2b70 --- /dev/null +++ b/src/base/test/trace_event_analyzer_unittest.cc
@@ -0,0 +1,825 @@ +// 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/bind.h" +#include "base/debug/trace_event_unittest.h" +#include "base/test/trace_event_analyzer.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace trace_analyzer { + +namespace { + +class TraceEventAnalyzerTest : public testing::Test { + public: + void ManualSetUp(); + void OnTraceDataCollected( + const scoped_refptr<base::RefCountedString>& json_events_str); + void BeginTracing(); + void EndTracing(); + + base::debug::TraceResultBuffer::SimpleOutput output_; + base::debug::TraceResultBuffer buffer_; +}; + +void TraceEventAnalyzerTest::ManualSetUp() { + base::debug::TraceLog::Resurrect(); + ASSERT_TRUE(base::debug::TraceLog::GetInstance()); + buffer_.SetOutputCallback(output_.GetCallback()); + output_.json_output.clear(); +} + +void TraceEventAnalyzerTest::OnTraceDataCollected( + const scoped_refptr<base::RefCountedString>& json_events_str) { + buffer_.AddFragment(json_events_str->data()); +} + +void TraceEventAnalyzerTest::BeginTracing() { + output_.json_output.clear(); + buffer_.Start(); + base::debug::TraceLog::GetInstance()->SetEnabled(true); +} + +void TraceEventAnalyzerTest::EndTracing() { + base::debug::TraceLog::GetInstance()->SetEnabled(false); + base::debug::TraceLog::GetInstance()->Flush( + base::Bind(&TraceEventAnalyzerTest::OnTraceDataCollected, + base::Unretained(this))); + buffer_.Finish(); +} + +} // namespace + +TEST_F(TraceEventAnalyzerTest, NoEvents) { + ManualSetUp(); + + // Create an empty JSON event string: + buffer_.Start(); + buffer_.Finish(); + + scoped_ptr<TraceAnalyzer> + analyzer(TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + + // Search for all events and verify that nothing is returned. + TraceEventVector found; + analyzer->FindEvents(Query::Bool(true), &found); + EXPECT_EQ(0u, found.size()); +} + +TEST_F(TraceEventAnalyzerTest, TraceEvent) { + ManualSetUp(); + + int int_num = 2; + double double_num = 3.5; + const char* str = "the string"; + + TraceEvent event; + event.arg_numbers["false"] = 0.0; + event.arg_numbers["true"] = 1.0; + event.arg_numbers["int"] = static_cast<double>(int_num); + event.arg_numbers["double"] = double_num; + event.arg_strings["string"] = str; + + ASSERT_TRUE(event.HasNumberArg("false")); + ASSERT_TRUE(event.HasNumberArg("true")); + ASSERT_TRUE(event.HasNumberArg("int")); + ASSERT_TRUE(event.HasNumberArg("double")); + ASSERT_TRUE(event.HasStringArg("string")); + ASSERT_FALSE(event.HasNumberArg("notfound")); + ASSERT_FALSE(event.HasStringArg("notfound")); + + EXPECT_FALSE(event.GetKnownArgAsBool("false")); + EXPECT_TRUE(event.GetKnownArgAsBool("true")); + EXPECT_EQ(int_num, event.GetKnownArgAsInt("int")); + EXPECT_EQ(double_num, event.GetKnownArgAsDouble("double")); + EXPECT_STREQ(str, event.GetKnownArgAsString("string").c_str()); +} + +TEST_F(TraceEventAnalyzerTest, QueryEventMember) { + ManualSetUp(); + + TraceEvent event; + event.thread.process_id = 3; + event.thread.thread_id = 4; + event.timestamp = 1.5; + event.phase = TRACE_EVENT_PHASE_BEGIN; + event.category = "category"; + event.name = "name"; + event.id = "1"; + event.arg_numbers["num"] = 7.0; + event.arg_strings["str"] = "the string"; + + // Other event with all different members: + TraceEvent other; + other.thread.process_id = 5; + other.thread.thread_id = 6; + other.timestamp = 2.5; + other.phase = TRACE_EVENT_PHASE_END; + other.category = "category2"; + other.name = "name2"; + other.id = "2"; + other.arg_numbers["num2"] = 8.0; + other.arg_strings["str2"] = "the string 2"; + + event.other_event = &other; + ASSERT_TRUE(event.has_other_event()); + double duration = event.GetAbsTimeToOtherEvent(); + + Query event_pid = Query::EventPidIs(event.thread.process_id); + Query event_tid = Query::EventTidIs(event.thread.thread_id); + Query event_time = Query::EventTimeIs(event.timestamp); + Query event_duration = Query::EventDurationIs(duration); + Query event_phase = Query::EventPhaseIs(event.phase); + Query event_category = Query::EventCategoryIs(event.category); + Query event_name = Query::EventNameIs(event.name); + Query event_id = Query::EventIdIs(event.id); + Query event_has_arg1 = Query::EventHasNumberArg("num"); + Query event_has_arg2 = Query::EventHasStringArg("str"); + Query event_arg1 = + (Query::EventArg("num") == Query::Double(event.arg_numbers["num"])); + Query event_arg2 = + (Query::EventArg("str") == Query::String(event.arg_strings["str"])); + Query event_has_other = Query::EventHasOther(); + Query other_pid = Query::OtherPidIs(other.thread.process_id); + Query other_tid = Query::OtherTidIs(other.thread.thread_id); + Query other_time = Query::OtherTimeIs(other.timestamp); + Query other_phase = Query::OtherPhaseIs(other.phase); + Query other_category = Query::OtherCategoryIs(other.category); + Query other_name = Query::OtherNameIs(other.name); + Query other_id = Query::OtherIdIs(other.id); + Query other_has_arg1 = Query::OtherHasNumberArg("num2"); + Query other_has_arg2 = Query::OtherHasStringArg("str2"); + Query other_arg1 = + (Query::OtherArg("num2") == Query::Double(other.arg_numbers["num2"])); + Query other_arg2 = + (Query::OtherArg("str2") == Query::String(other.arg_strings["str2"])); + + EXPECT_TRUE(event_pid.Evaluate(event)); + EXPECT_TRUE(event_tid.Evaluate(event)); + EXPECT_TRUE(event_time.Evaluate(event)); + EXPECT_TRUE(event_duration.Evaluate(event)); + EXPECT_TRUE(event_phase.Evaluate(event)); + EXPECT_TRUE(event_category.Evaluate(event)); + EXPECT_TRUE(event_name.Evaluate(event)); + EXPECT_TRUE(event_id.Evaluate(event)); + EXPECT_TRUE(event_has_arg1.Evaluate(event)); + EXPECT_TRUE(event_has_arg2.Evaluate(event)); + EXPECT_TRUE(event_arg1.Evaluate(event)); + EXPECT_TRUE(event_arg2.Evaluate(event)); + EXPECT_TRUE(event_has_other.Evaluate(event)); + EXPECT_TRUE(other_pid.Evaluate(event)); + EXPECT_TRUE(other_tid.Evaluate(event)); + EXPECT_TRUE(other_time.Evaluate(event)); + EXPECT_TRUE(other_phase.Evaluate(event)); + EXPECT_TRUE(other_category.Evaluate(event)); + EXPECT_TRUE(other_name.Evaluate(event)); + EXPECT_TRUE(other_id.Evaluate(event)); + EXPECT_TRUE(other_has_arg1.Evaluate(event)); + EXPECT_TRUE(other_has_arg2.Evaluate(event)); + EXPECT_TRUE(other_arg1.Evaluate(event)); + EXPECT_TRUE(other_arg2.Evaluate(event)); + + // Evaluate event queries against other to verify the queries fail when the + // event members are wrong. + EXPECT_FALSE(event_pid.Evaluate(other)); + EXPECT_FALSE(event_tid.Evaluate(other)); + EXPECT_FALSE(event_time.Evaluate(other)); + EXPECT_FALSE(event_duration.Evaluate(other)); + EXPECT_FALSE(event_phase.Evaluate(other)); + EXPECT_FALSE(event_category.Evaluate(other)); + EXPECT_FALSE(event_name.Evaluate(other)); + EXPECT_FALSE(event_id.Evaluate(other)); + EXPECT_FALSE(event_has_arg1.Evaluate(other)); + EXPECT_FALSE(event_has_arg2.Evaluate(other)); + EXPECT_FALSE(event_arg1.Evaluate(other)); + EXPECT_FALSE(event_arg2.Evaluate(other)); + EXPECT_FALSE(event_has_other.Evaluate(other)); +} + +TEST_F(TraceEventAnalyzerTest, BooleanOperators) { + ManualSetUp(); + + BeginTracing(); + { + TRACE_EVENT_INSTANT1("cat1", "name1", "num", 1); + TRACE_EVENT_INSTANT1("cat1", "name2", "num", 2); + TRACE_EVENT_INSTANT1("cat2", "name3", "num", 3); + TRACE_EVENT_INSTANT1("cat2", "name4", "num", 4); + } + EndTracing(); + + scoped_ptr<TraceAnalyzer> + analyzer(TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(!!analyzer.get()); + + TraceEventVector found; + + // == + + analyzer->FindEvents(Query::EventCategory() == Query::String("cat1"), &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + EXPECT_STREQ("name2", found[1]->name.c_str()); + + analyzer->FindEvents(Query::EventArg("num") == Query::Int(2), &found); + ASSERT_EQ(1u, found.size()); + EXPECT_STREQ("name2", found[0]->name.c_str()); + + // != + + analyzer->FindEvents(Query::EventCategory() != Query::String("cat1"), &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STREQ("name3", found[0]->name.c_str()); + EXPECT_STREQ("name4", found[1]->name.c_str()); + + analyzer->FindEvents(Query::EventArg("num") != Query::Int(2), &found); + ASSERT_EQ(3u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + EXPECT_STREQ("name3", found[1]->name.c_str()); + EXPECT_STREQ("name4", found[2]->name.c_str()); + + // < + analyzer->FindEvents(Query::EventArg("num") < Query::Int(2), &found); + ASSERT_EQ(1u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + + // <= + analyzer->FindEvents(Query::EventArg("num") <= Query::Int(2), &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + EXPECT_STREQ("name2", found[1]->name.c_str()); + + // > + analyzer->FindEvents(Query::EventArg("num") > Query::Int(3), &found); + ASSERT_EQ(1u, found.size()); + EXPECT_STREQ("name4", found[0]->name.c_str()); + + // >= + analyzer->FindEvents(Query::EventArg("num") >= Query::Int(4), &found); + ASSERT_EQ(1u, found.size()); + EXPECT_STREQ("name4", found[0]->name.c_str()); + + // && + analyzer->FindEvents(Query::EventName() != Query::String("name1") && + Query::EventArg("num") < Query::Int(3), &found); + ASSERT_EQ(1u, found.size()); + EXPECT_STREQ("name2", found[0]->name.c_str()); + + // || + analyzer->FindEvents(Query::EventName() == Query::String("name1") || + Query::EventArg("num") == Query::Int(3), &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + EXPECT_STREQ("name3", found[1]->name.c_str()); + + // ! + analyzer->FindEvents(!(Query::EventName() == Query::String("name1") || + Query::EventArg("num") == Query::Int(3)), &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STREQ("name2", found[0]->name.c_str()); + EXPECT_STREQ("name4", found[1]->name.c_str()); +} + +TEST_F(TraceEventAnalyzerTest, ArithmeticOperators) { + ManualSetUp(); + + BeginTracing(); + { + // These events are searched for: + TRACE_EVENT_INSTANT2("cat1", "math1", "a", 10, "b", 5); + TRACE_EVENT_INSTANT2("cat1", "math2", "a", 10, "b", 10); + // Extra events that never match, for noise: + TRACE_EVENT_INSTANT2("noise", "math3", "a", 1, "b", 3); + TRACE_EVENT_INSTANT2("noise", "math4", "c", 10, "d", 5); + } + EndTracing(); + + scoped_ptr<TraceAnalyzer> + analyzer(TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + + TraceEventVector found; + + // Verify that arithmetic operators function: + + // + + analyzer->FindEvents(Query::EventArg("a") + Query::EventArg("b") == + Query::Int(20), &found); + EXPECT_EQ(1u, found.size()); + EXPECT_STREQ("math2", found.front()->name.c_str()); + + // - + analyzer->FindEvents(Query::EventArg("a") - Query::EventArg("b") == + Query::Int(5), &found); + EXPECT_EQ(1u, found.size()); + EXPECT_STREQ("math1", found.front()->name.c_str()); + + // * + analyzer->FindEvents(Query::EventArg("a") * Query::EventArg("b") == + Query::Int(50), &found); + EXPECT_EQ(1u, found.size()); + EXPECT_STREQ("math1", found.front()->name.c_str()); + + // / + analyzer->FindEvents(Query::EventArg("a") / Query::EventArg("b") == + Query::Int(2), &found); + EXPECT_EQ(1u, found.size()); + EXPECT_STREQ("math1", found.front()->name.c_str()); + + // % + analyzer->FindEvents(Query::EventArg("a") % Query::EventArg("b") == + Query::Int(0), &found); + EXPECT_EQ(2u, found.size()); + + // - (negate) + analyzer->FindEvents(-Query::EventArg("b") == Query::Int(-10), &found); + EXPECT_EQ(1u, found.size()); + EXPECT_STREQ("math2", found.front()->name.c_str()); +} + +TEST_F(TraceEventAnalyzerTest, StringPattern) { + ManualSetUp(); + + BeginTracing(); + { + TRACE_EVENT_INSTANT0("cat1", "name1"); + TRACE_EVENT_INSTANT0("cat1", "name2"); + TRACE_EVENT_INSTANT0("cat1", "no match"); + TRACE_EVENT_INSTANT0("cat1", "name3x"); + } + EndTracing(); + + scoped_ptr<TraceAnalyzer> + analyzer(TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + + TraceEventVector found; + + analyzer->FindEvents(Query::EventName() == Query::Pattern("name?"), &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + EXPECT_STREQ("name2", found[1]->name.c_str()); + + analyzer->FindEvents(Query::EventName() == Query::Pattern("name*"), &found); + ASSERT_EQ(3u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + EXPECT_STREQ("name2", found[1]->name.c_str()); + EXPECT_STREQ("name3x", found[2]->name.c_str()); + + analyzer->FindEvents(Query::EventName() != Query::Pattern("name*"), &found); + ASSERT_EQ(1u, found.size()); + EXPECT_STREQ("no match", found[0]->name.c_str()); +} + +// Test that duration queries work. +TEST_F(TraceEventAnalyzerTest, Duration) { + ManualSetUp(); + + const base::TimeDelta kSleepTime = base::TimeDelta::FromMilliseconds(200); + // We will search for events that have a duration of greater than 90% of the + // sleep time, so that there is no flakiness. + int duration_cutoff_us = (kSleepTime.InMicroseconds() * 9) / 10; + + BeginTracing(); + { + TRACE_EVENT0("cat1", "name1"); // found by duration query + TRACE_EVENT0("noise", "name2"); // not searched for, just noise + { + TRACE_EVENT0("cat2", "name3"); // found by duration query + TRACE_EVENT_INSTANT0("noise", "name4"); // not searched for, just noise + base::debug::HighResSleepForTraceTest(kSleepTime); + TRACE_EVENT0("cat2", "name5"); // not found (duration too short) + } + } + EndTracing(); + + scoped_ptr<TraceAnalyzer> + analyzer(TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + analyzer->AssociateBeginEndEvents(); + + TraceEventVector found; + analyzer->FindEvents( + Query::MatchBeginWithEnd() && + Query::EventDuration() > Query::Int(duration_cutoff_us) && + (Query::EventCategory() == Query::String("cat1") || + Query::EventCategory() == Query::String("cat2") || + Query::EventCategory() == Query::String("cat3")), + &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + EXPECT_STREQ("name3", found[1]->name.c_str()); +} + +// Test AssociateBeginEndEvents +TEST_F(TraceEventAnalyzerTest, BeginEndAssocations) { + ManualSetUp(); + + BeginTracing(); + { + TRACE_EVENT_END0("cat1", "name1"); // does not match out of order begin + TRACE_EVENT0("cat1", "name2"); + TRACE_EVENT_INSTANT0("cat1", "name3"); + TRACE_EVENT_BEGIN0("cat1", "name1"); + } + EndTracing(); + + scoped_ptr<TraceAnalyzer> + analyzer(TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + analyzer->AssociateBeginEndEvents(); + + TraceEventVector found; + analyzer->FindEvents(Query::MatchBeginWithEnd(), &found); + ASSERT_EQ(1u, found.size()); + EXPECT_STREQ("name2", found[0]->name.c_str()); +} + +// Test MergeAssociatedEventArgs +TEST_F(TraceEventAnalyzerTest, MergeAssociatedEventArgs) { + ManualSetUp(); + + const char* arg_string = "arg_string"; + BeginTracing(); + { + TRACE_EVENT_BEGIN0("cat1", "name1"); + TRACE_EVENT_END1("cat1", "name1", "arg", arg_string); + } + EndTracing(); + + scoped_ptr<TraceAnalyzer> + analyzer(TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + analyzer->AssociateBeginEndEvents(); + + TraceEventVector found; + analyzer->FindEvents(Query::MatchBeginName("name1"), &found); + ASSERT_EQ(1u, found.size()); + std::string arg_actual; + EXPECT_FALSE(found[0]->GetArgAsString("arg", &arg_actual)); + + analyzer->MergeAssociatedEventArgs(); + EXPECT_TRUE(found[0]->GetArgAsString("arg", &arg_actual)); + EXPECT_STREQ(arg_string, arg_actual.c_str()); +} + +// Test AssociateAsyncBeginEndEvents +TEST_F(TraceEventAnalyzerTest, AsyncBeginEndAssocations) { + ManualSetUp(); + + BeginTracing(); + { + TRACE_EVENT_ASYNC_END0("cat1", "name1", 0xA); // no match / out of order + TRACE_EVENT_ASYNC_BEGIN0("cat1", "name1", 0xB); + TRACE_EVENT_ASYNC_BEGIN0("cat1", "name1", 0xC); + TRACE_EVENT_INSTANT0("cat1", "name1"); // noise + TRACE_EVENT0("cat1", "name1"); // noise + TRACE_EVENT_ASYNC_END0("cat1", "name1", 0xB); + TRACE_EVENT_ASYNC_END0("cat1", "name1", 0xC); + TRACE_EVENT_ASYNC_BEGIN0("cat1", "name1", 0xA); // no match / out of order + } + EndTracing(); + + scoped_ptr<TraceAnalyzer> + analyzer(TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + analyzer->AssociateAsyncBeginEndEvents(); + + TraceEventVector found; + analyzer->FindEvents(Query::MatchAsyncBeginWithNext(), &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STRCASEEQ("B", found[0]->id.c_str()); + EXPECT_STRCASEEQ("C", found[1]->id.c_str()); +} + +// Test AssociateAsyncBeginEndEvents +TEST_F(TraceEventAnalyzerTest, AsyncBeginEndAssocationsWithSteps) { + ManualSetUp(); + + BeginTracing(); + { + TRACE_EVENT_ASYNC_STEP0("c", "n", 0xA, "s1"); + TRACE_EVENT_ASYNC_END0("c", "n", 0xA); + TRACE_EVENT_ASYNC_BEGIN0("c", "n", 0xB); + TRACE_EVENT_ASYNC_BEGIN0("c", "n", 0xC); + TRACE_EVENT_ASYNC_STEP0("c", "n", 0xB, "s1"); + TRACE_EVENT_ASYNC_STEP0("c", "n", 0xC, "s1"); + TRACE_EVENT_ASYNC_STEP1("c", "n", 0xC, "s2", "a", 1); + TRACE_EVENT_ASYNC_END0("c", "n", 0xB); + TRACE_EVENT_ASYNC_END0("c", "n", 0xC); + TRACE_EVENT_ASYNC_BEGIN0("c", "n", 0xA); + TRACE_EVENT_ASYNC_STEP0("c", "n", 0xA, "s2"); + } + EndTracing(); + + scoped_ptr<TraceAnalyzer> + analyzer(TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + analyzer->AssociateAsyncBeginEndEvents(); + + TraceEventVector found; + analyzer->FindEvents(Query::MatchAsyncBeginWithNext(), &found); + ASSERT_EQ(3u, found.size()); + + EXPECT_STRCASEEQ("B", found[0]->id.c_str()); + EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP, found[0]->other_event->phase); + EXPECT_TRUE(found[0]->other_event->other_event); + EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_END, + found[0]->other_event->other_event->phase); + + EXPECT_STRCASEEQ("C", found[1]->id.c_str()); + EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP, found[1]->other_event->phase); + EXPECT_TRUE(found[1]->other_event->other_event); + EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP, + found[1]->other_event->other_event->phase); + double arg_actual = 0; + EXPECT_TRUE(found[1]->other_event->other_event->GetArgAsNumber( + "a", &arg_actual)); + EXPECT_EQ(1.0, arg_actual); + EXPECT_TRUE(found[1]->other_event->other_event->other_event); + EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_END, + found[1]->other_event->other_event->other_event->phase); + + EXPECT_STRCASEEQ("A", found[2]->id.c_str()); + EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP, found[2]->other_event->phase); +} + +// Test that the TraceAnalyzer custom associations work. +TEST_F(TraceEventAnalyzerTest, CustomAssociations) { + ManualSetUp(); + + // Add events that begin/end in pipelined ordering with unique ID parameter + // to match up the begin/end pairs. + BeginTracing(); + { + TRACE_EVENT_INSTANT1("cat1", "end", "id", 1); // no begin match + TRACE_EVENT_INSTANT1("cat2", "begin", "id", 2); // end is cat4 + TRACE_EVENT_INSTANT1("cat3", "begin", "id", 3); // end is cat5 + TRACE_EVENT_INSTANT1("cat4", "end", "id", 2); + TRACE_EVENT_INSTANT1("cat5", "end", "id", 3); + TRACE_EVENT_INSTANT1("cat6", "begin", "id", 1); // no end match + } + EndTracing(); + + scoped_ptr<TraceAnalyzer> + analyzer(TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + + // begin, end, and match queries to find proper begin/end pairs. + Query begin(Query::EventName() == Query::String("begin")); + Query end(Query::EventName() == Query::String("end")); + Query match(Query::EventArg("id") == Query::OtherArg("id")); + analyzer->AssociateEvents(begin, end, match); + + TraceEventVector found; + + // cat1 has no other_event. + analyzer->FindEvents(Query::EventCategory() == Query::String("cat1") && + Query::EventHasOther(), &found); + EXPECT_EQ(0u, found.size()); + + // cat1 has no other_event. + analyzer->FindEvents(Query::EventCategory() == Query::String("cat1") && + !Query::EventHasOther(), &found); + EXPECT_EQ(1u, found.size()); + + // cat6 has no other_event. + analyzer->FindEvents(Query::EventCategory() == Query::String("cat6") && + !Query::EventHasOther(), &found); + EXPECT_EQ(1u, found.size()); + + // cat2 and cat4 are associated. + analyzer->FindEvents(Query::EventCategory() == Query::String("cat2") && + Query::OtherCategory() == Query::String("cat4"), &found); + EXPECT_EQ(1u, found.size()); + + // cat4 and cat2 are not associated. + analyzer->FindEvents(Query::EventCategory() == Query::String("cat4") && + Query::OtherCategory() == Query::String("cat2"), &found); + EXPECT_EQ(0u, found.size()); + + // cat3 and cat5 are associated. + analyzer->FindEvents(Query::EventCategory() == Query::String("cat3") && + Query::OtherCategory() == Query::String("cat5"), &found); + EXPECT_EQ(1u, found.size()); + + // cat5 and cat3 are not associated. + analyzer->FindEvents(Query::EventCategory() == Query::String("cat5") && + Query::OtherCategory() == Query::String("cat3"), &found); + EXPECT_EQ(0u, found.size()); +} + +// Verify that Query literals and types are properly casted. +TEST_F(TraceEventAnalyzerTest, Literals) { + ManualSetUp(); + + // Since these queries don't refer to the event data, the dummy event below + // will never be accessed. + TraceEvent dummy; + char char_num = 5; + short short_num = -5; + EXPECT_TRUE((Query::Double(5.0) == Query::Int(char_num)).Evaluate(dummy)); + EXPECT_TRUE((Query::Double(-5.0) == Query::Int(short_num)).Evaluate(dummy)); + EXPECT_TRUE((Query::Double(1.0) == Query::Uint(1u)).Evaluate(dummy)); + EXPECT_TRUE((Query::Double(1.0) == Query::Int(1)).Evaluate(dummy)); + EXPECT_TRUE((Query::Double(-1.0) == Query::Int(-1)).Evaluate(dummy)); + EXPECT_TRUE((Query::Double(1.0) == Query::Double(1.0f)).Evaluate(dummy)); + EXPECT_TRUE((Query::Bool(true) == Query::Int(1)).Evaluate(dummy)); + EXPECT_TRUE((Query::Bool(false) == Query::Int(0)).Evaluate(dummy)); + EXPECT_TRUE((Query::Bool(true) == Query::Double(1.0f)).Evaluate(dummy)); + EXPECT_TRUE((Query::Bool(false) == Query::Double(0.0f)).Evaluate(dummy)); +} + +// Test GetRateStats. +TEST_F(TraceEventAnalyzerTest, RateStats) { + std::vector<TraceEvent> events; + events.reserve(100); + TraceEventVector event_ptrs; + TraceEvent event; + event.timestamp = 0.0; + double little_delta = 1.0; + double big_delta = 10.0; + double tiny_delta = 0.1; + RateStats stats; + RateStatsOptions options; + + // Insert 10 events, each apart by little_delta. + for (int i = 0; i < 10; ++i) { + event.timestamp += little_delta; + events.push_back(event); + event_ptrs.push_back(&events.back()); + } + + ASSERT_TRUE(GetRateStats(event_ptrs, &stats, NULL)); + EXPECT_EQ(little_delta, stats.mean_us); + EXPECT_EQ(little_delta, stats.min_us); + EXPECT_EQ(little_delta, stats.max_us); + EXPECT_EQ(0.0, stats.standard_deviation_us); + + // Add an event apart by big_delta. + event.timestamp += big_delta; + events.push_back(event); + event_ptrs.push_back(&events.back()); + + ASSERT_TRUE(GetRateStats(event_ptrs, &stats, NULL)); + EXPECT_LT(little_delta, stats.mean_us); + EXPECT_EQ(little_delta, stats.min_us); + EXPECT_EQ(big_delta, stats.max_us); + EXPECT_LT(0.0, stats.standard_deviation_us); + + // Trim off the biggest delta and verify stats. + options.trim_min = 0; + options.trim_max = 1; + ASSERT_TRUE(GetRateStats(event_ptrs, &stats, &options)); + EXPECT_EQ(little_delta, stats.mean_us); + EXPECT_EQ(little_delta, stats.min_us); + EXPECT_EQ(little_delta, stats.max_us); + EXPECT_EQ(0.0, stats.standard_deviation_us); + + // Add an event apart by tiny_delta. + event.timestamp += tiny_delta; + events.push_back(event); + event_ptrs.push_back(&events.back()); + + // Trim off both the biggest and tiniest delta and verify stats. + options.trim_min = 1; + options.trim_max = 1; + ASSERT_TRUE(GetRateStats(event_ptrs, &stats, &options)); + EXPECT_EQ(little_delta, stats.mean_us); + EXPECT_EQ(little_delta, stats.min_us); + EXPECT_EQ(little_delta, stats.max_us); + EXPECT_EQ(0.0, stats.standard_deviation_us); + + // Verify smallest allowed number of events. + TraceEventVector few_event_ptrs; + few_event_ptrs.push_back(&event); + few_event_ptrs.push_back(&event); + ASSERT_FALSE(GetRateStats(few_event_ptrs, &stats, NULL)); + few_event_ptrs.push_back(&event); + ASSERT_TRUE(GetRateStats(few_event_ptrs, &stats, NULL)); + + // Trim off more than allowed and verify failure. + options.trim_min = 0; + options.trim_max = 1; + ASSERT_FALSE(GetRateStats(few_event_ptrs, &stats, &options)); +} + +// Test FindFirstOf and FindLastOf. +TEST_F(TraceEventAnalyzerTest, FindOf) { + size_t num_events = 100; + size_t index = 0; + TraceEventVector event_ptrs; + EXPECT_FALSE(FindFirstOf(event_ptrs, Query::Bool(true), 0, &index)); + EXPECT_FALSE(FindFirstOf(event_ptrs, Query::Bool(true), 10, &index)); + EXPECT_FALSE(FindLastOf(event_ptrs, Query::Bool(true), 0, &index)); + EXPECT_FALSE(FindLastOf(event_ptrs, Query::Bool(true), 10, &index)); + + std::vector<TraceEvent> events; + events.resize(num_events); + for (size_t i = 0; i < events.size(); ++i) + event_ptrs.push_back(&events[i]); + size_t bam_index = num_events/2; + events[bam_index].name = "bam"; + Query query_bam = Query::EventName() == Query::String(events[bam_index].name); + + // FindFirstOf + EXPECT_FALSE(FindFirstOf(event_ptrs, Query::Bool(false), 0, &index)); + EXPECT_TRUE(FindFirstOf(event_ptrs, Query::Bool(true), 0, &index)); + EXPECT_EQ(0u, index); + EXPECT_TRUE(FindFirstOf(event_ptrs, Query::Bool(true), 5, &index)); + EXPECT_EQ(5u, index); + + EXPECT_FALSE(FindFirstOf(event_ptrs, query_bam, bam_index + 1, &index)); + EXPECT_TRUE(FindFirstOf(event_ptrs, query_bam, 0, &index)); + EXPECT_EQ(bam_index, index); + EXPECT_TRUE(FindFirstOf(event_ptrs, query_bam, bam_index, &index)); + EXPECT_EQ(bam_index, index); + + // FindLastOf + EXPECT_FALSE(FindLastOf(event_ptrs, Query::Bool(false), 1000, &index)); + EXPECT_TRUE(FindLastOf(event_ptrs, Query::Bool(true), 1000, &index)); + EXPECT_EQ(num_events - 1, index); + EXPECT_TRUE(FindLastOf(event_ptrs, Query::Bool(true), num_events - 5, + &index)); + EXPECT_EQ(num_events - 5, index); + + EXPECT_FALSE(FindLastOf(event_ptrs, query_bam, bam_index - 1, &index)); + EXPECT_TRUE(FindLastOf(event_ptrs, query_bam, num_events, &index)); + EXPECT_EQ(bam_index, index); + EXPECT_TRUE(FindLastOf(event_ptrs, query_bam, bam_index, &index)); + EXPECT_EQ(bam_index, index); +} + +// Test FindClosest. +TEST_F(TraceEventAnalyzerTest, FindClosest) { + size_t index_1 = 0; + size_t index_2 = 0; + TraceEventVector event_ptrs; + EXPECT_FALSE(FindClosest(event_ptrs, Query::Bool(true), 0, + &index_1, &index_2)); + + size_t num_events = 5; + std::vector<TraceEvent> events; + events.resize(num_events); + for (size_t i = 0; i < events.size(); ++i) { + // timestamps go up exponentially so the lower index is always closer in + // time than the higher index. + events[i].timestamp = static_cast<double>(i) * static_cast<double>(i); + event_ptrs.push_back(&events[i]); + } + events[0].name = "one"; + events[2].name = "two"; + events[4].name = "three"; + Query query_named = Query::EventName() != Query::String(""); + Query query_one = Query::EventName() == Query::String("one"); + + // Only one event matches query_one, so two closest can't be found. + EXPECT_FALSE(FindClosest(event_ptrs, query_one, 0, &index_1, &index_2)); + + EXPECT_TRUE(FindClosest(event_ptrs, query_one, 3, &index_1, NULL)); + EXPECT_EQ(0u, index_1); + + EXPECT_TRUE(FindClosest(event_ptrs, query_named, 1, &index_1, &index_2)); + EXPECT_EQ(0u, index_1); + EXPECT_EQ(2u, index_2); + + EXPECT_TRUE(FindClosest(event_ptrs, query_named, 4, &index_1, &index_2)); + EXPECT_EQ(4u, index_1); + EXPECT_EQ(2u, index_2); + + EXPECT_TRUE(FindClosest(event_ptrs, query_named, 3, &index_1, &index_2)); + EXPECT_EQ(2u, index_1); + EXPECT_EQ(0u, index_2); +} + +// Test CountMatches. +TEST_F(TraceEventAnalyzerTest, CountMatches) { + TraceEventVector event_ptrs; + EXPECT_EQ(0u, CountMatches(event_ptrs, Query::Bool(true), 0, 10)); + + size_t num_events = 5; + size_t num_named = 3; + std::vector<TraceEvent> events; + events.resize(num_events); + for (size_t i = 0; i < events.size(); ++i) + event_ptrs.push_back(&events[i]); + events[0].name = "one"; + events[2].name = "two"; + events[4].name = "three"; + Query query_named = Query::EventName() != Query::String(""); + Query query_one = Query::EventName() == Query::String("one"); + + EXPECT_EQ(0u, CountMatches(event_ptrs, Query::Bool(false))); + EXPECT_EQ(num_events, CountMatches(event_ptrs, Query::Bool(true))); + EXPECT_EQ(num_events - 1, CountMatches(event_ptrs, Query::Bool(true), + 1, num_events)); + EXPECT_EQ(1u, CountMatches(event_ptrs, query_one)); + EXPECT_EQ(num_events - 1, CountMatches(event_ptrs, !query_one)); + EXPECT_EQ(num_named, CountMatches(event_ptrs, query_named)); +} + + +} // namespace trace_analyzer
diff --git a/src/base/test/values_test_util.cc b/src/base/test/values_test_util.cc new file mode 100644 index 0000000..c36e7b2 --- /dev/null +++ b/src/base/test/values_test_util.cc
@@ -0,0 +1,78 @@ +// 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/test/values_test_util.h" + +#include "base/json/json_reader.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_number_conversions.h" +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +void ExpectDictBooleanValue(bool expected_value, + const DictionaryValue& value, + const std::string& key) { + bool boolean_value = false; + EXPECT_TRUE(value.GetBoolean(key, &boolean_value)) << key; + EXPECT_EQ(expected_value, boolean_value) << key; +} + +void ExpectDictDictionaryValue(const DictionaryValue& expected_value, + const DictionaryValue& value, + const std::string& key) { + const DictionaryValue* dict_value = NULL; + EXPECT_TRUE(value.GetDictionary(key, &dict_value)) << key; + EXPECT_TRUE(Value::Equals(dict_value, &expected_value)) << key; +} + +void ExpectDictIntegerValue(int expected_value, + const DictionaryValue& value, + const std::string& key) { + int integer_value = 0; + EXPECT_TRUE(value.GetInteger(key, &integer_value)) << key; + EXPECT_EQ(expected_value, integer_value) << key; +} + +void ExpectDictListValue(const ListValue& expected_value, + const DictionaryValue& value, + const std::string& key) { + const ListValue* list_value = NULL; + EXPECT_TRUE(value.GetList(key, &list_value)) << key; + EXPECT_TRUE(Value::Equals(list_value, &expected_value)) << key; +} + +void ExpectDictStringValue(const std::string& expected_value, + const DictionaryValue& value, + const std::string& key) { + std::string string_value; + EXPECT_TRUE(value.GetString(key, &string_value)) << key; + EXPECT_EQ(expected_value, string_value) << key; +} + +void ExpectStringValue(const std::string& expected_str, + StringValue* actual) { + scoped_ptr<StringValue> scoped_actual(actual); + std::string actual_str; + EXPECT_TRUE(scoped_actual->GetAsString(&actual_str)); + EXPECT_EQ(expected_str, actual_str); +} + +namespace test { + +scoped_ptr<Value> ParseJson(base::StringPiece json) { + std::string error_msg; + scoped_ptr<Value> result(base::JSONReader::ReadAndReturnError( + json, base::JSON_ALLOW_TRAILING_COMMAS, + NULL, &error_msg)); + if (!result) { + ADD_FAILURE() << "Failed to parse \"" << json << "\": " << error_msg; + result.reset(Value::CreateNullValue()); + } + return result.Pass(); +} + +} // namespace test +} // namespace base
diff --git a/src/base/test/values_test_util.h b/src/base/test/values_test_util.h new file mode 100644 index 0000000..ef4710d --- /dev/null +++ b/src/base/test/values_test_util.h
@@ -0,0 +1,56 @@ +// 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_TEST_VALUES_TEST_UTIL_H_ +#define BASE_TEST_VALUES_TEST_UTIL_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/string_piece.h" + +namespace base { +class DictionaryValue; +class ListValue; +class StringValue; +class Value; + +// All the functions below expect that the value for the given key in +// the given dictionary equals the given expected value. + +void ExpectDictBooleanValue(bool expected_value, + const DictionaryValue& value, + const std::string& key); + +void ExpectDictDictionaryValue(const DictionaryValue& expected_value, + const DictionaryValue& value, + const std::string& key); + +void ExpectDictIntegerValue(int expected_value, + const DictionaryValue& value, + const std::string& key); + +void ExpectDictListValue(const ListValue& expected_value, + const DictionaryValue& value, + const std::string& key); + +void ExpectDictStringValue(const std::string& expected_value, + const DictionaryValue& value, + const std::string& key); + +// Takes ownership of |actual|. +void ExpectStringValue(const std::string& expected_str, + StringValue* actual); + +namespace test { + +// Parses |json| as JSON, allowing trailing commas, and returns the +// resulting value. If the json fails to parse, causes an EXPECT +// failure and returns the Null Value (but never a NULL pointer). +scoped_ptr<Value> ParseJson(base::StringPiece json); + +} // namespace test +} // namespace base + +#endif // BASE_TEST_VALUES_TEST_UTIL_H_
diff --git a/src/base/third_party/dmg_fp/LICENSE b/src/base/third_party/dmg_fp/LICENSE new file mode 100644 index 0000000..716f1ef --- /dev/null +++ b/src/base/third_party/dmg_fp/LICENSE
@@ -0,0 +1,18 @@ +/**************************************************************** + * + * The author of this software is David M. Gay. + * + * Copyright (c) 1991, 2000, 2001 by Lucent Technologies. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + * + ***************************************************************/
diff --git a/src/base/third_party/dmg_fp/README.chromium b/src/base/third_party/dmg_fp/README.chromium new file mode 100644 index 0000000..10db408 --- /dev/null +++ b/src/base/third_party/dmg_fp/README.chromium
@@ -0,0 +1,22 @@ +Name: David M. Gay's floating point routines +URL: http://www.netlib.org/fp/ +License: MIT-like + +Original dtoa.c file can be found at <http://www.netlib.org/fp/dtoa.c>. +Original g_fmt.c file can be found at <http://www.netlib.org/fp/g_fmt.c>. + +List of changes made to original code: + - wrapped functions in dmg_fp namespace + - renamed .c files to .cc + - added dmg_fp.h header + - added #define IEEE_8087 to dtoa.cc + - added #define NO_HEX_FP to dtoa.cc + - made some minor changes to allow clean compilation under g++ -Wall, see + gcc_warnings.patch. + - made some minor changes to build on 64-bit, see gcc_64_bit.patch. + - made minor changes for -Wextra for Mac build, see mac_wextra.patch + - crash fix for running with reduced CPU float precision, see + float_precision_crash.patch and crbug.com/123157 + - Fix for 'warning C4703: potentially uninitialized local pointer variable' + in VS2012. +
diff --git a/src/base/third_party/dmg_fp/dmg_fp.h b/src/base/third_party/dmg_fp/dmg_fp.h new file mode 100644 index 0000000..4795397 --- /dev/null +++ b/src/base/third_party/dmg_fp/dmg_fp.h
@@ -0,0 +1,30 @@ +// Copyright (c) 2008 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 THIRD_PARTY_DMG_FP_H_ +#define THIRD_PARTY_DMG_FP_H_ + +namespace dmg_fp { + +// Return a nearest machine number to the input decimal +// string (or set errno to ERANGE). With IEEE arithmetic, ties are +// broken by the IEEE round-even rule. Otherwise ties are broken by +// biased rounding (add half and chop). +double strtod(const char* s00, char** se); + +// Convert double to ASCII string. For meaning of parameters +// see dtoa.cc file. +char* dtoa(double d, int mode, int ndigits, + int* decpt, int* sign, char** rve); + +// Must be used to free values returned by dtoa. +void freedtoa(char* s); + +// Store the closest decimal approximation to x in b (null terminated). +// Returns a pointer to b. It is sufficient for |b| to be 32 characters. +char* g_fmt(char* b, double x); + +} // namespace dmg_fp + +#endif // THIRD_PARTY_DMG_FP_H_
diff --git a/src/base/third_party/dmg_fp/dtoa.cc b/src/base/third_party/dmg_fp/dtoa.cc new file mode 100644 index 0000000..1339d3f --- /dev/null +++ b/src/base/third_party/dmg_fp/dtoa.cc
@@ -0,0 +1,4219 @@ +/**************************************************************** + * + * The author of this software is David M. Gay. + * + * Copyright (c) 1991, 2000, 2001 by Lucent Technologies. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + * + ***************************************************************/ + +/* Please send bug reports to David M. Gay (dmg at acm dot org, + * with " at " changed at "@" and " dot " changed to "."). */ + +/* On a machine with IEEE extended-precision registers, it is + * necessary to specify double-precision (53-bit) rounding precision + * before invoking strtod or dtoa. If the machine uses (the equivalent + * of) Intel 80x87 arithmetic, the call + * _control87(PC_53, MCW_PC); + * does this with many compilers. Whether this or another call is + * appropriate depends on the compiler; for this to work, it may be + * necessary to #include "float.h" or another system-dependent header + * file. + */ + +/* strtod for IEEE-, VAX-, and IBM-arithmetic machines. + * + * This strtod returns a nearest machine number to the input decimal + * string (or sets errno to ERANGE). With IEEE arithmetic, ties are + * broken by the IEEE round-even rule. Otherwise ties are broken by + * biased rounding (add half and chop). + * + * Inspired loosely by William D. Clinger's paper "How to Read Floating + * Point Numbers Accurately" [Proc. ACM SIGPLAN '90, pp. 92-101]. + * + * Modifications: + * + * 1. We only require IEEE, IBM, or VAX double-precision + * arithmetic (not IEEE double-extended). + * 2. We get by with floating-point arithmetic in a case that + * Clinger missed -- when we're computing d * 10^n + * for a small integer d and the integer n is not too + * much larger than 22 (the maximum integer k for which + * we can represent 10^k exactly), we may be able to + * compute (d*10^k) * 10^(e-k) with just one roundoff. + * 3. Rather than a bit-at-a-time adjustment of the binary + * result in the hard case, we use floating-point + * arithmetic to determine the adjustment to within + * one bit; only in really hard cases do we need to + * compute a second residual. + * 4. Because of 3., we don't need a large table of powers of 10 + * for ten-to-e (just some small tables, e.g. of 10^k + * for 0 <= k <= 22). + */ + +/* + * #define IEEE_8087 for IEEE-arithmetic machines where the least + * significant byte has the lowest address. + * #define IEEE_MC68k for IEEE-arithmetic machines where the most + * significant byte has the lowest address. + * #define Long int on machines with 32-bit ints and 64-bit longs. + * #define IBM for IBM mainframe-style floating-point arithmetic. + * #define VAX for VAX-style floating-point arithmetic (D_floating). + * #define No_leftright to omit left-right logic in fast floating-point + * computation of dtoa. + * #define Honor_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 + * and strtod and dtoa should round accordingly. Unless Trust_FLT_ROUNDS + * is also #defined, fegetround() will be queried for the rounding mode. + * Note that both FLT_ROUNDS and fegetround() are specified by the C99 + * standard (and are specified to be consistent, with fesetround() + * affecting the value of FLT_ROUNDS), but that some (Linux) systems + * do not work correctly in this regard, so using fegetround() is more + * portable than using FLT_FOUNDS directly. + * #define Check_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 + * and Honor_FLT_ROUNDS is not #defined. + * #define RND_PRODQUOT to use rnd_prod and rnd_quot (assembly routines + * that use extended-precision instructions to compute rounded + * products and quotients) with IBM. + * #define ROUND_BIASED for IEEE-format with biased rounding. + * #define Inaccurate_Divide for IEEE-format with correctly rounded + * products but inaccurate quotients, e.g., for Intel i860. + * #define NO_LONG_LONG on machines that do not have a "long long" + * integer type (of >= 64 bits). On such machines, you can + * #define Just_16 to store 16 bits per 32-bit Long when doing + * high-precision integer arithmetic. Whether this speeds things + * up or slows things down depends on the machine and the number + * being converted. If long long is available and the name is + * something other than "long long", #define Llong to be the name, + * and if "unsigned Llong" does not work as an unsigned version of + * Llong, #define #ULLong to be the corresponding unsigned type. + * #define KR_headers for old-style C function headers. + * #define Bad_float_h if your system lacks a float.h or if it does not + * define some or all of DBL_DIG, DBL_MAX_10_EXP, DBL_MAX_EXP, + * FLT_RADIX, FLT_ROUNDS, and DBL_MAX. + * #define MALLOC your_malloc, where your_malloc(n) acts like malloc(n) + * if memory is available and otherwise does something you deem + * appropriate. If MALLOC is undefined, malloc will be invoked + * directly -- and assumed always to succeed. Similarly, if you + * want something other than the system's free() to be called to + * recycle memory acquired from MALLOC, #define FREE to be the + * name of the alternate routine. (FREE or free is only called in + * pathological cases, e.g., in a dtoa call after a dtoa return in + * mode 3 with thousands of digits requested.) + * #define Omit_Private_Memory to omit logic (added Jan. 1998) for making + * memory allocations from a private pool of memory when possible. + * When used, the private pool is PRIVATE_MEM bytes long: 2304 bytes, + * unless #defined to be a different length. This default length + * suffices to get rid of MALLOC calls except for unusual cases, + * such as decimal-to-binary conversion of a very long string of + * digits. The longest string dtoa can return is about 751 bytes + * long. For conversions by strtod of strings of 800 digits and + * all dtoa conversions in single-threaded executions with 8-byte + * pointers, PRIVATE_MEM >= 7400 appears to suffice; with 4-byte + * pointers, PRIVATE_MEM >= 7112 appears adequate. + * #define NO_INFNAN_CHECK if you do not wish to have INFNAN_CHECK + * #defined automatically on IEEE systems. On such systems, + * when INFNAN_CHECK is #defined, strtod checks + * for Infinity and NaN (case insensitively). On some systems + * (e.g., some HP systems), it may be necessary to #define NAN_WORD0 + * appropriately -- to the most significant word of a quiet NaN. + * (On HP Series 700/800 machines, -DNAN_WORD0=0x7ff40000 works.) + * When INFNAN_CHECK is #defined and No_Hex_NaN is not #defined, + * strtod also accepts (case insensitively) strings of the form + * NaN(x), where x is a string of hexadecimal digits and spaces; + * if there is only one string of hexadecimal digits, it is taken + * for the 52 fraction bits of the resulting NaN; if there are two + * or more strings of hex digits, the first is for the high 20 bits, + * the second and subsequent for the low 32 bits, with intervening + * white space ignored; but if this results in none of the 52 + * fraction bits being on (an IEEE Infinity symbol), then NAN_WORD0 + * and NAN_WORD1 are used instead. + * #define MULTIPLE_THREADS if the system offers preemptively scheduled + * multiple threads. In this case, you must provide (or suitably + * #define) two locks, acquired by ACQUIRE_DTOA_LOCK(n) and freed + * by FREE_DTOA_LOCK(n) for n = 0 or 1. (The second lock, accessed + * in pow5mult, ensures lazy evaluation of only one copy of high + * powers of 5; omitting this lock would introduce a small + * probability of wasting memory, but would otherwise be harmless.) + * You must also invoke freedtoa(s) to free the value s returned by + * dtoa. You may do so whether or not MULTIPLE_THREADS is #defined. + * #define NO_IEEE_Scale to disable new (Feb. 1997) logic in strtod that + * avoids underflows on inputs whose result does not underflow. + * If you #define NO_IEEE_Scale on a machine that uses IEEE-format + * floating-point numbers and flushes underflows to zero rather + * than implementing gradual underflow, then you must also #define + * Sudden_Underflow. + * #define USE_LOCALE to use the current locale's decimal_point value. + * #define SET_INEXACT if IEEE arithmetic is being used and extra + * computation should be done to set the inexact flag when the + * result is inexact and avoid setting inexact when the result + * is exact. In this case, dtoa.c must be compiled in + * an environment, perhaps provided by #include "dtoa.c" in a + * suitable wrapper, that defines two functions, + * int get_inexact(void); + * void clear_inexact(void); + * such that get_inexact() returns a nonzero value if the + * inexact bit is already set, and clear_inexact() sets the + * inexact bit to 0. When SET_INEXACT is #defined, strtod + * also does extra computations to set the underflow and overflow + * flags when appropriate (i.e., when the result is tiny and + * inexact or when it is a numeric value rounded to +-infinity). + * #define NO_ERRNO if strtod should not assign errno = ERANGE when + * the result overflows to +-Infinity or underflows to 0. + * #define NO_HEX_FP to omit recognition of hexadecimal floating-point + * values by strtod. + * #define NO_STRTOD_BIGCOMP (on IEEE-arithmetic systems only for now) + * to disable logic for "fast" testing of very long input strings + * to strtod. This testing proceeds by initially truncating the + * input string, then if necessary comparing the whole string with + * a decimal expansion to decide close cases. This logic is only + * used for input more than STRTOD_DIGLIM digits long (default 40). + */ + +#if defined(ARCH_CPU_BIG_ENDIAN) +#define IEEE_MC68k +#else +#define IEEE_8087 +#endif +#define NO_HEX_FP + +#ifndef Long +#if __LP64__ +#define Long int +#else +#define Long long +#endif +#endif +#ifndef ULong +typedef unsigned Long ULong; +#endif + +#ifdef DEBUG +#include "stdio.h" +#define Bug(x) {fprintf(stderr, "%s\n", x); exit(1);} +#endif + +#if defined(STARBOARD) +#include "starboard/memory.h" +#define MALLOC SbMemoryAllocate +#define FREE SbMemoryFree +#else +#include "stdlib.h" +#include "string.h" +#endif + +#ifdef USE_LOCALE +#include "locale.h" +#endif + +#ifdef Honor_FLT_ROUNDS +#ifndef Trust_FLT_ROUNDS +#include <fenv.h> +#endif +#endif + +#ifdef MALLOC +#ifdef KR_headers +extern char *MALLOC(); +#else +extern void *MALLOC(size_t); +#endif +#else +#define MALLOC malloc +#endif + +#ifndef Omit_Private_Memory +#ifndef PRIVATE_MEM +#define PRIVATE_MEM 2304 +#endif +#define PRIVATE_mem ((unsigned)((PRIVATE_MEM+sizeof(double)-1)/sizeof(double))) +static double private_mem[PRIVATE_mem], *pmem_next = private_mem; +#endif + +#undef IEEE_Arith +#undef Avoid_Underflow +#ifdef IEEE_MC68k +#define IEEE_Arith +#endif +#ifdef IEEE_8087 +#define IEEE_Arith +#endif + +#ifdef IEEE_Arith +#ifndef NO_INFNAN_CHECK +#undef INFNAN_CHECK +#define INFNAN_CHECK +#endif +#else +#undef INFNAN_CHECK +#define NO_STRTOD_BIGCOMP +#endif + +#include "errno.h" + +#ifdef Bad_float_h + +#ifdef IEEE_Arith +#define DBL_DIG 15 +#define DBL_MAX_10_EXP 308 +#define DBL_MAX_EXP 1024 +#define FLT_RADIX 2 +#endif /*IEEE_Arith*/ + +#ifdef IBM +#define DBL_DIG 16 +#define DBL_MAX_10_EXP 75 +#define DBL_MAX_EXP 63 +#define FLT_RADIX 16 +#define DBL_MAX 7.2370055773322621e+75 +#endif + +#ifdef VAX +#define DBL_DIG 16 +#define DBL_MAX_10_EXP 38 +#define DBL_MAX_EXP 127 +#define FLT_RADIX 2 +#define DBL_MAX 1.7014118346046923e+38 +#endif + +#ifndef LONG_MAX +#define LONG_MAX 2147483647 +#endif + +#else /* ifndef Bad_float_h */ +#include "float.h" +#endif /* Bad_float_h */ + +#ifndef __MATH_H__ +#include "math.h" +#endif + +namespace dmg_fp { + +#ifndef CONST +#ifdef KR_headers +#define CONST /* blank */ +#else +#define CONST const +#endif +#endif + +#if defined(IEEE_8087) + defined(IEEE_MC68k) + defined(VAX) + defined(IBM) != 1 +Exactly one of IEEE_8087, IEEE_MC68k, VAX, or IBM should be defined. +#endif + +typedef union { double d; ULong L[2]; } U; + +#ifdef IEEE_8087 +#define word0(x) (x)->L[1] +#define word1(x) (x)->L[0] +#else +#define word0(x) (x)->L[0] +#define word1(x) (x)->L[1] +#endif +#define dval(x) (x)->d + +#ifndef STRTOD_DIGLIM +#define STRTOD_DIGLIM 40 +#endif + +#ifdef DIGLIM_DEBUG +extern int strtod_diglim; +#else +#define strtod_diglim STRTOD_DIGLIM +#endif + +/* The following definition of Storeinc is appropriate for MIPS processors. + * An alternative that might be better on some machines is + * #define Storeinc(a,b,c) (*a++ = b << 16 | c & 0xffff) + */ +#if defined(IEEE_8087) + defined(VAX) +#define Storeinc(a,b,c) (((unsigned short *)a)[1] = (unsigned short)b, \ +((unsigned short *)a)[0] = (unsigned short)c, a++) +#else +#define Storeinc(a,b,c) (((unsigned short *)a)[0] = (unsigned short)b, \ +((unsigned short *)a)[1] = (unsigned short)c, a++) +#endif + +/* #define P DBL_MANT_DIG */ +/* Ten_pmax = floor(P*log(2)/log(5)) */ +/* Bletch = (highest power of 2 < DBL_MAX_10_EXP) / 16 */ +/* Quick_max = floor((P-1)*log(FLT_RADIX)/log(10) - 1) */ +/* Int_max = floor(P*log(FLT_RADIX)/log(10) - 1) */ + +#ifdef IEEE_Arith +#define Exp_shift 20 +#define Exp_shift1 20 +#define Exp_msk1 0x100000 +#define Exp_msk11 0x100000 +#define Exp_mask 0x7ff00000 +#define P 53 +#define Nbits 53 +#define Bias 1023 +#define Emax 1023 +#define Emin (-1022) +#define Exp_1 0x3ff00000 +#define Exp_11 0x3ff00000 +#define Ebits 11 +#define Frac_mask 0xfffff +#define Frac_mask1 0xfffff +#define Ten_pmax 22 +#define Bletch 0x10 +#define Bndry_mask 0xfffff +#define Bndry_mask1 0xfffff +#define LSB 1 +#define Sign_bit 0x80000000 +#define Log2P 1 +#define Tiny0 0 +#define Tiny1 1 +#define Quick_max 14 +#define Int_max 14 +#ifndef NO_IEEE_Scale +#define Avoid_Underflow +#ifdef Flush_Denorm /* debugging option */ +#undef Sudden_Underflow +#endif +#endif + +#ifndef Flt_Rounds +#ifdef FLT_ROUNDS +#define Flt_Rounds FLT_ROUNDS +#else +#define Flt_Rounds 1 +#endif +#endif /*Flt_Rounds*/ + +#ifdef Honor_FLT_ROUNDS +#undef Check_FLT_ROUNDS +#define Check_FLT_ROUNDS +#else +#define Rounding Flt_Rounds +#endif + +#else /* ifndef IEEE_Arith */ +#undef Check_FLT_ROUNDS +#undef Honor_FLT_ROUNDS +#undef SET_INEXACT +#undef Sudden_Underflow +#define Sudden_Underflow +#ifdef IBM +#undef Flt_Rounds +#define Flt_Rounds 0 +#define Exp_shift 24 +#define Exp_shift1 24 +#define Exp_msk1 0x1000000 +#define Exp_msk11 0x1000000 +#define Exp_mask 0x7f000000 +#define P 14 +#define Nbits 56 +#define Bias 65 +#define Emax 248 +#define Emin (-260) +#define Exp_1 0x41000000 +#define Exp_11 0x41000000 +#define Ebits 8 /* exponent has 7 bits, but 8 is the right value in b2d */ +#define Frac_mask 0xffffff +#define Frac_mask1 0xffffff +#define Bletch 4 +#define Ten_pmax 22 +#define Bndry_mask 0xefffff +#define Bndry_mask1 0xffffff +#define LSB 1 +#define Sign_bit 0x80000000 +#define Log2P 4 +#define Tiny0 0x100000 +#define Tiny1 0 +#define Quick_max 14 +#define Int_max 15 +#else /* VAX */ +#undef Flt_Rounds +#define Flt_Rounds 1 +#define Exp_shift 23 +#define Exp_shift1 7 +#define Exp_msk1 0x80 +#define Exp_msk11 0x800000 +#define Exp_mask 0x7f80 +#define P 56 +#define Nbits 56 +#define Bias 129 +#define Emax 126 +#define Emin (-129) +#define Exp_1 0x40800000 +#define Exp_11 0x4080 +#define Ebits 8 +#define Frac_mask 0x7fffff +#define Frac_mask1 0xffff007f +#define Ten_pmax 24 +#define Bletch 2 +#define Bndry_mask 0xffff007f +#define Bndry_mask1 0xffff007f +#define LSB 0x10000 +#define Sign_bit 0x8000 +#define Log2P 1 +#define Tiny0 0x80 +#define Tiny1 0 +#define Quick_max 15 +#define Int_max 15 +#endif /* IBM, VAX */ +#endif /* IEEE_Arith */ + +#ifndef IEEE_Arith +#define ROUND_BIASED +#endif + +#ifdef RND_PRODQUOT +#define rounded_product(a,b) a = rnd_prod(a, b) +#define rounded_quotient(a,b) a = rnd_quot(a, b) +#ifdef KR_headers +extern double rnd_prod(), rnd_quot(); +#else +extern double rnd_prod(double, double), rnd_quot(double, double); +#endif +#else +#define rounded_product(a,b) a *= b +#define rounded_quotient(a,b) a /= b +#endif + +#define Big0 (Frac_mask1 | Exp_msk1*(DBL_MAX_EXP+Bias-1)) +#define Big1 0xffffffff + +#ifndef Pack_32 +#define Pack_32 +#endif + +typedef struct BCinfo BCinfo; + struct +BCinfo { int dp0, dp1, dplen, dsign, e0, inexact, nd, nd0, rounding, scale, uflchk; }; + +#ifdef KR_headers +#define FFFFFFFF ((((unsigned long)0xffff)<<16)|(unsigned long)0xffff) +#else +#define FFFFFFFF 0xffffffffUL +#endif + +#ifdef NO_LONG_LONG +#undef ULLong +#ifdef Just_16 +#undef Pack_32 +/* When Pack_32 is not defined, we store 16 bits per 32-bit Long. + * This makes some inner loops simpler and sometimes saves work + * during multiplications, but it often seems to make things slightly + * slower. Hence the default is now to store 32 bits per Long. + */ +#endif +#else /* long long available */ +#ifndef Llong +#define Llong long long +#endif +#ifndef ULLong +#define ULLong unsigned Llong +#endif +#endif /* NO_LONG_LONG */ + +#ifndef MULTIPLE_THREADS +#define ACQUIRE_DTOA_LOCK(n) /*nothing*/ +#define FREE_DTOA_LOCK(n) /*nothing*/ +#endif + +#define Kmax 7 + +double strtod(const char *s00, char **se); +char *dtoa(double d, int mode, int ndigits, + int *decpt, int *sign, char **rve); + + struct +Bigint { + struct Bigint *next; + int k, maxwds, sign, wds; + ULong x[1]; + }; + + typedef struct Bigint Bigint; + + static Bigint *freelist[Kmax+1]; + + static Bigint * +Balloc +#ifdef KR_headers + (k) int k; +#else + (int k) +#endif +{ + int x; + Bigint *rv; +#ifndef Omit_Private_Memory + unsigned int len; +#endif + + ACQUIRE_DTOA_LOCK(0); + /* The k > Kmax case does not need ACQUIRE_DTOA_LOCK(0), */ + /* but this case seems very unlikely. */ + if (k <= Kmax && (rv = freelist[k])) + freelist[k] = rv->next; + else { + x = 1 << k; +#ifdef Omit_Private_Memory + rv = (Bigint *)MALLOC(sizeof(Bigint) + (x-1)*sizeof(ULong)); +#else + len = (sizeof(Bigint) + (x-1)*sizeof(ULong) + sizeof(double) - 1) + /sizeof(double); + if (k <= Kmax && pmem_next - private_mem + len <= PRIVATE_mem) { + rv = (Bigint*)pmem_next; + pmem_next += len; + } + else + rv = (Bigint*)MALLOC(len*sizeof(double)); +#endif + rv->k = k; + rv->maxwds = x; + } + FREE_DTOA_LOCK(0); + rv->sign = rv->wds = 0; + return rv; + } + + static void +Bfree +#ifdef KR_headers + (v) Bigint *v; +#else + (Bigint *v) +#endif +{ + if (v) { + if (v->k > Kmax) +#ifdef FREE + FREE((void*)v); +#else + free((void*)v); +#endif + else { + ACQUIRE_DTOA_LOCK(0); + v->next = freelist[v->k]; + freelist[v->k] = v; + FREE_DTOA_LOCK(0); + } + } + } + +#define Bcopy(x,y) memcpy((char *)&x->sign, (char *)&y->sign, \ +y->wds*sizeof(Long) + 2*sizeof(int)) + + static Bigint * +multadd +#ifdef KR_headers + (b, m, a) Bigint *b; int m, a; +#else + (Bigint *b, int m, int a) /* multiply by m and add a */ +#endif +{ + int i, wds; +#ifdef ULLong + ULong *x; + ULLong carry, y; +#else + ULong carry, *x, y; +#ifdef Pack_32 + ULong xi, z; +#endif +#endif + Bigint *b1; + + wds = b->wds; + x = b->x; + i = 0; + carry = a; + do { +#ifdef ULLong + y = *x * (ULLong)m + carry; + carry = y >> 32; + *x++ = y & FFFFFFFF; +#else +#ifdef Pack_32 + xi = *x; + y = (xi & 0xffff) * m + carry; + z = (xi >> 16) * m + (y >> 16); + carry = z >> 16; + *x++ = (z << 16) + (y & 0xffff); +#else + y = *x * m + carry; + carry = y >> 16; + *x++ = y & 0xffff; +#endif +#endif + } + while(++i < wds); + if (carry) { + if (wds >= b->maxwds) { + b1 = Balloc(b->k+1); + Bcopy(b1, b); + Bfree(b); + b = b1; + } + b->x[wds++] = carry; + b->wds = wds; + } + return b; + } + + static Bigint * +s2b +#ifdef KR_headers + (s, nd0, nd, y9, dplen) CONST char *s; int nd0, nd, dplen; ULong y9; +#else + (CONST char *s, int nd0, int nd, ULong y9, int dplen) +#endif +{ + Bigint *b; + int i, k; + Long x, y; + + x = (nd + 8) / 9; + for(k = 0, y = 1; x > y; y <<= 1, k++) ; +#ifdef Pack_32 + b = Balloc(k); + b->x[0] = y9; + b->wds = 1; +#else + b = Balloc(k+1); + b->x[0] = y9 & 0xffff; + b->wds = (b->x[1] = y9 >> 16) ? 2 : 1; +#endif + + i = 9; + if (9 < nd0) { + s += 9; + do b = multadd(b, 10, *s++ - '0'); + while(++i < nd0); + s += dplen; + } + else + s += dplen + 9; + for(; i < nd; i++) + b = multadd(b, 10, *s++ - '0'); + return b; + } + + static int +hi0bits +#ifdef KR_headers + (x) ULong x; +#else + (ULong x) +#endif +{ + int k = 0; + + if (!(x & 0xffff0000)) { + k = 16; + x <<= 16; + } + if (!(x & 0xff000000)) { + k += 8; + x <<= 8; + } + if (!(x & 0xf0000000)) { + k += 4; + x <<= 4; + } + if (!(x & 0xc0000000)) { + k += 2; + x <<= 2; + } + if (!(x & 0x80000000)) { + k++; + if (!(x & 0x40000000)) + return 32; + } + return k; + } + + static int +lo0bits +#ifdef KR_headers + (y) ULong *y; +#else + (ULong *y) +#endif +{ + int k; + ULong x = *y; + + if (x & 7) { + if (x & 1) + return 0; + if (x & 2) { + *y = x >> 1; + return 1; + } + *y = x >> 2; + return 2; + } + k = 0; + if (!(x & 0xffff)) { + k = 16; + x >>= 16; + } + if (!(x & 0xff)) { + k += 8; + x >>= 8; + } + if (!(x & 0xf)) { + k += 4; + x >>= 4; + } + if (!(x & 0x3)) { + k += 2; + x >>= 2; + } + if (!(x & 1)) { + k++; + x >>= 1; + if (!x) + return 32; + } + *y = x; + return k; + } + + static Bigint * +i2b +#ifdef KR_headers + (i) int i; +#else + (int i) +#endif +{ + Bigint *b; + + b = Balloc(1); + b->x[0] = i; + b->wds = 1; + return b; + } + + static Bigint * +mult +#ifdef KR_headers + (a, b) Bigint *a, *b; +#else + (Bigint *a, Bigint *b) +#endif +{ + Bigint *c; + int k, wa, wb, wc; + ULong *x, *xa, *xae, *xb, *xbe, *xc, *xc0; + ULong y; +#ifdef ULLong + ULLong carry, z; +#else + ULong carry, z; +#ifdef Pack_32 + ULong z2; +#endif +#endif + + if (a->wds < b->wds) { + c = a; + a = b; + b = c; + } + k = a->k; + wa = a->wds; + wb = b->wds; + wc = wa + wb; + if (wc > a->maxwds) + k++; + c = Balloc(k); + for(x = c->x, xa = x + wc; x < xa; x++) + *x = 0; + xa = a->x; + xae = xa + wa; + xb = b->x; + xbe = xb + wb; + xc0 = c->x; +#ifdef ULLong + for(; xb < xbe; xc0++) { + if ((y = *xb++)) { + x = xa; + xc = xc0; + carry = 0; + do { + z = *x++ * (ULLong)y + *xc + carry; + carry = z >> 32; + *xc++ = z & FFFFFFFF; + } + while(x < xae); + *xc = carry; + } + } +#else +#ifdef Pack_32 + for(; xb < xbe; xb++, xc0++) { + if (y = *xb & 0xffff) { + x = xa; + xc = xc0; + carry = 0; + do { + z = (*x & 0xffff) * y + (*xc & 0xffff) + carry; + carry = z >> 16; + z2 = (*x++ >> 16) * y + (*xc >> 16) + carry; + carry = z2 >> 16; + Storeinc(xc, z2, z); + } + while(x < xae); + *xc = carry; + } + if (y = *xb >> 16) { + x = xa; + xc = xc0; + carry = 0; + z2 = *xc; + do { + z = (*x & 0xffff) * y + (*xc >> 16) + carry; + carry = z >> 16; + Storeinc(xc, z, z2); + z2 = (*x++ >> 16) * y + (*xc & 0xffff) + carry; + carry = z2 >> 16; + } + while(x < xae); + *xc = z2; + } + } +#else + for(; xb < xbe; xc0++) { + if (y = *xb++) { + x = xa; + xc = xc0; + carry = 0; + do { + z = *x++ * y + *xc + carry; + carry = z >> 16; + *xc++ = z & 0xffff; + } + while(x < xae); + *xc = carry; + } + } +#endif +#endif + for(xc0 = c->x, xc = xc0 + wc; wc > 0 && !*--xc; --wc) ; + c->wds = wc; + return c; + } + + static Bigint *p5s; + + static Bigint * +pow5mult +#ifdef KR_headers + (b, k) Bigint *b; int k; +#else + (Bigint *b, int k) +#endif +{ + Bigint *b1, *p5, *p51; + int i; + static int p05[3] = { 5, 25, 125 }; + + if ((i = k & 3)) + b = multadd(b, p05[i-1], 0); + + if (!(k >>= 2)) + return b; + if (!(p5 = p5s)) { + /* first time */ +#ifdef MULTIPLE_THREADS + ACQUIRE_DTOA_LOCK(1); + if (!(p5 = p5s)) { + p5 = p5s = i2b(625); + p5->next = 0; + } + FREE_DTOA_LOCK(1); +#else + p5 = p5s = i2b(625); + p5->next = 0; +#endif + } + for(;;) { + if (k & 1) { + b1 = mult(b, p5); + Bfree(b); + b = b1; + } + if (!(k >>= 1)) + break; + if (!(p51 = p5->next)) { +#ifdef MULTIPLE_THREADS + ACQUIRE_DTOA_LOCK(1); + if (!(p51 = p5->next)) { + p51 = p5->next = mult(p5,p5); + p51->next = 0; + } + FREE_DTOA_LOCK(1); +#else + p51 = p5->next = mult(p5,p5); + p51->next = 0; +#endif + } + p5 = p51; + } + return b; + } + + static Bigint * +lshift +#ifdef KR_headers + (b, k) Bigint *b; int k; +#else + (Bigint *b, int k) +#endif +{ + int i, k1, n, n1; + Bigint *b1; + ULong *x, *x1, *xe, z; + +#ifdef Pack_32 + n = k >> 5; +#else + n = k >> 4; +#endif + k1 = b->k; + n1 = n + b->wds + 1; + for(i = b->maxwds; n1 > i; i <<= 1) + k1++; + b1 = Balloc(k1); + x1 = b1->x; + for(i = 0; i < n; i++) + *x1++ = 0; + x = b->x; + xe = x + b->wds; +#ifdef Pack_32 + if (k &= 0x1f) { + k1 = 32 - k; + z = 0; + do { + *x1++ = *x << k | z; + z = *x++ >> k1; + } + while(x < xe); + if ((*x1 = z)) + ++n1; + } +#else + if (k &= 0xf) { + k1 = 16 - k; + z = 0; + do { + *x1++ = *x << k & 0xffff | z; + z = *x++ >> k1; + } + while(x < xe); + if (*x1 = z) + ++n1; + } +#endif + else do + *x1++ = *x++; + while(x < xe); + b1->wds = n1 - 1; + Bfree(b); + return b1; + } + + static int +cmp +#ifdef KR_headers + (a, b) Bigint *a, *b; +#else + (Bigint *a, Bigint *b) +#endif +{ + ULong *xa, *xa0, *xb, *xb0; + int i, j; + + i = a->wds; + j = b->wds; +#ifdef DEBUG + if (i > 1 && !a->x[i-1]) + Bug("cmp called with a->x[a->wds-1] == 0"); + if (j > 1 && !b->x[j-1]) + Bug("cmp called with b->x[b->wds-1] == 0"); +#endif + if (i -= j) + return i; + xa0 = a->x; + xa = xa0 + j; + xb0 = b->x; + xb = xb0 + j; + for(;;) { + if (*--xa != *--xb) + return *xa < *xb ? -1 : 1; + if (xa <= xa0) + break; + } + return 0; + } + + static Bigint * +diff +#ifdef KR_headers + (a, b) Bigint *a, *b; +#else + (Bigint *a, Bigint *b) +#endif +{ + Bigint *c; + int i, wa, wb; + ULong *xa, *xae, *xb, *xbe, *xc; +#ifdef ULLong + ULLong borrow, y; +#else + ULong borrow, y; +#ifdef Pack_32 + ULong z; +#endif +#endif + + i = cmp(a,b); + if (!i) { + c = Balloc(0); + c->wds = 1; + c->x[0] = 0; + return c; + } + if (i < 0) { + c = a; + a = b; + b = c; + i = 1; + } + else + i = 0; + c = Balloc(a->k); + c->sign = i; + wa = a->wds; + xa = a->x; + xae = xa + wa; + wb = b->wds; + xb = b->x; + xbe = xb + wb; + xc = c->x; + borrow = 0; +#ifdef ULLong + do { + y = (ULLong)*xa++ - *xb++ - borrow; + borrow = y >> 32 & (ULong)1; + *xc++ = y & FFFFFFFF; + } + while(xb < xbe); + while(xa < xae) { + y = *xa++ - borrow; + borrow = y >> 32 & (ULong)1; + *xc++ = y & FFFFFFFF; + } +#else +#ifdef Pack_32 + do { + y = (*xa & 0xffff) - (*xb & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*xa++ >> 16) - (*xb++ >> 16) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(xc, z, y); + } + while(xb < xbe); + while(xa < xae) { + y = (*xa & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*xa++ >> 16) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(xc, z, y); + } +#else + do { + y = *xa++ - *xb++ - borrow; + borrow = (y & 0x10000) >> 16; + *xc++ = y & 0xffff; + } + while(xb < xbe); + while(xa < xae) { + y = *xa++ - borrow; + borrow = (y & 0x10000) >> 16; + *xc++ = y & 0xffff; + } +#endif +#endif + while(!*--xc) + wa--; + c->wds = wa; + return c; + } + + static double +ulp +#ifdef KR_headers + (x) U *x; +#else + (U *x) +#endif +{ + Long L; + U u; + + L = (word0(x) & Exp_mask) - (P-1)*Exp_msk1; +#ifndef Avoid_Underflow +#ifndef Sudden_Underflow + if (L > 0) { +#endif +#endif +#ifdef IBM + L |= Exp_msk1 >> 4; +#endif + word0(&u) = L; + word1(&u) = 0; +#ifndef Avoid_Underflow +#ifndef Sudden_Underflow + } + else { + L = -L >> Exp_shift; + if (L < Exp_shift) { + word0(&u) = 0x80000 >> L; + word1(&u) = 0; + } + else { + word0(&u) = 0; + L -= Exp_shift; + word1(&u) = L >= 31 ? 1 : 1 << 31 - L; + } + } +#endif +#endif + return dval(&u); + } + + static double +b2d +#ifdef KR_headers + (a, e) Bigint *a; int *e; +#else + (Bigint *a, int *e) +#endif +{ + ULong *xa, *xa0, w, y, z; + int k; + U d; +#ifdef VAX + ULong d0, d1; +#else +#define d0 word0(&d) +#define d1 word1(&d) +#endif + + xa0 = a->x; + xa = xa0 + a->wds; + y = *--xa; +#ifdef DEBUG + if (!y) Bug("zero y in b2d"); +#endif + k = hi0bits(y); + *e = 32 - k; +#ifdef Pack_32 + if (k < Ebits) { + d0 = Exp_1 | y >> (Ebits - k); + w = xa > xa0 ? *--xa : 0; + d1 = y << ((32-Ebits) + k) | w >> (Ebits - k); + goto ret_d; + } + z = xa > xa0 ? *--xa : 0; + if (k -= Ebits) { + d0 = Exp_1 | y << k | z >> (32 - k); + y = xa > xa0 ? *--xa : 0; + d1 = z << k | y >> (32 - k); + } + else { + d0 = Exp_1 | y; + d1 = z; + } +#else + if (k < Ebits + 16) { + z = xa > xa0 ? *--xa : 0; + d0 = Exp_1 | y << k - Ebits | z >> Ebits + 16 - k; + w = xa > xa0 ? *--xa : 0; + y = xa > xa0 ? *--xa : 0; + d1 = z << k + 16 - Ebits | w << k - Ebits | y >> 16 + Ebits - k; + goto ret_d; + } + z = xa > xa0 ? *--xa : 0; + w = xa > xa0 ? *--xa : 0; + k -= Ebits + 16; + d0 = Exp_1 | y << k + 16 | z << k | w >> 16 - k; + y = xa > xa0 ? *--xa : 0; + d1 = w << k + 16 | y << k; +#endif + ret_d: +#ifdef VAX + word0(&d) = d0 >> 16 | d0 << 16; + word1(&d) = d1 >> 16 | d1 << 16; +#else +#undef d0 +#undef d1 +#endif + return dval(&d); + } + + static Bigint * +d2b +#ifdef KR_headers + (d, e, bits) U *d; int *e, *bits; +#else + (U *d, int *e, int *bits) +#endif +{ + Bigint *b; + int de, k; + ULong *x, y, z; +#ifndef Sudden_Underflow + int i; +#endif +#ifdef VAX + ULong d0, d1; + d0 = word0(d) >> 16 | word0(d) << 16; + d1 = word1(d) >> 16 | word1(d) << 16; +#else +#define d0 word0(d) +#define d1 word1(d) +#endif + +#ifdef Pack_32 + b = Balloc(1); +#else + b = Balloc(2); +#endif + x = b->x; + + z = d0 & Frac_mask; + d0 &= 0x7fffffff; /* clear sign bit, which we ignore */ +#ifdef Sudden_Underflow + de = (int)(d0 >> Exp_shift); +#ifndef IBM + z |= Exp_msk11; +#endif +#else + if ((de = (int)(d0 >> Exp_shift))) + z |= Exp_msk1; +#endif +#ifdef Pack_32 + if ((y = d1)) { + if ((k = lo0bits(&y))) { + x[0] = y | z << (32 - k); + z >>= k; + } + else + x[0] = y; +#ifndef Sudden_Underflow + i = +#endif + b->wds = (x[1] = z) ? 2 : 1; + } + else { + k = lo0bits(&z); + x[0] = z; +#ifndef Sudden_Underflow + i = +#endif + b->wds = 1; + k += 32; + } +#else + if (y = d1) { + if (k = lo0bits(&y)) + if (k >= 16) { + x[0] = y | z << 32 - k & 0xffff; + x[1] = z >> k - 16 & 0xffff; + x[2] = z >> k; + i = 2; + } + else { + x[0] = y & 0xffff; + x[1] = y >> 16 | z << 16 - k & 0xffff; + x[2] = z >> k & 0xffff; + x[3] = z >> k+16; + i = 3; + } + else { + x[0] = y & 0xffff; + x[1] = y >> 16; + x[2] = z & 0xffff; + x[3] = z >> 16; + i = 3; + } + } + else { +#ifdef DEBUG + if (!z) + Bug("Zero passed to d2b"); +#endif + k = lo0bits(&z); + if (k >= 16) { + x[0] = z; + i = 0; + } + else { + x[0] = z & 0xffff; + x[1] = z >> 16; + i = 1; + } + k += 32; + } + while(!x[i]) + --i; + b->wds = i + 1; +#endif +#ifndef Sudden_Underflow + if (de) { +#endif +#ifdef IBM + *e = (de - Bias - (P-1) << 2) + k; + *bits = 4*P + 8 - k - hi0bits(word0(d) & Frac_mask); +#else + *e = de - Bias - (P-1) + k; + *bits = P - k; +#endif +#ifndef Sudden_Underflow + } + else { + *e = de - Bias - (P-1) + 1 + k; +#ifdef Pack_32 + *bits = 32*i - hi0bits(x[i-1]); +#else + *bits = (i+2)*16 - hi0bits(x[i]); +#endif + } +#endif + return b; + } +#undef d0 +#undef d1 + + static double +ratio +#ifdef KR_headers + (a, b) Bigint *a, *b; +#else + (Bigint *a, Bigint *b) +#endif +{ + U da, db; + int k, ka, kb; + + dval(&da) = b2d(a, &ka); + dval(&db) = b2d(b, &kb); +#ifdef Pack_32 + k = ka - kb + 32*(a->wds - b->wds); +#else + k = ka - kb + 16*(a->wds - b->wds); +#endif +#ifdef IBM + if (k > 0) { + word0(&da) += (k >> 2)*Exp_msk1; + if (k &= 3) + dval(&da) *= 1 << k; + } + else { + k = -k; + word0(&db) += (k >> 2)*Exp_msk1; + if (k &= 3) + dval(&db) *= 1 << k; + } +#else + if (k > 0) + word0(&da) += k*Exp_msk1; + else { + k = -k; + word0(&db) += k*Exp_msk1; + } +#endif + return dval(&da) / dval(&db); + } + + static CONST double +tens[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22 +#ifdef VAX + , 1e23, 1e24 +#endif + }; + + static CONST double +#ifdef IEEE_Arith +bigtens[] = { 1e16, 1e32, 1e64, 1e128, 1e256 }; +static CONST double tinytens[] = { 1e-16, 1e-32, 1e-64, 1e-128, +#ifdef Avoid_Underflow + 9007199254740992.*9007199254740992.e-256 + /* = 2^106 * 1e-256 */ +#else + 1e-256 +#endif + }; +/* The factor of 2^53 in tinytens[4] helps us avoid setting the underflow */ +/* flag unnecessarily. It leads to a song and dance at the end of strtod. */ +#define Scale_Bit 0x10 +#define n_bigtens 5 +#else +#ifdef IBM +bigtens[] = { 1e16, 1e32, 1e64 }; +static CONST double tinytens[] = { 1e-16, 1e-32, 1e-64 }; +#define n_bigtens 3 +#else +bigtens[] = { 1e16, 1e32 }; +static CONST double tinytens[] = { 1e-16, 1e-32 }; +#define n_bigtens 2 +#endif +#endif + +#undef Need_Hexdig +#ifdef INFNAN_CHECK +#ifndef No_Hex_NaN +#define Need_Hexdig +#endif +#endif + +#ifndef Need_Hexdig +#ifndef NO_HEX_FP +#define Need_Hexdig +#endif +#endif + +#ifdef Need_Hexdig /*{*/ +static unsigned char hexdig[256]; + + static void +#ifdef KR_headers +htinit(h, s, inc) unsigned char *h; unsigned char *s; int inc; +#else +htinit(unsigned char *h, unsigned char *s, int inc) +#endif +{ + int i, j; + for(i = 0; (j = s[i]) !=0; i++) + h[j] = i + inc; + } + + static void +#ifdef KR_headers +hexdig_init() +#else +hexdig_init(void) +#endif +{ +#define USC (unsigned char *) + htinit(hexdig, USC "0123456789", 0x10); + htinit(hexdig, USC "abcdef", 0x10 + 10); + htinit(hexdig, USC "ABCDEF", 0x10 + 10); + } +#endif /* } Need_Hexdig */ + +#ifdef INFNAN_CHECK + +#ifndef NAN_WORD0 +#define NAN_WORD0 0x7ff80000 +#endif + +#ifndef NAN_WORD1 +#define NAN_WORD1 0 +#endif + + static int +match +#ifdef KR_headers + (sp, t) char **sp, *t; +#else + (CONST char **sp, CONST char *t) +#endif +{ + int c, d; + CONST char *s = *sp; + + while((d = *t++)) { + if ((c = *++s) >= 'A' && c <= 'Z') + c += 'a' - 'A'; + if (c != d) + return 0; + } + *sp = s + 1; + return 1; + } + +#ifndef No_Hex_NaN + static void +hexnan +#ifdef KR_headers + (rvp, sp) U *rvp; CONST char **sp; +#else + (U *rvp, CONST char **sp) +#endif +{ + ULong c, x[2]; + CONST char *s; + int c1, havedig, udx0, xshift; + + if (!hexdig['0']) + hexdig_init(); + x[0] = x[1] = 0; + havedig = xshift = 0; + udx0 = 1; + s = *sp; + /* allow optional initial 0x or 0X */ + while((c = *(CONST unsigned char*)(s+1)) && c <= ' ') + ++s; + if (s[1] == '0' && (s[2] == 'x' || s[2] == 'X')) + s += 2; + while((c = *(CONST unsigned char*)++s)) { + if ((c1 = hexdig[c])) + c = c1 & 0xf; + else if (c <= ' ') { + if (udx0 && havedig) { + udx0 = 0; + xshift = 1; + } + continue; + } +#ifdef GDTOA_NON_PEDANTIC_NANCHECK + else if (/*(*/ c == ')' && havedig) { + *sp = s + 1; + break; + } + else + return; /* invalid form: don't change *sp */ +#else + else { + do { + if (/*(*/ c == ')') { + *sp = s + 1; + break; + } + } while((c = *++s)); + break; + } +#endif + havedig = 1; + if (xshift) { + xshift = 0; + x[0] = x[1]; + x[1] = 0; + } + if (udx0) + x[0] = (x[0] << 4) | (x[1] >> 28); + x[1] = (x[1] << 4) | c; + } + if ((x[0] &= 0xfffff) || x[1]) { + word0(rvp) = Exp_mask | x[0]; + word1(rvp) = x[1]; + } + } +#endif /*No_Hex_NaN*/ +#endif /* INFNAN_CHECK */ + +#ifdef Pack_32 +#define ULbits 32 +#define kshift 5 +#define kmask 31 +#else +#define ULbits 16 +#define kshift 4 +#define kmask 15 +#endif +#ifndef NO_HEX_FP /*{*/ + + static void +#ifdef KR_headers +rshift(b, k) Bigint *b; int k; +#else +rshift(Bigint *b, int k) +#endif +{ + ULong *x, *x1, *xe, y; + int n; + + x = x1 = b->x; + n = k >> kshift; + if (n < b->wds) { + xe = x + b->wds; + x += n; + if (k &= kmask) { + n = 32 - k; + y = *x++ >> k; + while(x < xe) { + *x1++ = (y | (*x << n)) & 0xffffffff; + y = *x++ >> k; + } + if ((*x1 = y) !=0) + x1++; + } + else + while(x < xe) + *x1++ = *x++; + } + if ((b->wds = x1 - b->x) == 0) + b->x[0] = 0; + } + + static ULong +#ifdef KR_headers +any_on(b, k) Bigint *b; int k; +#else +any_on(Bigint *b, int k) +#endif +{ + int n, nwds; + ULong *x, *x0, x1, x2; + + x = b->x; + nwds = b->wds; + n = k >> kshift; + if (n > nwds) + n = nwds; + else if (n < nwds && (k &= kmask)) { + x1 = x2 = x[n]; + x1 >>= k; + x1 <<= k; + if (x1 != x2) + return 1; + } + x0 = x; + x += n; + while(x > x0) + if (*--x) + return 1; + return 0; + } + +enum { /* rounding values: same as FLT_ROUNDS */ + Round_zero = 0, + Round_near = 1, + Round_up = 2, + Round_down = 3 + }; + + static Bigint * +#ifdef KR_headers +increment(b) Bigint *b; +#else +increment(Bigint *b) +#endif +{ + ULong *x, *xe; + Bigint *b1; + + x = b->x; + xe = x + b->wds; + do { + if (*x < (ULong)0xffffffffL) { + ++*x; + return b; + } + *x++ = 0; + } while(x < xe); + { + if (b->wds >= b->maxwds) { + b1 = Balloc(b->k+1); + Bcopy(b1,b); + Bfree(b); + b = b1; + } + b->x[b->wds++] = 1; + } + return b; + } + + void +#ifdef KR_headers +gethex(sp, rvp, rounding, sign) + CONST char **sp; U *rvp; int rounding, sign; +#else +gethex( CONST char **sp, U *rvp, int rounding, int sign) +#endif +{ + Bigint *b; + CONST unsigned char *decpt, *s0, *s, *s1; + Long e, e1; + ULong L, lostbits, *x; + int big, denorm, esign, havedig, k, n, nbits, up, zret; +#ifdef IBM + int j; +#endif + enum { +#ifdef IEEE_Arith /*{{*/ + emax = 0x7fe - Bias - P + 1, + emin = Emin - P + 1 +#else /*}{*/ + emin = Emin - P, +#ifdef VAX + emax = 0x7ff - Bias - P + 1 +#endif +#ifdef IBM + emax = 0x7f - Bias - P +#endif +#endif /*}}*/ + }; +#ifdef USE_LOCALE + int i; +#ifdef NO_LOCALE_CACHE + const unsigned char *decimalpoint = (unsigned char*) + localeconv()->decimal_point; +#else + const unsigned char *decimalpoint; + static unsigned char *decimalpoint_cache; + if (!(s0 = decimalpoint_cache)) { + s0 = (unsigned char*)localeconv()->decimal_point; + if ((decimalpoint_cache = (unsigned char*) + MALLOC(strlen((CONST char*)s0) + 1))) { + strcpy((char*)decimalpoint_cache, (CONST char*)s0); + s0 = decimalpoint_cache; + } + } + decimalpoint = s0; +#endif +#endif + + if (!hexdig['0']) + hexdig_init(); + havedig = 0; + s0 = *(CONST unsigned char **)sp + 2; + while(s0[havedig] == '0') + havedig++; + s0 += havedig; + s = s0; + decpt = 0; + zret = 0; + e = 0; + if (hexdig[*s]) + havedig++; + else { + zret = 1; +#ifdef USE_LOCALE + for(i = 0; decimalpoint[i]; ++i) { + if (s[i] != decimalpoint[i]) + goto pcheck; + } + decpt = s += i; +#else + if (*s != '.') + goto pcheck; + decpt = ++s; +#endif + if (!hexdig[*s]) + goto pcheck; + while(*s == '0') + s++; + if (hexdig[*s]) + zret = 0; + havedig = 1; + s0 = s; + } + while(hexdig[*s]) + s++; +#ifdef USE_LOCALE + if (*s == *decimalpoint && !decpt) { + for(i = 1; decimalpoint[i]; ++i) { + if (s[i] != decimalpoint[i]) + goto pcheck; + } + decpt = s += i; +#else + if (*s == '.' && !decpt) { + decpt = ++s; +#endif + while(hexdig[*s]) + s++; + }/*}*/ + if (decpt) + e = -(((Long)(s-decpt)) << 2); + pcheck: + s1 = s; + big = esign = 0; + switch(*s) { + case 'p': + case 'P': + switch(*++s) { + case '-': + esign = 1; + /* no break */ + case '+': + s++; + } + if ((n = hexdig[*s]) == 0 || n > 0x19) { + s = s1; + break; + } + e1 = n - 0x10; + while((n = hexdig[*++s]) !=0 && n <= 0x19) { + if (e1 & 0xf8000000) + big = 1; + e1 = 10*e1 + n - 0x10; + } + if (esign) + e1 = -e1; + e += e1; + } + *sp = (char*)s; + if (!havedig) + *sp = (char*)s0 - 1; + if (zret) + goto retz1; + if (big) { + if (esign) { +#ifdef IEEE_Arith + switch(rounding) { + case Round_up: + if (sign) + break; + goto ret_tiny; + case Round_down: + if (!sign) + break; + goto ret_tiny; + } +#endif + goto retz; +#ifdef IEEE_Arith + ret_tiny: +#ifndef NO_ERRNO + errno = ERANGE; +#endif + word0(rvp) = 0; + word1(rvp) = 1; + return; +#endif /* IEEE_Arith */ + } + switch(rounding) { + case Round_near: + goto ovfl1; + case Round_up: + if (!sign) + goto ovfl1; + goto ret_big; + case Round_down: + if (sign) + goto ovfl1; + goto ret_big; + } + ret_big: + word0(rvp) = Big0; + word1(rvp) = Big1; + return; + } + n = s1 - s0 - 1; + for(k = 0; n > (1 << (kshift-2)) - 1; n >>= 1) + k++; + b = Balloc(k); + x = b->x; + n = 0; + L = 0; +#ifdef USE_LOCALE + for(i = 0; decimalpoint[i+1]; ++i); +#endif + while(s1 > s0) { +#ifdef USE_LOCALE + if (*--s1 == decimalpoint[i]) { + s1 -= i; + continue; + } +#else + if (*--s1 == '.') + continue; +#endif + if (n == ULbits) { + *x++ = L; + L = 0; + n = 0; + } + L |= (hexdig[*s1] & 0x0f) << n; + n += 4; + } + *x++ = L; + b->wds = n = x - b->x; + n = ULbits*n - hi0bits(L); + nbits = Nbits; + lostbits = 0; + x = b->x; + if (n > nbits) { + n -= nbits; + if (any_on(b,n)) { + lostbits = 1; + k = n - 1; + if (x[k>>kshift] & 1 << (k & kmask)) { + lostbits = 2; + if (k > 0 && any_on(b,k)) + lostbits = 3; + } + } + rshift(b, n); + e += n; + } + else if (n < nbits) { + n = nbits - n; + b = lshift(b, n); + e -= n; + x = b->x; + } + if (e > Emax) { + ovfl: + Bfree(b); + ovfl1: +#ifndef NO_ERRNO + errno = ERANGE; +#endif + word0(rvp) = Exp_mask; + word1(rvp) = 0; + return; + } + denorm = 0; + if (e < emin) { + denorm = 1; + n = emin - e; + if (n >= nbits) { +#ifdef IEEE_Arith /*{*/ + switch (rounding) { + case Round_near: + if (n == nbits && (n < 2 || any_on(b,n-1))) + goto ret_tiny; + break; + case Round_up: + if (!sign) + goto ret_tiny; + break; + case Round_down: + if (sign) + goto ret_tiny; + } +#endif /* } IEEE_Arith */ + Bfree(b); + retz: +#ifndef NO_ERRNO + errno = ERANGE; +#endif + retz1: + rvp->d = 0.; + return; + } + k = n - 1; + if (lostbits) + lostbits = 1; + else if (k > 0) + lostbits = any_on(b,k); + if (x[k>>kshift] & 1 << (k & kmask)) + lostbits |= 2; + nbits -= n; + rshift(b,n); + e = emin; + } + if (lostbits) { + up = 0; + switch(rounding) { + case Round_zero: + break; + case Round_near: + if (lostbits & 2 + && (lostbits & 1) | (x[0] & 1)) + up = 1; + break; + case Round_up: + up = 1 - sign; + break; + case Round_down: + up = sign; + } + if (up) { + k = b->wds; + b = increment(b); + x = b->x; + if (denorm) { +#if 0 + if (nbits == Nbits - 1 + && x[nbits >> kshift] & 1 << (nbits & kmask)) + denorm = 0; /* not currently used */ +#endif + } + else if (b->wds > k + || ((n = nbits & kmask) !=0 + && hi0bits(x[k-1]) < 32-n)) { + rshift(b,1); + if (++e > Emax) + goto ovfl; + } + } + } +#ifdef IEEE_Arith + if (denorm) + word0(rvp) = b->wds > 1 ? b->x[1] & ~0x100000 : 0; + else + word0(rvp) = (b->x[1] & ~0x100000) | ((e + 0x3ff + 52) << 20); + word1(rvp) = b->x[0]; +#endif +#ifdef IBM + if ((j = e & 3)) { + k = b->x[0] & ((1 << j) - 1); + rshift(b,j); + if (k) { + switch(rounding) { + case Round_up: + if (!sign) + increment(b); + break; + case Round_down: + if (sign) + increment(b); + break; + case Round_near: + j = 1 << (j-1); + if (k & j && ((k & (j-1)) | lostbits)) + increment(b); + } + } + } + e >>= 2; + word0(rvp) = b->x[1] | ((e + 65 + 13) << 24); + word1(rvp) = b->x[0]; +#endif +#ifdef VAX + /* The next two lines ignore swap of low- and high-order 2 bytes. */ + /* word0(rvp) = (b->x[1] & ~0x800000) | ((e + 129 + 55) << 23); */ + /* word1(rvp) = b->x[0]; */ + word0(rvp) = ((b->x[1] & ~0x800000) >> 16) | ((e + 129 + 55) << 7) | (b->x[1] << 16); + word1(rvp) = (b->x[0] >> 16) | (b->x[0] << 16); +#endif + Bfree(b); + } +#endif /*}!NO_HEX_FP*/ + + static int +#ifdef KR_headers +dshift(b, p2) Bigint *b; int p2; +#else +dshift(Bigint *b, int p2) +#endif +{ + int rv = hi0bits(b->x[b->wds-1]) - 4; + if (p2 > 0) + rv -= p2; + return rv & kmask; + } + + static int +quorem +#ifdef KR_headers + (b, S) Bigint *b, *S; +#else + (Bigint *b, Bigint *S) +#endif +{ + int n; + ULong *bx, *bxe, q, *sx, *sxe; +#ifdef ULLong + ULLong borrow, carry, y, ys; +#else + ULong borrow, carry, y, ys; +#ifdef Pack_32 + ULong si, z, zs; +#endif +#endif + + n = S->wds; +#ifdef DEBUG + /*debug*/ if (b->wds > n) + /*debug*/ Bug("oversize b in quorem"); +#endif + if (b->wds < n) + return 0; + sx = S->x; + sxe = sx + --n; + bx = b->x; + bxe = bx + n; + q = *bxe / (*sxe + 1); /* ensure q <= true quotient */ +#ifdef DEBUG + /*debug*/ if (q > 9) + /*debug*/ Bug("oversized quotient in quorem"); +#endif + if (q) { + borrow = 0; + carry = 0; + do { +#ifdef ULLong + ys = *sx++ * (ULLong)q + carry; + carry = ys >> 32; + y = *bx - (ys & FFFFFFFF) - borrow; + borrow = y >> 32 & (ULong)1; + *bx++ = y & FFFFFFFF; +#else +#ifdef Pack_32 + si = *sx++; + ys = (si & 0xffff) * q + carry; + zs = (si >> 16) * q + (ys >> 16); + carry = zs >> 16; + y = (*bx & 0xffff) - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*bx >> 16) - (zs & 0xffff) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(bx, z, y); +#else + ys = *sx++ * q + carry; + carry = ys >> 16; + y = *bx - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + *bx++ = y & 0xffff; +#endif +#endif + } + while(sx <= sxe); + if (!*bxe) { + bx = b->x; + while(--bxe > bx && !*bxe) + --n; + b->wds = n; + } + } + if (cmp(b, S) >= 0) { + q++; + borrow = 0; + carry = 0; + bx = b->x; + sx = S->x; + do { +#ifdef ULLong + ys = *sx++ + carry; + carry = ys >> 32; + y = *bx - (ys & FFFFFFFF) - borrow; + borrow = y >> 32 & (ULong)1; + *bx++ = y & FFFFFFFF; +#else +#ifdef Pack_32 + si = *sx++; + ys = (si & 0xffff) + carry; + zs = (si >> 16) + (ys >> 16); + carry = zs >> 16; + y = (*bx & 0xffff) - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*bx >> 16) - (zs & 0xffff) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(bx, z, y); +#else + ys = *sx++ + carry; + carry = ys >> 16; + y = *bx - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + *bx++ = y & 0xffff; +#endif +#endif + } + while(sx <= sxe); + bx = b->x; + bxe = bx + n; + if (!*bxe) { + while(--bxe > bx && !*bxe) + --n; + b->wds = n; + } + } + return q; + } + +#ifndef NO_STRTOD_BIGCOMP + + static void +bigcomp +#ifdef KR_headers + (rv, s0, bc) + U *rv; CONST char *s0; BCinfo *bc; +#else + (U *rv, CONST char *s0, BCinfo *bc) +#endif +{ + Bigint *b, *d; + int b2, bbits, d2, dd, dig, dsign, i, j, nd, nd0, p2, p5, speccase; + + dsign = bc->dsign; + nd = bc->nd; + nd0 = bc->nd0; + p5 = nd + bc->e0 - 1; + dd = speccase = 0; +#ifndef Sudden_Underflow + if (rv->d == 0.) { /* special case: value near underflow-to-zero */ + /* threshold was rounded to zero */ + b = i2b(1); + p2 = Emin - P + 1; + bbits = 1; +#ifdef Avoid_Underflow + word0(rv) = (P+2) << Exp_shift; +#else + word1(rv) = 1; +#endif + i = 0; +#ifdef Honor_FLT_ROUNDS + if (bc->rounding == 1) +#endif + { + speccase = 1; + --p2; + dsign = 0; + goto have_i; + } + } + else +#endif + b = d2b(rv, &p2, &bbits); +#ifdef Avoid_Underflow + p2 -= bc->scale; +#endif + /* floor(log2(rv)) == bbits - 1 + p2 */ + /* Check for denormal case. */ + i = P - bbits; + if (i > (j = P - Emin - 1 + p2)) { +#ifdef Sudden_Underflow + Bfree(b); + b = i2b(1); + p2 = Emin; + i = P - 1; +#ifdef Avoid_Underflow + word0(rv) = (1 + bc->scale) << Exp_shift; +#else + word0(rv) = Exp_msk1; +#endif + word1(rv) = 0; +#else + i = j; +#endif + } +#ifdef Honor_FLT_ROUNDS + if (bc->rounding != 1) { + if (i > 0) + b = lshift(b, i); + if (dsign) + b = increment(b); + } + else +#endif + { + b = lshift(b, ++i); + b->x[0] |= 1; + } +#ifndef Sudden_Underflow + have_i: +#endif + p2 -= p5 + i; + d = i2b(1); + /* Arrange for convenient computation of quotients: + * shift left if necessary so divisor has 4 leading 0 bits. + */ + if (p5 > 0) + d = pow5mult(d, p5); + else if (p5 < 0) + b = pow5mult(b, -p5); + if (p2 > 0) { + b2 = p2; + d2 = 0; + } + else { + b2 = 0; + d2 = -p2; + } + i = dshift(d, d2); + if ((b2 += i) > 0) + b = lshift(b, b2); + if ((d2 += i) > 0) + d = lshift(d, d2); + + /* Now b/d = exactly half-way between the two floating-point values */ + /* on either side of the input string. Compute first digit of b/d. */ + + if (!(dig = quorem(b,d))) { + b = multadd(b, 10, 0); /* very unlikely */ + dig = quorem(b,d); + } + + /* Compare b/d with s0 */ + + for(i = 0; i < nd0; ) { + if ((dd = s0[i++] - '0' - dig)) + goto ret; + if (!b->x[0] && b->wds == 1) { + if (i < nd) + dd = 1; + goto ret; + } + b = multadd(b, 10, 0); + dig = quorem(b,d); + } + for(j = bc->dp1; i++ < nd;) { + if ((dd = s0[j++] - '0' - dig)) + goto ret; + if (!b->x[0] && b->wds == 1) { + if (i < nd) + dd = 1; + goto ret; + } + b = multadd(b, 10, 0); + dig = quorem(b,d); + } + if (b->x[0] || b->wds > 1) + dd = -1; + ret: + Bfree(b); + Bfree(d); +#ifdef Honor_FLT_ROUNDS + if (bc->rounding != 1) { + if (dd < 0) { + if (bc->rounding == 0) { + if (!dsign) + goto retlow1; + } + else if (dsign) + goto rethi1; + } + else if (dd > 0) { + if (bc->rounding == 0) { + if (dsign) + goto rethi1; + goto ret1; + } + if (!dsign) + goto rethi1; + dval(rv) += 2.*ulp(rv); + } + else { + bc->inexact = 0; + if (dsign) + goto rethi1; + } + } + else +#endif + if (speccase) { + if (dd <= 0) + rv->d = 0.; + } + else if (dd < 0) { + if (!dsign) /* does not happen for round-near */ +retlow1: + dval(rv) -= ulp(rv); + } + else if (dd > 0) { + if (dsign) { + rethi1: + dval(rv) += ulp(rv); + } + } + else { + /* Exact half-way case: apply round-even rule. */ + if (word1(rv) & 1) { + if (dsign) + goto rethi1; + goto retlow1; + } + } + +#ifdef Honor_FLT_ROUNDS + ret1: +#endif + return; + } +#endif /* NO_STRTOD_BIGCOMP */ + + double +strtod +#ifdef KR_headers + (s00, se) CONST char *s00; char **se; +#else + (CONST char *s00, char **se) +#endif +{ + int bb2, bb5, bbe, bd2, bd5, bbbits, bs2, c, e, e1; + int esign, i, j, k, nd, nd0, nf, nz, nz0, sign; + CONST char *s, *s0, *s1; + double aadj, aadj1; + Long L; + U aadj2, adj, rv, rv0; + ULong y, z; + BCinfo bc; + Bigint *bb, *bb1, *bd, *bd0, *bs, *delta; +#ifdef SET_INEXACT + int oldinexact; +#endif +#ifdef Honor_FLT_ROUNDS /*{*/ +#ifdef Trust_FLT_ROUNDS /*{{ only define this if FLT_ROUNDS really works! */ + bc.rounding = Flt_Rounds; +#else /*}{*/ + bc.rounding = 1; + switch(fegetround()) { + case FE_TOWARDZERO: bc.rounding = 0; break; + case FE_UPWARD: bc.rounding = 2; break; + case FE_DOWNWARD: bc.rounding = 3; + } +#endif /*}}*/ +#endif /*}*/ +#ifdef USE_LOCALE + CONST char *s2; +#endif + + sign = nz0 = nz = bc.dplen = bc.uflchk = 0; + dval(&rv) = 0.; + for(s = s00;;s++) switch(*s) { + case '-': + sign = 1; + /* no break */ + case '+': + if (*++s) + goto break2; + /* no break */ + case 0: + goto ret0; + case '\t': + case '\n': + case '\v': + case '\f': + case '\r': + case ' ': + continue; + default: + goto break2; + } + break2: + if (*s == '0') { +#ifndef NO_HEX_FP /*{*/ + switch(s[1]) { + case 'x': + case 'X': +#ifdef Honor_FLT_ROUNDS + gethex(&s, &rv, bc.rounding, sign); +#else + gethex(&s, &rv, 1, sign); +#endif + goto ret; + } +#endif /*}*/ + nz0 = 1; + while(*++s == '0') ; + if (!*s) + goto ret; + } + s0 = s; + y = z = 0; + for(nd = nf = 0; (c = *s) >= '0' && c <= '9'; nd++, s++) + if (nd < 9) + y = 10*y + c - '0'; + else if (nd < 16) + z = 10*z + c - '0'; + nd0 = nd; + bc.dp0 = bc.dp1 = s - s0; +#ifdef USE_LOCALE + s1 = localeconv()->decimal_point; + if (c == *s1) { + c = '.'; + if (*++s1) { + s2 = s; + for(;;) { + if (*++s2 != *s1) { + c = 0; + break; + } + if (!*++s1) { + s = s2; + break; + } + } + } + } +#endif + if (c == '.') { + c = *++s; + bc.dp1 = s - s0; + bc.dplen = bc.dp1 - bc.dp0; + if (!nd) { + for(; c == '0'; c = *++s) + nz++; + if (c > '0' && c <= '9') { + s0 = s; + nf += nz; + nz = 0; + goto have_dig; + } + goto dig_done; + } + for(; c >= '0' && c <= '9'; c = *++s) { + have_dig: + nz++; + if (c -= '0') { + nf += nz; + for(i = 1; i < nz; i++) + if (nd++ < 9) + y *= 10; + else if (nd <= DBL_DIG + 1) + z *= 10; + if (nd++ < 9) + y = 10*y + c; + else if (nd <= DBL_DIG + 1) + z = 10*z + c; + nz = 0; + } + } + } + dig_done: + e = 0; + if (c == 'e' || c == 'E') { + if (!nd && !nz && !nz0) { + goto ret0; + } + s00 = s; + esign = 0; + switch(c = *++s) { + case '-': + esign = 1; + case '+': + c = *++s; + } + if (c >= '0' && c <= '9') { + while(c == '0') + c = *++s; + if (c > '0' && c <= '9') { + L = c - '0'; + s1 = s; + while((c = *++s) >= '0' && c <= '9') + L = 10*L + c - '0'; + if (s - s1 > 8 || L > 19999) + /* Avoid confusion from exponents + * so large that e might overflow. + */ + e = 19999; /* safe for 16 bit ints */ + else + e = (int)L; + if (esign) + e = -e; + } + else + e = 0; + } + else + s = s00; + } + if (!nd) { + if (!nz && !nz0) { +#ifdef INFNAN_CHECK + /* Check for Nan and Infinity */ + if (!bc.dplen) + switch(c) { + case 'i': + case 'I': + if (match(&s,"nf")) { + --s; + if (!match(&s,"inity")) + ++s; + word0(&rv) = 0x7ff00000; + word1(&rv) = 0; + goto ret; + } + break; + case 'n': + case 'N': + if (match(&s, "an")) { + word0(&rv) = NAN_WORD0; + word1(&rv) = NAN_WORD1; +#ifndef No_Hex_NaN + if (*s == '(') /*)*/ + hexnan(&rv, &s); +#endif + goto ret; + } + } +#endif /* INFNAN_CHECK */ + ret0: + s = s00; + sign = 0; + } + goto ret; + } + bc.e0 = e1 = e -= nf; + + /* Now we have nd0 digits, starting at s0, followed by a + * decimal point, followed by nd-nd0 digits. The number we're + * after is the integer represented by those digits times + * 10**e */ + + if (!nd0) + nd0 = nd; + k = nd < DBL_DIG + 1 ? nd : DBL_DIG + 1; + dval(&rv) = y; + if (k > 9) { +#ifdef SET_INEXACT + if (k > DBL_DIG) + oldinexact = get_inexact(); +#endif + dval(&rv) = tens[k - 9] * dval(&rv) + z; + } + bd0 = 0; + if (nd <= DBL_DIG +#ifndef RND_PRODQUOT +#ifndef Honor_FLT_ROUNDS + && Flt_Rounds == 1 +#endif +#endif + ) { + if (!e) + goto ret; + if (e > 0) { + if (e <= Ten_pmax) { +#ifdef VAX + goto vax_ovfl_check; +#else +#ifdef Honor_FLT_ROUNDS + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + rv.d = -rv.d; + sign = 0; + } +#endif + /* rv = */ rounded_product(dval(&rv), tens[e]); + goto ret; +#endif + } + i = DBL_DIG - nd; + if (e <= Ten_pmax + i) { + /* A fancier test would sometimes let us do + * this for larger i values. + */ +#ifdef Honor_FLT_ROUNDS + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + rv.d = -rv.d; + sign = 0; + } +#endif + e -= i; + dval(&rv) *= tens[i]; +#ifdef VAX + /* VAX exponent range is so narrow we must + * worry about overflow here... + */ + vax_ovfl_check: + word0(&rv) -= P*Exp_msk1; + /* rv = */ rounded_product(dval(&rv), tens[e]); + if ((word0(&rv) & Exp_mask) + > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) + goto ovfl; + word0(&rv) += P*Exp_msk1; +#else + /* rv = */ rounded_product(dval(&rv), tens[e]); +#endif + goto ret; + } + } +#ifndef Inaccurate_Divide + else if (e >= -Ten_pmax) { +#ifdef Honor_FLT_ROUNDS + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + rv.d = -rv.d; + sign = 0; + } +#endif + /* rv = */ rounded_quotient(dval(&rv), tens[-e]); + goto ret; + } +#endif + } + e1 += nd - k; + +#ifdef IEEE_Arith +#ifdef SET_INEXACT + bc.inexact = 1; + if (k <= DBL_DIG) + oldinexact = get_inexact(); +#endif +#ifdef Avoid_Underflow + bc.scale = 0; +#endif +#ifdef Honor_FLT_ROUNDS + if (bc.rounding >= 2) { + if (sign) + bc.rounding = bc.rounding == 2 ? 0 : 2; + else + if (bc.rounding != 2) + bc.rounding = 0; + } +#endif +#endif /*IEEE_Arith*/ + + /* Get starting approximation = rv * 10**e1 */ + + if (e1 > 0) { + if ((i = e1 & 15)) + dval(&rv) *= tens[i]; + if (e1 &= ~15) { + if (e1 > DBL_MAX_10_EXP) { + ovfl: +#ifndef NO_ERRNO + errno = ERANGE; +#endif + /* Can't trust HUGE_VAL */ +#ifdef IEEE_Arith +#ifdef Honor_FLT_ROUNDS + switch(bc.rounding) { + case 0: /* toward 0 */ + case 3: /* toward -infinity */ + word0(&rv) = Big0; + word1(&rv) = Big1; + break; + default: + word0(&rv) = Exp_mask; + word1(&rv) = 0; + } +#else /*Honor_FLT_ROUNDS*/ + word0(&rv) = Exp_mask; + word1(&rv) = 0; +#endif /*Honor_FLT_ROUNDS*/ +#ifdef SET_INEXACT + /* set overflow bit */ + dval(&rv0) = 1e300; + dval(&rv0) *= dval(&rv0); +#endif +#else /*IEEE_Arith*/ + word0(&rv) = Big0; + word1(&rv) = Big1; +#endif /*IEEE_Arith*/ + goto ret; + } + e1 >>= 4; + for(j = 0; e1 > 1; j++, e1 >>= 1) + if (e1 & 1) + dval(&rv) *= bigtens[j]; + /* The last multiplication could overflow. */ + word0(&rv) -= P*Exp_msk1; + dval(&rv) *= bigtens[j]; + if ((z = word0(&rv) & Exp_mask) + > Exp_msk1*(DBL_MAX_EXP+Bias-P)) + goto ovfl; + if (z > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) { + /* set to largest number */ + /* (Can't trust DBL_MAX) */ + word0(&rv) = Big0; + word1(&rv) = Big1; + } + else + word0(&rv) += P*Exp_msk1; + } + } + else if (e1 < 0) { + e1 = -e1; + if ((i = e1 & 15)) + dval(&rv) /= tens[i]; + if (e1 >>= 4) { + if (e1 >= 1 << n_bigtens) + goto undfl; +#ifdef Avoid_Underflow + if (e1 & Scale_Bit) + bc.scale = 2*P; + for(j = 0; e1 > 0; j++, e1 >>= 1) + if (e1 & 1) + dval(&rv) *= tinytens[j]; + if (bc.scale && (j = 2*P + 1 - ((word0(&rv) & Exp_mask) + >> Exp_shift)) > 0) { + /* scaled rv is denormal; clear j low bits */ + if (j >= 32) { + word1(&rv) = 0; + if (j >= 53) + word0(&rv) = (P+2)*Exp_msk1; + else + word0(&rv) &= 0xffffffff << (j-32); + } + else + word1(&rv) &= 0xffffffff << j; + } +#else + for(j = 0; e1 > 1; j++, e1 >>= 1) + if (e1 & 1) + dval(&rv) *= tinytens[j]; + /* The last multiplication could underflow. */ + dval(&rv0) = dval(&rv); + dval(&rv) *= tinytens[j]; + if (!dval(&rv)) { + dval(&rv) = 2.*dval(&rv0); + dval(&rv) *= tinytens[j]; +#endif + if (!dval(&rv)) { + undfl: + dval(&rv) = 0.; +#ifndef NO_ERRNO + errno = ERANGE; +#endif + goto ret; + } +#ifndef Avoid_Underflow + word0(&rv) = Tiny0; + word1(&rv) = Tiny1; + /* The refinement below will clean + * this approximation up. + */ + } +#endif + } + } + + /* Now the hard part -- adjusting rv to the correct value.*/ + + /* Put digits into bd: true value = bd * 10^e */ + + bc.nd = nd; +#ifndef NO_STRTOD_BIGCOMP + bc.nd0 = nd0; /* Only needed if nd > strtod_diglim, but done here */ + /* to silence an erroneous warning about bc.nd0 */ + /* possibly not being initialized. */ + if (nd > strtod_diglim) { + /* ASSERT(strtod_diglim >= 18); 18 == one more than the */ + /* minimum number of decimal digits to distinguish double values */ + /* in IEEE arithmetic. */ + i = j = 18; + if (i > nd0) + j += bc.dplen; + for(;;) { + if (--j <= bc.dp1 && j >= bc.dp0) + j = bc.dp0 - 1; + if (s0[j] != '0') + break; + --i; + } + e += nd - i; + nd = i; + if (nd0 > nd) + nd0 = nd; + if (nd < 9) { /* must recompute y */ + y = 0; + for(i = 0; i < nd0; ++i) + y = 10*y + s0[i] - '0'; + for(j = bc.dp1; i < nd; ++i) + y = 10*y + s0[j++] - '0'; + } + } +#endif + bd0 = s2b(s0, nd0, nd, y, bc.dplen); + + for(;;) { + bd = Balloc(bd0->k); + Bcopy(bd, bd0); + bb = d2b(&rv, &bbe, &bbbits); /* rv = bb * 2^bbe */ + bs = i2b(1); + + if (e >= 0) { + bb2 = bb5 = 0; + bd2 = bd5 = e; + } + else { + bb2 = bb5 = -e; + bd2 = bd5 = 0; + } + if (bbe >= 0) + bb2 += bbe; + else + bd2 -= bbe; + bs2 = bb2; +#ifdef Honor_FLT_ROUNDS + if (bc.rounding != 1) + bs2++; +#endif +#ifdef Avoid_Underflow + j = bbe - bc.scale; + i = j + bbbits - 1; /* logb(rv) */ + if (i < Emin) /* denormal */ + j += P - Emin; + else + j = P + 1 - bbbits; +#else /*Avoid_Underflow*/ +#ifdef Sudden_Underflow +#ifdef IBM + j = 1 + 4*P - 3 - bbbits + ((bbe + bbbits - 1) & 3); +#else + j = P + 1 - bbbits; +#endif +#else /*Sudden_Underflow*/ + j = bbe; + i = j + bbbits - 1; /* logb(rv) */ + if (i < Emin) /* denormal */ + j += P - Emin; + else + j = P + 1 - bbbits; +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + bb2 += j; + bd2 += j; +#ifdef Avoid_Underflow + bd2 += bc.scale; +#endif + i = bb2 < bd2 ? bb2 : bd2; + if (i > bs2) + i = bs2; + if (i > 0) { + bb2 -= i; + bd2 -= i; + bs2 -= i; + } + if (bb5 > 0) { + bs = pow5mult(bs, bb5); + bb1 = mult(bs, bb); + Bfree(bb); + bb = bb1; + } + if (bb2 > 0) + bb = lshift(bb, bb2); + if (bd5 > 0) + bd = pow5mult(bd, bd5); + if (bd2 > 0) + bd = lshift(bd, bd2); + if (bs2 > 0) + bs = lshift(bs, bs2); + delta = diff(bb, bd); + bc.dsign = delta->sign; + delta->sign = 0; + i = cmp(delta, bs); +#ifndef NO_STRTOD_BIGCOMP + if (bc.nd > nd && i <= 0) { + if (bc.dsign) + break; /* Must use bigcomp(). */ +#ifdef Honor_FLT_ROUNDS + if (bc.rounding != 1) { + if (i < 0) + break; + } + else +#endif + { + bc.nd = nd; + i = -1; /* Discarded digits make delta smaller. */ + } + } +#endif +#ifdef Honor_FLT_ROUNDS + if (bc.rounding != 1) { + if (i < 0) { + /* Error is less than an ulp */ + if (!delta->x[0] && delta->wds <= 1) { + /* exact */ +#ifdef SET_INEXACT + bc.inexact = 0; +#endif + break; + } + if (bc.rounding) { + if (bc.dsign) { + adj.d = 1.; + goto apply_adj; + } + } + else if (!bc.dsign) { + adj.d = -1.; + if (!word1(&rv) + && !(word0(&rv) & Frac_mask)) { + y = word0(&rv) & Exp_mask; +#ifdef Avoid_Underflow + if (!bc.scale || y > 2*P*Exp_msk1) +#else + if (y) +#endif + { + delta = lshift(delta,Log2P); + if (cmp(delta, bs) <= 0) + adj.d = -0.5; + } + } + apply_adj: +#ifdef Avoid_Underflow + if (bc.scale && (y = word0(&rv) & Exp_mask) + <= 2*P*Exp_msk1) + word0(&adj) += (2*P+1)*Exp_msk1 - y; +#else +#ifdef Sudden_Underflow + if ((word0(&rv) & Exp_mask) <= + P*Exp_msk1) { + word0(&rv) += P*Exp_msk1; + dval(&rv) += adj.d*ulp(dval(&rv)); + word0(&rv) -= P*Exp_msk1; + } + else +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + dval(&rv) += adj.d*ulp(&rv); + } + break; + } + adj.d = ratio(delta, bs); + if (adj.d < 1.) + adj.d = 1.; + if (adj.d <= 0x7ffffffe) { + /* adj = rounding ? ceil(adj) : floor(adj); */ + y = adj.d; + if (y != adj.d) { + if (!((bc.rounding>>1) ^ bc.dsign)) + y++; + adj.d = y; + } + } +#ifdef Avoid_Underflow + if (bc.scale && (y = word0(&rv) & Exp_mask) <= 2*P*Exp_msk1) + word0(&adj) += (2*P+1)*Exp_msk1 - y; +#else +#ifdef Sudden_Underflow + if ((word0(&rv) & Exp_mask) <= P*Exp_msk1) { + word0(&rv) += P*Exp_msk1; + adj.d *= ulp(dval(&rv)); + if (bc.dsign) + dval(&rv) += adj.d; + else + dval(&rv) -= adj.d; + word0(&rv) -= P*Exp_msk1; + goto cont; + } +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + adj.d *= ulp(&rv); + if (bc.dsign) { + if (word0(&rv) == Big0 && word1(&rv) == Big1) + goto ovfl; + dval(&rv) += adj.d; + } + else + dval(&rv) -= adj.d; + goto cont; + } +#endif /*Honor_FLT_ROUNDS*/ + + if (i < 0) { + /* Error is less than half an ulp -- check for + * special case of mantissa a power of two. + */ + if (bc.dsign || word1(&rv) || word0(&rv) & Bndry_mask +#ifdef IEEE_Arith +#ifdef Avoid_Underflow + || (word0(&rv) & Exp_mask) <= (2*P+1)*Exp_msk1 +#else + || (word0(&rv) & Exp_mask) <= Exp_msk1 +#endif +#endif + ) { +#ifdef SET_INEXACT + if (!delta->x[0] && delta->wds <= 1) + bc.inexact = 0; +#endif + break; + } + if (!delta->x[0] && delta->wds <= 1) { + /* exact result */ +#ifdef SET_INEXACT + bc.inexact = 0; +#endif + break; + } + delta = lshift(delta,Log2P); + if (cmp(delta, bs) > 0) + goto drop_down; + break; + } + if (i == 0) { + /* exactly half-way between */ + if (bc.dsign) { + if ((word0(&rv) & Bndry_mask1) == Bndry_mask1 + && word1(&rv) == ( +#ifdef Avoid_Underflow + (bc.scale && (y = word0(&rv) & Exp_mask) <= 2*P*Exp_msk1) + ? (0xffffffff & (0xffffffff << (2*P+1-(y>>Exp_shift)))) : +#endif + 0xffffffff)) { + /*boundary case -- increment exponent*/ + word0(&rv) = (word0(&rv) & Exp_mask) + + Exp_msk1 +#ifdef IBM + | Exp_msk1 >> 4 +#endif + ; + word1(&rv) = 0; +#ifdef Avoid_Underflow + bc.dsign = 0; +#endif + break; + } + } + else if (!(word0(&rv) & Bndry_mask) && !word1(&rv)) { + drop_down: + /* boundary case -- decrement exponent */ +#ifdef Sudden_Underflow /*{{*/ + L = word0(&rv) & Exp_mask; +#ifdef IBM + if (L < Exp_msk1) +#else +#ifdef Avoid_Underflow + if (L <= (bc.scale ? (2*P+1)*Exp_msk1 : Exp_msk1)) +#else + if (L <= Exp_msk1) +#endif /*Avoid_Underflow*/ +#endif /*IBM*/ + { + if (bc.nd >nd) { + bc.uflchk = 1; + break; + } + goto undfl; + } + L -= Exp_msk1; +#else /*Sudden_Underflow}{*/ +#ifdef Avoid_Underflow + if (bc.scale) { + L = word0(&rv) & Exp_mask; + if (L <= (2*P+1)*Exp_msk1) { + if (L > (P+2)*Exp_msk1) + /* round even ==> */ + /* accept rv */ + break; + /* rv = smallest denormal */ + if (bc.nd >nd) { + bc.uflchk = 1; + break; + } + goto undfl; + } + } +#endif /*Avoid_Underflow*/ + L = (word0(&rv) & Exp_mask) - Exp_msk1; +#endif /*Sudden_Underflow}}*/ + word0(&rv) = L | Bndry_mask1; + word1(&rv) = 0xffffffff; +#ifdef IBM + goto cont; +#else + break; +#endif + } +#ifndef ROUND_BIASED + if (!(word1(&rv) & LSB)) + break; +#endif + if (bc.dsign) + dval(&rv) += ulp(&rv); +#ifndef ROUND_BIASED + else { + dval(&rv) -= ulp(&rv); +#ifndef Sudden_Underflow + if (!dval(&rv)) { + if (bc.nd >nd) { + bc.uflchk = 1; + break; + } + goto undfl; + } +#endif + } +#ifdef Avoid_Underflow + bc.dsign = 1 - bc.dsign; +#endif +#endif + break; + } + if ((aadj = ratio(delta, bs)) <= 2.) { + if (bc.dsign) + aadj = aadj1 = 1.; + else if (word1(&rv) || word0(&rv) & Bndry_mask) { +#ifndef Sudden_Underflow + if (word1(&rv) == Tiny1 && !word0(&rv)) { + if (bc.nd >nd) { + bc.uflchk = 1; + break; + } + goto undfl; + } +#endif + aadj = 1.; + aadj1 = -1.; + } + else { + /* special case -- power of FLT_RADIX to be */ + /* rounded down... */ + + if (aadj < 2./FLT_RADIX) + aadj = 1./FLT_RADIX; + else + aadj *= 0.5; + aadj1 = -aadj; + } + } + else { + aadj *= 0.5; + aadj1 = bc.dsign ? aadj : -aadj; +#ifdef Check_FLT_ROUNDS + switch(bc.rounding) { + case 2: /* towards +infinity */ + aadj1 -= 0.5; + break; + case 0: /* towards 0 */ + case 3: /* towards -infinity */ + aadj1 += 0.5; + } +#else + if (Flt_Rounds == 0) + aadj1 += 0.5; +#endif /*Check_FLT_ROUNDS*/ + } + y = word0(&rv) & Exp_mask; + + /* Check for overflow */ + + if (y == Exp_msk1*(DBL_MAX_EXP+Bias-1)) { + dval(&rv0) = dval(&rv); + word0(&rv) -= P*Exp_msk1; + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; + if ((word0(&rv) & Exp_mask) >= + Exp_msk1*(DBL_MAX_EXP+Bias-P)) { + if (word0(&rv0) == Big0 && word1(&rv0) == Big1) + goto ovfl; + word0(&rv) = Big0; + word1(&rv) = Big1; + goto cont; + } + else + word0(&rv) += P*Exp_msk1; + } + else { +#ifdef Avoid_Underflow + if (bc.scale && y <= 2*P*Exp_msk1) { + if (aadj <= 0x7fffffff) { + if ((z = aadj) <= 0) + z = 1; + aadj = z; + aadj1 = bc.dsign ? aadj : -aadj; + } + dval(&aadj2) = aadj1; + word0(&aadj2) += (2*P+1)*Exp_msk1 - y; + aadj1 = dval(&aadj2); + } + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; +#else +#ifdef Sudden_Underflow + if ((word0(&rv) & Exp_mask) <= P*Exp_msk1) { + dval(&rv0) = dval(&rv); + word0(&rv) += P*Exp_msk1; + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; +#ifdef IBM + if ((word0(&rv) & Exp_mask) < P*Exp_msk1) +#else + if ((word0(&rv) & Exp_mask) <= P*Exp_msk1) +#endif + { + if (word0(&rv0) == Tiny0 + && word1(&rv0) == Tiny1) { + if (bc.nd >nd) { + bc.uflchk = 1; + break; + } + goto undfl; + } + word0(&rv) = Tiny0; + word1(&rv) = Tiny1; + goto cont; + } + else + word0(&rv) -= P*Exp_msk1; + } + else { + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; + } +#else /*Sudden_Underflow*/ + /* Compute adj so that the IEEE rounding rules will + * correctly round rv + adj in some half-way cases. + * If rv * ulp(rv) is denormalized (i.e., + * y <= (P-1)*Exp_msk1), we must adjust aadj to avoid + * trouble from bits lost to denormalization; + * example: 1.2e-307 . + */ + if (y <= (P-1)*Exp_msk1 && aadj > 1.) { + aadj1 = (double)(int)(aadj + 0.5); + if (!bc.dsign) + aadj1 = -aadj1; + } + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + } + z = word0(&rv) & Exp_mask; +#ifndef SET_INEXACT + if (bc.nd == nd) { +#ifdef Avoid_Underflow + if (!bc.scale) +#endif + if (y == z) { + /* Can we stop now? */ + L = (Long)aadj; + aadj -= L; + /* The tolerances below are conservative. */ + if (bc.dsign || word1(&rv) || word0(&rv) & Bndry_mask) { + if (aadj < .4999999 || aadj > .5000001) + break; + } + else if (aadj < .4999999/FLT_RADIX) + break; + } + } +#endif + cont: + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(delta); + } + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(bd0); + Bfree(delta); +#ifndef NO_STRTOD_BIGCOMP + if (bc.nd > nd) + bigcomp(&rv, s0, &bc); +#endif +#ifdef SET_INEXACT + if (bc.inexact) { + if (!oldinexact) { + word0(&rv0) = Exp_1 + (70 << Exp_shift); + word1(&rv0) = 0; + dval(&rv0) += 1.; + } + } + else if (!oldinexact) + clear_inexact(); +#endif +#ifdef Avoid_Underflow + if (bc.scale) { + word0(&rv0) = Exp_1 - 2*P*Exp_msk1; + word1(&rv0) = 0; + dval(&rv) *= dval(&rv0); +#ifndef NO_ERRNO + /* try to avoid the bug of testing an 8087 register value */ +#ifdef IEEE_Arith + if (!(word0(&rv) & Exp_mask)) +#else + if (word0(&rv) == 0 && word1(&rv) == 0) +#endif + errno = ERANGE; +#endif + } +#endif /* Avoid_Underflow */ +#ifdef SET_INEXACT + if (bc.inexact && !(word0(&rv) & Exp_mask)) { + /* set underflow bit */ + dval(&rv0) = 1e-300; + dval(&rv0) *= dval(&rv0); + } +#endif + ret: + if (se) + *se = (char *)s; + return sign ? -dval(&rv) : dval(&rv); + } + +#ifndef MULTIPLE_THREADS + static char *dtoa_result; +#endif + + static char * +#ifdef KR_headers +rv_alloc(i) int i; +#else +rv_alloc(int i) +#endif +{ + int j, k, *r; + + j = sizeof(ULong); + for(k = 0; + sizeof(Bigint) - sizeof(ULong) - sizeof(int) + j <= (size_t)i; + j <<= 1) + k++; + r = (int*)Balloc(k); + *r = k; + return +#ifndef MULTIPLE_THREADS + dtoa_result = +#endif + (char *)(r+1); + } + + static char * +#ifdef KR_headers +nrv_alloc(s, rve, n) char *s, **rve; int n; +#else +nrv_alloc(CONST char *s, char **rve, int n) +#endif +{ + char *rv, *t; + + t = rv = rv_alloc(n); + while((*t = *s++)) t++; + if (rve) + *rve = t; + return rv; + } + +/* freedtoa(s) must be used to free values s returned by dtoa + * when MULTIPLE_THREADS is #defined. It should be used in all cases, + * but for consistency with earlier versions of dtoa, it is optional + * when MULTIPLE_THREADS is not defined. + */ + + void +#ifdef KR_headers +freedtoa(s) char *s; +#else +freedtoa(char *s) +#endif +{ + Bigint *b = (Bigint *)((int *)s - 1); + b->maxwds = 1 << (b->k = *(int*)b); + Bfree(b); +#ifndef MULTIPLE_THREADS + if (s == dtoa_result) + dtoa_result = 0; +#endif + } + +/* dtoa for IEEE arithmetic (dmg): convert double to ASCII string. + * + * Inspired by "How to Print Floating-Point Numbers Accurately" by + * Guy L. Steele, Jr. and Jon L. White [Proc. ACM SIGPLAN '90, pp. 112-126]. + * + * Modifications: + * 1. Rather than iterating, we use a simple numeric overestimate + * to determine k = floor(log10(d)). We scale relevant + * quantities using O(log2(k)) rather than O(k) multiplications. + * 2. For some modes > 2 (corresponding to ecvt and fcvt), we don't + * try to generate digits strictly left to right. Instead, we + * compute with fewer bits and propagate the carry if necessary + * when rounding the final digit up. This is often faster. + * 3. Under the assumption that input will be rounded nearest, + * mode 0 renders 1e23 as 1e23 rather than 9.999999999999999e22. + * That is, we allow equality in stopping tests when the + * round-nearest rule will give the same floating-point value + * as would satisfaction of the stopping test with strict + * inequality. + * 4. We remove common factors of powers of 2 from relevant + * quantities. + * 5. When converting floating-point integers less than 1e16, + * we use floating-point arithmetic rather than resorting + * to multiple-precision integers. + * 6. When asked to produce fewer than 15 digits, we first try + * to get by with floating-point arithmetic; we resort to + * multiple-precision integer arithmetic only if we cannot + * guarantee that the floating-point calculation has given + * the correctly rounded result. For k requested digits and + * "uniformly" distributed input, the probability is + * something like 10^(k-15) that we must resort to the Long + * calculation. + */ + + char * +dtoa +#ifdef KR_headers + (dd, mode, ndigits, decpt, sign, rve) + double dd; int mode, ndigits, *decpt, *sign; char **rve; +#else + (double dd, int mode, int ndigits, int *decpt, int *sign, char **rve) +#endif +{ + /* Arguments ndigits, decpt, sign are similar to those + of ecvt and fcvt; trailing zeros are suppressed from + the returned string. If not null, *rve is set to point + to the end of the return value. If d is +-Infinity or NaN, + then *decpt is set to 9999. + + mode: + 0 ==> shortest string that yields d when read in + and rounded to nearest. + 1 ==> like 0, but with Steele & White stopping rule; + e.g. with IEEE P754 arithmetic , mode 0 gives + 1e23 whereas mode 1 gives 9.999999999999999e22. + 2 ==> max(1,ndigits) significant digits. This gives a + return value similar to that of ecvt, except + that trailing zeros are suppressed. + 3 ==> through ndigits past the decimal point. This + gives a return value similar to that from fcvt, + except that trailing zeros are suppressed, and + ndigits can be negative. + 4,5 ==> similar to 2 and 3, respectively, but (in + round-nearest mode) with the tests of mode 0 to + possibly return a shorter string that rounds to d. + With IEEE arithmetic and compilation with + -DHonor_FLT_ROUNDS, modes 4 and 5 behave the same + as modes 2 and 3 when FLT_ROUNDS != 1. + 6-9 ==> Debugging modes similar to mode - 4: don't try + fast floating-point estimate (if applicable). + + Values of mode other than 0-9 are treated as mode 0. + + Sufficient space is allocated to the return value + to hold the suppressed trailing zeros. + */ + + int bbits, b2, b5, be, dig, i, ieps, ilim, ilim0, ilim1, + j, j1, k, k0, k_check, leftright, m2, m5, s2, s5, + spec_case, try_quick; + Long L; +#ifndef Sudden_Underflow + int denorm; + ULong x; +#endif + Bigint *b, *b1, *delta, *mlo = NULL, *mhi, *S; + U d2, eps, u; + double ds; + char *s, *s0; +#ifdef SET_INEXACT + int inexact, oldinexact; +#endif +#ifdef Honor_FLT_ROUNDS /*{*/ + int Rounding; +#ifdef Trust_FLT_ROUNDS /*{{ only define this if FLT_ROUNDS really works! */ + Rounding = Flt_Rounds; +#else /*}{*/ + Rounding = 1; + switch(fegetround()) { + case FE_TOWARDZERO: Rounding = 0; break; + case FE_UPWARD: Rounding = 2; break; + case FE_DOWNWARD: Rounding = 3; + } +#endif /*}}*/ +#endif /*}*/ + +#ifndef MULTIPLE_THREADS + if (dtoa_result) { + freedtoa(dtoa_result); + dtoa_result = 0; + } +#endif + + u.d = dd; + if (word0(&u) & Sign_bit) { + /* set sign for everything, including 0's and NaNs */ + *sign = 1; + word0(&u) &= ~Sign_bit; /* clear sign bit */ + } + else + *sign = 0; + +#if defined(IEEE_Arith) + defined(VAX) +#ifdef IEEE_Arith + if ((word0(&u) & Exp_mask) == Exp_mask) +#else + if (word0(&u) == 0x8000) +#endif + { + /* Infinity or NaN */ + *decpt = 9999; +#ifdef IEEE_Arith + if (!word1(&u) && !(word0(&u) & 0xfffff)) + return nrv_alloc("Infinity", rve, 8); +#endif + return nrv_alloc("NaN", rve, 3); + } +#endif +#ifdef IBM + dval(&u) += 0; /* normalize */ +#endif + if (!dval(&u)) { + *decpt = 1; + return nrv_alloc("0", rve, 1); + } + +#ifdef SET_INEXACT + try_quick = oldinexact = get_inexact(); + inexact = 1; +#endif +#ifdef Honor_FLT_ROUNDS + if (Rounding >= 2) { + if (*sign) + Rounding = Rounding == 2 ? 0 : 2; + else + if (Rounding != 2) + Rounding = 0; + } +#endif + + b = d2b(&u, &be, &bbits); +#ifdef Sudden_Underflow + i = (int)(word0(&u) >> Exp_shift1 & (Exp_mask>>Exp_shift1)); +#else + if ((i = (int)(word0(&u) >> Exp_shift1 & (Exp_mask>>Exp_shift1)))) { +#endif + dval(&d2) = dval(&u); + word0(&d2) &= Frac_mask1; + word0(&d2) |= Exp_11; +#ifdef IBM + if (j = 11 - hi0bits(word0(&d2) & Frac_mask)) + dval(&d2) /= 1 << j; +#endif + + /* log(x) ~=~ log(1.5) + (x-1.5)/1.5 + * log10(x) = log(x) / log(10) + * ~=~ log(1.5)/log(10) + (x-1.5)/(1.5*log(10)) + * log10(d) = (i-Bias)*log(2)/log(10) + log10(d2) + * + * This suggests computing an approximation k to log10(d) by + * + * k = (i - Bias)*0.301029995663981 + * + ( (d2-1.5)*0.289529654602168 + 0.176091259055681 ); + * + * We want k to be too large rather than too small. + * The error in the first-order Taylor series approximation + * is in our favor, so we just round up the constant enough + * to compensate for any error in the multiplication of + * (i - Bias) by 0.301029995663981; since |i - Bias| <= 1077, + * and 1077 * 0.30103 * 2^-52 ~=~ 7.2e-14, + * adding 1e-13 to the constant term more than suffices. + * Hence we adjust the constant term to 0.1760912590558. + * (We could get a more accurate k by invoking log10, + * but this is probably not worthwhile.) + */ + + i -= Bias; +#ifdef IBM + i <<= 2; + i += j; +#endif +#ifndef Sudden_Underflow + denorm = 0; + } + else { + /* d is denormalized */ + + i = bbits + be + (Bias + (P-1) - 1); + x = i > 32 ? word0(&u) << (64 - i) | word1(&u) >> (i - 32) + : word1(&u) << (32 - i); + dval(&d2) = x; + word0(&d2) -= 31*Exp_msk1; /* adjust exponent */ + i -= (Bias + (P-1) - 1) + 1; + denorm = 1; + } +#endif + ds = (dval(&d2)-1.5)*0.289529654602168 + 0.1760912590558 + i*0.301029995663981; + k = (int)ds; + if (ds < 0. && ds != k) + k--; /* want k = floor(ds) */ + k_check = 1; + if (k >= 0 && k <= Ten_pmax) { + if (dval(&u) < tens[k]) + k--; + k_check = 0; + } + j = bbits - i - 1; + if (j >= 0) { + b2 = 0; + s2 = j; + } + else { + b2 = -j; + s2 = 0; + } + if (k >= 0) { + b5 = 0; + s5 = k; + s2 += k; + } + else { + b2 -= k; + b5 = -k; + s5 = 0; + } + if (mode < 0 || mode > 9) + mode = 0; + +#ifndef SET_INEXACT +#ifdef Check_FLT_ROUNDS + try_quick = Rounding == 1; +#else + try_quick = 1; +#endif +#endif /*SET_INEXACT*/ + + if (mode > 5) { + mode -= 4; + try_quick = 0; + } + leftright = 1; + ilim = ilim1 = -1; /* Values for cases 0 and 1; done here to */ + /* silence erroneous "gcc -Wall" warning. */ + switch(mode) { + case 0: + case 1: + i = 18; + ndigits = 0; + break; + case 2: + leftright = 0; + /* no break */ + case 4: + if (ndigits <= 0) + ndigits = 1; + ilim = ilim1 = i = ndigits; + break; + case 3: + leftright = 0; + /* no break */ + case 5: + i = ndigits + k + 1; + ilim = i; + ilim1 = i - 1; + if (i <= 0) + i = 1; + } + s = s0 = rv_alloc(i); + +#ifdef Honor_FLT_ROUNDS + if (mode > 1 && Rounding != 1) + leftright = 0; +#endif + + if (ilim >= 0 && ilim <= Quick_max && try_quick) { + + /* Try to get by with floating-point arithmetic. */ + + i = 0; + dval(&d2) = dval(&u); + k0 = k; + ilim0 = ilim; + ieps = 2; /* conservative */ + if (k > 0) { + ds = tens[k&0xf]; + j = k >> 4; + if (j & Bletch) { + /* prevent overflows */ + j &= Bletch - 1; + dval(&u) /= bigtens[n_bigtens-1]; + ieps++; + } + for(; j; j >>= 1, i++) + if (j & 1) { + ieps++; + ds *= bigtens[i]; + } + dval(&u) /= ds; + } + else if ((j1 = -k)) { + dval(&u) *= tens[j1 & 0xf]; + for(j = j1 >> 4; j; j >>= 1, i++) + if (j & 1) { + ieps++; + dval(&u) *= bigtens[i]; + } + } + if (k_check && dval(&u) < 1. && ilim > 0) { + if (ilim1 <= 0) + goto fast_failed; + ilim = ilim1; + k--; + dval(&u) *= 10.; + ieps++; + } + dval(&eps) = ieps*dval(&u) + 7.; + word0(&eps) -= (P-1)*Exp_msk1; + if (ilim == 0) { + S = mhi = 0; + dval(&u) -= 5.; + if (dval(&u) > dval(&eps)) + goto one_digit; + if (dval(&u) < -dval(&eps)) + goto no_digits; + goto fast_failed; + } +#ifndef No_leftright + if (leftright) { + /* Use Steele & White method of only + * generating digits needed. + */ + dval(&eps) = 0.5/tens[ilim-1] - dval(&eps); + for(i = 0;;) { + L = dval(&u); + dval(&u) -= L; + *s++ = '0' + (int)L; + if (dval(&u) < dval(&eps)) + goto ret1; + if (1. - dval(&u) < dval(&eps)) + goto bump_up; + if (++i >= ilim) + break; + dval(&eps) *= 10.; + dval(&u) *= 10.; + } + } + else { +#endif + /* Generate ilim digits, then fix them up. */ + dval(&eps) *= tens[ilim-1]; + for(i = 1;; i++, dval(&u) *= 10.) { + L = (Long)(dval(&u)); + if (!(dval(&u) -= L)) + ilim = i; + *s++ = '0' + (int)L; + if (i == ilim) { + if (dval(&u) > 0.5 + dval(&eps)) + goto bump_up; + else if (dval(&u) < 0.5 - dval(&eps)) { + while(*--s == '0') {} + s++; + goto ret1; + } + break; + } + } +#ifndef No_leftright + } +#endif + fast_failed: + s = s0; + dval(&u) = dval(&d2); + k = k0; + ilim = ilim0; + } + + /* Do we have a "small" integer? */ + + if (be >= 0 && k <= Int_max) { + /* Yes. */ + ds = tens[k]; + if (ndigits < 0 && ilim <= 0) { + S = mhi = 0; + if (ilim < 0 || dval(&u) <= 5*ds) + goto no_digits; + goto one_digit; + } + for(i = 1; i <= k + 1; i++, dval(&u) *= 10.) { + L = (Long)(dval(&u) / ds); + dval(&u) -= L*ds; +#ifdef Check_FLT_ROUNDS + /* If FLT_ROUNDS == 2, L will usually be high by 1 */ + if (dval(&u) < 0) { + L--; + dval(&u) += ds; + } +#endif + *s++ = '0' + (int)L; + if (!dval(&u)) { +#ifdef SET_INEXACT + inexact = 0; +#endif + break; + } + if (i == ilim) { +#ifdef Honor_FLT_ROUNDS + if (mode > 1) + switch(Rounding) { + case 0: goto ret1; + case 2: goto bump_up; + } +#endif + dval(&u) += dval(&u); + if (dval(&u) > ds || (dval(&u) == ds && L & 1)) { + bump_up: + while(*--s == '9') + if (s == s0) { + k++; + *s = '0'; + break; + } + ++*s++; + } + break; + } + } + goto ret1; + } + + m2 = b2; + m5 = b5; + mhi = mlo = 0; + if (leftright) { + i = +#ifndef Sudden_Underflow + denorm ? be + (Bias + (P-1) - 1 + 1) : +#endif +#ifdef IBM + 1 + 4*P - 3 - bbits + ((bbits + be - 1) & 3); +#else + 1 + P - bbits; +#endif + b2 += i; + s2 += i; + mhi = i2b(1); + } + if (m2 > 0 && s2 > 0) { + i = m2 < s2 ? m2 : s2; + b2 -= i; + m2 -= i; + s2 -= i; + } + if (b5 > 0) { + if (leftright) { + if (m5 > 0) { + mhi = pow5mult(mhi, m5); + b1 = mult(mhi, b); + Bfree(b); + b = b1; + } + if ((j = b5 - m5)) + b = pow5mult(b, j); + } + else + b = pow5mult(b, b5); + } + S = i2b(1); + if (s5 > 0) + S = pow5mult(S, s5); + + /* Check for special case that d is a normalized power of 2. */ + + spec_case = 0; + if ((mode < 2 || leftright) +#ifdef Honor_FLT_ROUNDS + && Rounding == 1 +#endif + ) { + if (!word1(&u) && !(word0(&u) & Bndry_mask) +#ifndef Sudden_Underflow + && word0(&u) & (Exp_mask & ~Exp_msk1) +#endif + ) { + /* The special case */ + b2 += Log2P; + s2 += Log2P; + spec_case = 1; + } + } + + /* Arrange for convenient computation of quotients: + * shift left if necessary so divisor has 4 leading 0 bits. + * + * Perhaps we should just compute leading 28 bits of S once + * and for all and pass them and a shift to quorem, so it + * can do shifts and ors to compute the numerator for q. + */ +#ifdef Pack_32 + if ((i = ((s5 ? 32 - hi0bits(S->x[S->wds-1]) : 1) + s2) & 0x1f)) + i = 32 - i; +#define iInc 28 +#else + if (i = ((s5 ? 32 - hi0bits(S->x[S->wds-1]) : 1) + s2) & 0xf) + i = 16 - i; +#define iInc 12 +#endif + i = dshift(S, s2); + b2 += i; + m2 += i; + s2 += i; + if (b2 > 0) + b = lshift(b, b2); + if (s2 > 0) + S = lshift(S, s2); + if (k_check) { + if (cmp(b,S) < 0) { + k--; + b = multadd(b, 10, 0); /* we botched the k estimate */ + if (leftright) + mhi = multadd(mhi, 10, 0); + ilim = ilim1; + } + } + if (ilim <= 0 && (mode == 3 || mode == 5)) { + if (ilim < 0 || cmp(b,S = multadd(S,5,0)) <= 0) { + /* no digits, fcvt style */ + no_digits: + k = -1 - ndigits; + goto ret; + } + one_digit: + *s++ = '1'; + k++; + goto ret; + } + if (leftright) { + if (m2 > 0) + mhi = lshift(mhi, m2); + + /* Compute mlo -- check for special case + * that d is a normalized power of 2. + */ + + mlo = mhi; + if (spec_case) { + mhi = Balloc(mhi->k); + Bcopy(mhi, mlo); + mhi = lshift(mhi, Log2P); + } + + for(i = 1;;i++) { + dig = quorem(b,S) + '0'; + /* Do we yet have the shortest decimal string + * that will round to d? + */ + j = cmp(b, mlo); + delta = diff(S, mhi); + j1 = delta->sign ? 1 : cmp(b, delta); + Bfree(delta); +#ifndef ROUND_BIASED + if (j1 == 0 && mode != 1 && !(word1(&u) & 1) +#ifdef Honor_FLT_ROUNDS + && Rounding >= 1 +#endif + ) { + if (dig == '9') + goto round_9_up; + if (j > 0) + dig++; +#ifdef SET_INEXACT + else if (!b->x[0] && b->wds <= 1) + inexact = 0; +#endif + *s++ = dig; + goto ret; + } +#endif + if (j < 0 || (j == 0 && mode != 1 +#ifndef ROUND_BIASED + && !(word1(&u) & 1) +#endif + )) { + if (!b->x[0] && b->wds <= 1) { +#ifdef SET_INEXACT + inexact = 0; +#endif + goto accept_dig; + } +#ifdef Honor_FLT_ROUNDS + if (mode > 1) + switch(Rounding) { + case 0: goto accept_dig; + case 2: goto keep_dig; + } +#endif /*Honor_FLT_ROUNDS*/ + if (j1 > 0) { + b = lshift(b, 1); + j1 = cmp(b, S); + if ((j1 > 0 || (j1 == 0 && dig & 1)) + && dig++ == '9') + goto round_9_up; + } + accept_dig: + *s++ = dig; + goto ret; + } + if (j1 > 0) { +#ifdef Honor_FLT_ROUNDS + if (!Rounding) + goto accept_dig; +#endif + if (dig == '9') { /* possible if i == 1 */ + round_9_up: + *s++ = '9'; + goto roundoff; + } + *s++ = dig + 1; + goto ret; + } +#ifdef Honor_FLT_ROUNDS + keep_dig: +#endif + *s++ = dig; + if (i == ilim) + break; + b = multadd(b, 10, 0); + if (mlo == mhi) + mlo = mhi = multadd(mhi, 10, 0); + else { + mlo = multadd(mlo, 10, 0); + mhi = multadd(mhi, 10, 0); + } + } + } + else + for(i = 1;; i++) { + *s++ = dig = quorem(b,S) + '0'; + if (!b->x[0] && b->wds <= 1) { +#ifdef SET_INEXACT + inexact = 0; +#endif + goto ret; + } + if (i >= ilim) + break; + b = multadd(b, 10, 0); + } + + /* Round off last digit */ + +#ifdef Honor_FLT_ROUNDS + switch(Rounding) { + case 0: goto trimzeros; + case 2: goto roundoff; + } +#endif + b = lshift(b, 1); + j = cmp(b, S); + if (j > 0 || (j == 0 && dig & 1)) { + roundoff: + while(*--s == '9') + if (s == s0) { + k++; + *s++ = '1'; + goto ret; + } + ++*s++; + } + else { +#ifdef Honor_FLT_ROUNDS + trimzeros: +#endif + while(*--s == '0') {} + s++; + } + ret: + Bfree(S); + if (mhi) { + if (mlo && mlo != mhi) + Bfree(mlo); + Bfree(mhi); + } + ret1: +#ifdef SET_INEXACT + if (inexact) { + if (!oldinexact) { + word0(&u) = Exp_1 + (70 << Exp_shift); + word1(&u) = 0; + dval(&u) += 1.; + } + } + else if (!oldinexact) + clear_inexact(); +#endif + Bfree(b); + *s = 0; + *decpt = k + 1; + if (rve) + *rve = s; + return s0; + } + +} // namespace dmg_fp
diff --git a/src/base/third_party/dmg_fp/dtoa_wrapper.cc b/src/base/third_party/dmg_fp/dtoa_wrapper.cc new file mode 100644 index 0000000..c314c59 --- /dev/null +++ b/src/base/third_party/dmg_fp/dtoa_wrapper.cc
@@ -0,0 +1,46 @@ +// 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. +// +// The purpose of this file is to supply the macro definintions necessary +// to make third_party/dmg_fp/dtoa.cc threadsafe. +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/synchronization/lock.h" + +// We need two locks because they're sometimes grabbed at the same time. +// A single lock would lead to an attempted recursive grab. +static base::LazyInstance<base::Lock>::Leaky + dtoa_lock_0 = LAZY_INSTANCE_INITIALIZER; +static base::LazyInstance<base::Lock>::Leaky + dtoa_lock_1 = LAZY_INSTANCE_INITIALIZER; + +/* + * This define and the code below is to trigger thread-safe behavior + * in dtoa.cc, per this comment from the file: + * + * #define MULTIPLE_THREADS if the system offers preemptively scheduled + * multiple threads. In this case, you must provide (or suitably + * #define) two locks, acquired by ACQUIRE_DTOA_LOCK(n) and freed + * by FREE_DTOA_LOCK(n) for n = 0 or 1. (The second lock, accessed + * in pow5mult, ensures lazy evaluation of only one copy of high + * powers of 5; omitting this lock would introduce a small + * probability of wasting memory, but would otherwise be harmless.) + * You must also invoke freedtoa(s) to free the value s returned by + * dtoa. You may do so whether or not MULTIPLE_THREADS is #defined. + */ +#define MULTIPLE_THREADS + +inline static void ACQUIRE_DTOA_LOCK(size_t n) { + DCHECK(n < 2); + base::Lock* lock = n == 0 ? dtoa_lock_0.Pointer() : dtoa_lock_1.Pointer(); + lock->Acquire(); +} + +inline static void FREE_DTOA_LOCK(size_t n) { + DCHECK(n < 2); + base::Lock* lock = n == 0 ? dtoa_lock_0.Pointer() : dtoa_lock_1.Pointer(); + lock->Release(); +} + +#include "base/third_party/dmg_fp/dtoa.cc"
diff --git a/src/base/third_party/dmg_fp/float_precision_crash.patch b/src/base/third_party/dmg_fp/float_precision_crash.patch new file mode 100644 index 0000000..df64e7e --- /dev/null +++ b/src/base/third_party/dmg_fp/float_precision_crash.patch
@@ -0,0 +1,13 @@ +diff --git a/base/third_party/dmg_fp/dtoa.cc b/base/third_party/dmg_fp/dtoa.cc +index 3f7e794..3312fa4 100644 +--- dtoa.cc ++++ dtoa.cc +@@ -3891,7 +3891,7 @@ dtoa + goto no_digits; + goto one_digit; + } +- for(i = 1;; i++, dval(&u) *= 10.) { ++ for(i = 1; i <= k + 1; i++, dval(&u) *= 10.) { + L = (Long)(dval(&u) / ds); + dval(&u) -= L*ds; + #ifdef Check_FLT_ROUNDS
diff --git a/src/base/third_party/dmg_fp/g_fmt.cc b/src/base/third_party/dmg_fp/g_fmt.cc new file mode 100644 index 0000000..d864eb7 --- /dev/null +++ b/src/base/third_party/dmg_fp/g_fmt.cc
@@ -0,0 +1,102 @@ +/**************************************************************** + * + * The author of this software is David M. Gay. + * + * Copyright (c) 1991, 1996 by Lucent Technologies. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + * + ***************************************************************/ + +/* g_fmt(buf,x) stores the closest decimal approximation to x in buf; + * it suffices to declare buf + * char buf[32]; + */ + +#include "dmg_fp.h" + +namespace dmg_fp { + + char * +g_fmt(register char *b, double x) +{ + register int i, k; + register char *s; + int decpt, j, sign; + char *b0, *s0, *se; + + b0 = b; +#ifdef IGNORE_ZERO_SIGN + if (!x) { + *b++ = '0'; + *b = 0; + goto done; + } +#endif + s = s0 = dtoa(x, 0, 0, &decpt, &sign, &se); + if (sign) + *b++ = '-'; + if (decpt == 9999) /* Infinity or Nan */ { + while((*b++ = *s++)) {} + goto done0; + } + if (decpt <= -4 || decpt > se - s + 5) { + *b++ = *s++; + if (*s) { + *b++ = '.'; + while((*b = *s++)) + b++; + } + *b++ = 'e'; + /* sprintf(b, "%+.2d", decpt - 1); */ + if (--decpt < 0) { + *b++ = '-'; + decpt = -decpt; + } + else + *b++ = '+'; + for(j = 2, k = 10; 10*k <= decpt; j++, k *= 10) {} + for(;;) { + i = decpt / k; + *b++ = i + '0'; + if (--j <= 0) + break; + decpt -= i*k; + decpt *= 10; + } + *b = 0; + } + else if (decpt <= 0) { + *b++ = '.'; + for(; decpt < 0; decpt++) + *b++ = '0'; + while((*b++ = *s++)) {} + } + else { + while((*b = *s++)) { + b++; + if (--decpt == 0 && *s) + *b++ = '.'; + } + for(; decpt > 0; decpt--) + *b++ = '0'; + *b = 0; + } + done0: + freedtoa(s0); +#ifdef IGNORE_ZERO_SIGN + done: +#endif + return b0; + } + +} // namespace dmg_fp
diff --git a/src/base/third_party/dmg_fp/gcc_64_bit.patch b/src/base/third_party/dmg_fp/gcc_64_bit.patch new file mode 100644 index 0000000..ab943c0 --- /dev/null +++ b/src/base/third_party/dmg_fp/gcc_64_bit.patch
@@ -0,0 +1,25 @@ +Index: dtoa.cc +--- dtoa.cc (old copy) ++++ dtoa.cc (working copy) +@@ -183,8 +183,12 @@ + #define NO_HEX_FP + + #ifndef Long ++#if __LP64__ ++#define Long int ++#else + #define Long long + #endif ++#endif + #ifndef ULong + typedef unsigned Long ULong; + #endif +@@ -221,7 +225,7 @@ extern void *MALLOC(size_t); + #ifndef PRIVATE_MEM + #define PRIVATE_MEM 2304 + #endif +-#define PRIVATE_mem ((PRIVATE_MEM+sizeof(double)-1)/sizeof(double)) ++#define PRIVATE_mem ((unsigned)((PRIVATE_MEM+sizeof(double)-1)/sizeof(double))) + static double private_mem[PRIVATE_mem], *pmem_next = private_mem; + #endif +
diff --git a/src/base/third_party/dmg_fp/gcc_warnings.patch b/src/base/third_party/dmg_fp/gcc_warnings.patch new file mode 100644 index 0000000..4262237 --- /dev/null +++ b/src/base/third_party/dmg_fp/gcc_warnings.patch
@@ -0,0 +1,126 @@ +Index: dtoa.cc +--- dtoa.cc (old copy) ++++ dtoa.cc (working copy) +@@ -179,6 +179,9 @@ + * used for input more than STRTOD_DIGLIM digits long (default 40). + */ + ++#define IEEE_8087 ++#define NO_HEX_FP ++ + #ifndef Long + #define Long long + #endif +@@ -280,9 +283,7 @@ + #include "math.h" + #endif + +-#ifdef __cplusplus +-extern "C" { +-#endif ++namespace dmg_fp { + + #ifndef CONST + #ifdef KR_headers +@@ -511,11 +512,9 @@ + + #define Kmax 7 + +-#ifdef __cplusplus +-extern "C" double strtod(const char *s00, char **se); +-extern "C" char *dtoa(double d, int mode, int ndigits, ++double strtod(const char *s00, char **se); ++char *dtoa(double d, int mode, int ndigits, + int *decpt, int *sign, char **rve); +-#endif + + struct + Bigint { +@@ -1527,7 +1526,7 @@ + #ifdef KR_headers + (sp, t) char **sp, *t; + #else +- (CONST char **sp, char *t) ++ (CONST char **sp, CONST char *t) + #endif + { + int c, d; +@@ -2234,7 +2234,7 @@ bigcomp + nd = bc->nd; + nd0 = bc->nd0; + p5 = nd + bc->e0 - 1; +- speccase = 0; ++ dd = speccase = 0; + #ifndef Sudden_Underflow + if (rv->d == 0.) { /* special case: value near underflow-to-zero */ + /* threshold was rounded to zero */ +@@ -3431,7 +3430,7 @@ + + j = sizeof(ULong); + for(k = 0; +- sizeof(Bigint) - sizeof(ULong) - sizeof(int) + j <= i; ++ sizeof(Bigint) - sizeof(ULong) - sizeof(int) + j <= (size_t)i; + j <<= 1) + k++; + r = (int*)Balloc(k); +@@ -3447,7 +3446,7 @@ + #ifdef KR_headers + nrv_alloc(s, rve, n) char *s, **rve; int n; + #else +-nrv_alloc(char *s, char **rve, int n) ++nrv_alloc(CONST char *s, char **rve, int n) + #endif + { + char *rv, *t; +@@ -4202,6 +4201,5 @@ + *rve = s; + return s0; + } +-#ifdef __cplusplus +-} +-#endif ++ ++} // namespace dmg_fp +Index: g_fmt.cc +--- g_fmt.cc (old copy) ++++ g_fmt.cc (new copy) +@@ -46,14 +46,14 @@ g_fmt(register char *b, double x) + if (sign) + *b++ = '-'; + if (decpt == 9999) /* Infinity or Nan */ { +- while(*b++ = *s++); ++ while((*b++ = *s++)); + goto done0; + } + if (decpt <= -4 || decpt > se - s + 5) { + *b++ = *s++; + if (*s) { + *b++ = '.'; +- while(*b = *s++) ++ while((*b = *s++)) + b++; + } + *b++ = 'e'; +@@ -79,10 +79,10 @@ g_fmt(register char *b, double x) + *b++ = '.'; + for(; decpt < 0; decpt++) + *b++ = '0'; +- while(*b++ = *s++); ++ while((*b++ = *s++)); + } + else { +- while(*b = *s++) { ++ while((*b = *s++)) { + b++; + if (--decpt == 0 && *s) + *b++ = '.'; +@@ -93,7 +93,9 @@ g_fmt(register char *b, double x) + } + done0: + freedtoa(s0); ++#ifdef IGNORE_ZERO_SIGN + done: ++#endif + return b0; + } +
diff --git a/src/base/third_party/dmg_fp/mac_wextra.patch b/src/base/third_party/dmg_fp/mac_wextra.patch new file mode 100644 index 0000000..15340f2 --- /dev/null +++ b/src/base/third_party/dmg_fp/mac_wextra.patch
@@ -0,0 +1,53 @@ +Index: g_fmt.cc +=================================================================== +--- g_fmt.cc (revision 49784) ++++ g_fmt.cc (working copy) +@@ -46,7 +46,7 @@ + if (sign) + *b++ = '-'; + if (decpt == 9999) /* Infinity or Nan */ { +- while((*b++ = *s++)); ++ while((*b++ = *s++)) {} + goto done0; + } + if (decpt <= -4 || decpt > se - s + 5) { +@@ -64,7 +64,7 @@ + } + else + *b++ = '+'; +- for(j = 2, k = 10; 10*k <= decpt; j++, k *= 10); ++ for(j = 2, k = 10; 10*k <= decpt; j++, k *= 10) {} + for(;;) { + i = decpt / k; + *b++ = i + '0'; +@@ -79,7 +79,7 @@ + *b++ = '.'; + for(; decpt < 0; decpt++) + *b++ = '0'; +- while((*b++ = *s++)); ++ while((*b++ = *s++)) {} + } + else { + while((*b = *s++)) { +Index: dtoa.cc +=================================================================== +--- dtoa.cc (revision 49784) ++++ dtoa.cc (working copy) +@@ -3863,7 +3863,7 @@ + if (dval(&u) > 0.5 + dval(&eps)) + goto bump_up; + else if (dval(&u) < 0.5 - dval(&eps)) { +- while(*--s == '0'); ++ while(*--s == '0') {} + s++; + goto ret1; + } +@@ -4176,7 +4176,7 @@ + #ifdef Honor_FLT_ROUNDS + trimzeros: + #endif +- while(*--s == '0'); ++ while(*--s == '0') {} + s++; + } + ret:
diff --git a/src/base/third_party/dmg_fp/win_vs2012.patch b/src/base/third_party/dmg_fp/win_vs2012.patch new file mode 100644 index 0000000..f3f599c --- /dev/null +++ b/src/base/third_party/dmg_fp/win_vs2012.patch
@@ -0,0 +1,13 @@ +Index: dtoa.cc +=================================================================== +--- dtoa.cc (revision 161424) ++++ dtoa.cc (working copy) +@@ -3569,7 +3569,7 @@ + int denorm; + ULong x; + #endif +- Bigint *b, *b1, *delta, *mlo, *mhi, *S; ++ Bigint *b, *b1, *delta, *mlo = NULL, *mhi, *S; + U d2, eps, u; + double ds; + char *s, *s0;
diff --git a/src/base/third_party/dynamic_annotations/LICENSE b/src/base/third_party/dynamic_annotations/LICENSE new file mode 100644 index 0000000..5c581a9 --- /dev/null +++ b/src/base/third_party/dynamic_annotations/LICENSE
@@ -0,0 +1,28 @@ +/* Copyright (c) 2008-2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * --- + * Author: Kostya Serebryany + */
diff --git a/src/base/third_party/dynamic_annotations/README.chromium b/src/base/third_party/dynamic_annotations/README.chromium new file mode 100644 index 0000000..dc8bdef --- /dev/null +++ b/src/base/third_party/dynamic_annotations/README.chromium
@@ -0,0 +1,13 @@ +Name: dynamic annotations +URL: http://code.google.com/p/data-race-test/wiki/DynamicAnnotations +Version: 4384 +License: BSD + +One header and one source file (dynamic_annotations.h and dynamic_annotations.c) +in this directory define runtime macros useful for annotating synchronization +utilities and benign data races so data race detectors can handle Chromium code +with better precision. + +These files were taken from +http://code.google.com/p/data-race-test/source/browse/?#svn/trunk/dynamic_annotations +The files are covered under BSD license as described within the files.
diff --git a/src/base/third_party/dynamic_annotations/dynamic_annotations.c b/src/base/third_party/dynamic_annotations/dynamic_annotations.c new file mode 100644 index 0000000..5a13979 --- /dev/null +++ b/src/base/third_party/dynamic_annotations/dynamic_annotations.c
@@ -0,0 +1,273 @@ +/* Copyright (c) 2011, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && !defined(COBALT_WIN) +# include <windows.h> +#endif + +#ifdef __cplusplus +# error "This file should be built as pure C to avoid name mangling" +#endif + +#include <stdlib.h> +#include <string.h> + +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" + +#ifdef __GNUC__ +/* valgrind.h uses gcc extensions so it won't build with other compilers */ +# include "base/third_party/valgrind/valgrind.h" +#endif + +/* Compiler-based ThreadSanitizer defines + DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL = 1 + and provides its own definitions of the functions. */ + +#ifndef DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL +# define DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL 0 +#endif + +/* Each function is empty and called (via a macro) only in debug mode. + The arguments are captured by dynamic tools at runtime. */ + +#if DYNAMIC_ANNOTATIONS_ENABLED == 1 \ + && DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL == 0 + +/* Identical code folding(-Wl,--icf=all) countermeasures. + This makes all Annotate* functions different, which prevents the linker from + folding them. */ +#ifdef __COUNTER__ +#define DYNAMIC_ANNOTATIONS_IMPL \ + volatile short lineno = (__LINE__ << 8) + __COUNTER__; (void)lineno; +#else +#define DYNAMIC_ANNOTATIONS_IMPL \ + volatile short lineno = (__LINE__ << 8); (void)lineno; +#endif + +/* WARNING: always add new annotations to the end of the list. + Otherwise, lineno (see above) numbers for different Annotate* functions may + conflict. */ +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockCreate)( + const char *file, int line, const volatile void *lock) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockDestroy)( + const char *file, int line, const volatile void *lock) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockAcquired)( + const char *file, int line, const volatile void *lock, long is_w) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockReleased)( + const char *file, int line, const volatile void *lock, long is_w) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierInit)( + const char *file, int line, const volatile void *barrier, long count, + long reinitialization_allowed) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierWaitBefore)( + const char *file, int line, const volatile void *barrier) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierWaitAfter)( + const char *file, int line, const volatile void *barrier) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierDestroy)( + const char *file, int line, const volatile void *barrier) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarWait)( + const char *file, int line, const volatile void *cv, + const volatile void *lock) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarSignal)( + const char *file, int line, const volatile void *cv) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarSignalAll)( + const char *file, int line, const volatile void *cv) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateHappensBefore)( + const char *file, int line, const volatile void *obj) +{DYNAMIC_ANNOTATIONS_IMPL}; + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateHappensAfter)( + const char *file, int line, const volatile void *obj) +{DYNAMIC_ANNOTATIONS_IMPL}; + +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePublishMemoryRange)( + const char *file, int line, const volatile void *address, long size) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateUnpublishMemoryRange)( + const char *file, int line, const volatile void *address, long size) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQCreate)( + const char *file, int line, const volatile void *pcq) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQDestroy)( + const char *file, int line, const volatile void *pcq) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQPut)( + const char *file, int line, const volatile void *pcq) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQGet)( + const char *file, int line, const volatile void *pcq) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateNewMemory)( + const char *file, int line, const volatile void *mem, long size) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateExpectRace)( + const char *file, int line, const volatile void *mem, + const char *description) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateFlushExpectedRaces)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBenignRace)( + const char *file, int line, const volatile void *mem, + const char *description) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBenignRaceSized)( + const char *file, int line, const volatile void *mem, long size, + const char *description) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateMutexIsUsedAsCondVar)( + const char *file, int line, const volatile void *mu) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateMutexIsNotPHB)( + const char *file, int line, const volatile void *mu) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateTraceMemory)( + const char *file, int line, const volatile void *arg) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateThreadName)( + const char *file, int line, const char *name) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreReadsBegin)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreReadsEnd)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreWritesBegin)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreWritesEnd)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreSyncBegin)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreSyncEnd)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateEnableRaceDetection)( + const char *file, int line, int enable) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateNoOp)( + const char *file, int line, const volatile void *arg) +{DYNAMIC_ANNOTATIONS_IMPL} + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateFlushState)( + const char *file, int line) +{DYNAMIC_ANNOTATIONS_IMPL} + +#endif /* DYNAMIC_ANNOTATIONS_ENABLED == 1 + && DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL == 0 */ + +#if DYNAMIC_ANNOTATIONS_PROVIDE_RUNNING_ON_VALGRIND == 1 \ + && DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL == 0 +static int GetRunningOnValgrind(void) { +#ifdef RUNNING_ON_VALGRIND + if (RUNNING_ON_VALGRIND) return 1; +#endif +#if defined(__LB_SHELL__) || defined(STARBOARD) + return 0; +#else + +#ifndef _MSC_VER + char *running_on_valgrind_str = getenv("RUNNING_ON_VALGRIND"); + if (running_on_valgrind_str) { + return strcmp(running_on_valgrind_str, "0") != 0; + } +#else + /* Visual Studio issues warnings if we use getenv, + * so we use GetEnvironmentVariableA instead. + */ + char value[100] = "1"; + int res = GetEnvironmentVariableA("RUNNING_ON_VALGRIND", + value, sizeof(value)); + /* value will remain "1" if res == 0 or res >= sizeof(value). The latter + * can happen only if the given value is long, in this case it can't be "0". + */ + if (res > 0 && strcmp(value, "0") != 0) + return 1; +#endif + return 0; +#endif +} + +/* See the comments in dynamic_annotations.h */ +int RunningOnValgrind(void) { + static volatile int running_on_valgrind = -1; + /* C doesn't have thread-safe initialization of statics, and we + don't want to depend on pthread_once here, so hack it. */ + int local_running_on_valgrind = running_on_valgrind; + if (local_running_on_valgrind == -1) + running_on_valgrind = local_running_on_valgrind = GetRunningOnValgrind(); + return local_running_on_valgrind; +} + +#endif /* DYNAMIC_ANNOTATIONS_PROVIDE_RUNNING_ON_VALGRIND == 1 + && DYNAMIC_ANNOTATIONS_EXTERNAL_IMPL == 0 */
diff --git a/src/base/third_party/dynamic_annotations/dynamic_annotations.gyp b/src/base/third_party/dynamic_annotations/dynamic_annotations.gyp new file mode 100644 index 0000000..1f1dc49 --- /dev/null +++ b/src/base/third_party/dynamic_annotations/dynamic_annotations.gyp
@@ -0,0 +1,47 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'dynamic_annotations', + 'type': 'static_library', + 'toolsets': ['host', 'target'], + 'include_dirs': [ + '../../../', + ], + 'sources': [ + 'dynamic_annotations.c', + 'dynamic_annotations.h', + '../valgrind/valgrind.h', + ], + }, + ], + 'conditions': [ + ['OS == "win"', { + 'targets': [ + { + 'target_name': 'dynamic_annotations_win64', + 'type': 'static_library', + # We can't use dynamic_annotations target for win64 build since it is + # a 32-bit library. + # TODO(gregoryd): merge with dynamic_annotations when + # the win32/64 targets are merged. + 'include_dirs': [ + '../../../', + ], + 'sources': [ + 'dynamic_annotations.c', + 'dynamic_annotations.h', + ], + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + }, + ], + }], + ], +}
diff --git a/src/base/third_party/dynamic_annotations/dynamic_annotations.h b/src/base/third_party/dynamic_annotations/dynamic_annotations.h new file mode 100644 index 0000000..f7403bc --- /dev/null +++ b/src/base/third_party/dynamic_annotations/dynamic_annotations.h
@@ -0,0 +1,602 @@ +/* Copyright (c) 2011, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* This file defines dynamic annotations for use with dynamic analysis + tool such as valgrind, PIN, etc. + + Dynamic annotation is a source code annotation that affects + the generated code (that is, the annotation is not a comment). + Each such annotation is attached to a particular + instruction and/or to a particular object (address) in the program. + + The annotations that should be used by users are macros in all upper-case + (e.g., ANNOTATE_NEW_MEMORY). + + Actual implementation of these macros may differ depending on the + dynamic analysis tool being used. + + See http://code.google.com/p/data-race-test/ for more information. + + This file supports the following dynamic analysis tools: + - None (DYNAMIC_ANNOTATIONS_ENABLED is not defined or zero). + Macros are defined empty. + - ThreadSanitizer, Helgrind, DRD (DYNAMIC_ANNOTATIONS_ENABLED is 1). + Macros are defined as calls to non-inlinable empty functions + that are intercepted by Valgrind. */ + +#ifndef __DYNAMIC_ANNOTATIONS_H__ +#define __DYNAMIC_ANNOTATIONS_H__ + +#ifndef DYNAMIC_ANNOTATIONS_PREFIX +# define DYNAMIC_ANNOTATIONS_PREFIX +#endif + +#ifndef DYNAMIC_ANNOTATIONS_PROVIDE_RUNNING_ON_VALGRIND +# define DYNAMIC_ANNOTATIONS_PROVIDE_RUNNING_ON_VALGRIND 1 +#endif + +#ifdef DYNAMIC_ANNOTATIONS_WANT_ATTRIBUTE_WEAK +# ifdef __GNUC__ +# define DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK __attribute__((weak)) +# else +/* TODO(glider): for Windows support we may want to change this macro in order + to prepend __declspec(selectany) to the annotations' declarations. */ +# error weak annotations are not supported for your compiler +# endif +#else +# define DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK +#endif + +/* The following preprocessor magic prepends the value of + DYNAMIC_ANNOTATIONS_PREFIX to annotation function names. */ +#define DYNAMIC_ANNOTATIONS_GLUE0(A, B) A##B +#define DYNAMIC_ANNOTATIONS_GLUE(A, B) DYNAMIC_ANNOTATIONS_GLUE0(A, B) +#define DYNAMIC_ANNOTATIONS_NAME(name) \ + DYNAMIC_ANNOTATIONS_GLUE(DYNAMIC_ANNOTATIONS_PREFIX, name) + +#ifndef DYNAMIC_ANNOTATIONS_ENABLED +# define DYNAMIC_ANNOTATIONS_ENABLED 0 +#endif + +#if DYNAMIC_ANNOTATIONS_ENABLED != 0 + + /* ------------------------------------------------------------- + Annotations useful when implementing condition variables such as CondVar, + using conditional critical sections (Await/LockWhen) and when constructing + user-defined synchronization mechanisms. + + The annotations ANNOTATE_HAPPENS_BEFORE() and ANNOTATE_HAPPENS_AFTER() can + be used to define happens-before arcs in user-defined synchronization + mechanisms: the race detector will infer an arc from the former to the + latter when they share the same argument pointer. + + Example 1 (reference counting): + + void Unref() { + ANNOTATE_HAPPENS_BEFORE(&refcount_); + if (AtomicDecrementByOne(&refcount_) == 0) { + ANNOTATE_HAPPENS_AFTER(&refcount_); + delete this; + } + } + + Example 2 (message queue): + + void MyQueue::Put(Type *e) { + MutexLock lock(&mu_); + ANNOTATE_HAPPENS_BEFORE(e); + PutElementIntoMyQueue(e); + } + + Type *MyQueue::Get() { + MutexLock lock(&mu_); + Type *e = GetElementFromMyQueue(); + ANNOTATE_HAPPENS_AFTER(e); + return e; + } + + Note: when possible, please use the existing reference counting and message + queue implementations instead of inventing new ones. */ + + /* Report that wait on the condition variable at address "cv" has succeeded + and the lock at address "lock" is held. */ + #define ANNOTATE_CONDVAR_LOCK_WAIT(cv, lock) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarWait)(__FILE__, __LINE__, cv, lock) + + /* Report that wait on the condition variable at "cv" has succeeded. Variant + w/o lock. */ + #define ANNOTATE_CONDVAR_WAIT(cv) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarWait)(__FILE__, __LINE__, cv, NULL) + + /* Report that we are about to signal on the condition variable at address + "cv". */ + #define ANNOTATE_CONDVAR_SIGNAL(cv) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarSignal)(__FILE__, __LINE__, cv) + + /* Report that we are about to signal_all on the condition variable at address + "cv". */ + #define ANNOTATE_CONDVAR_SIGNAL_ALL(cv) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarSignalAll)(__FILE__, __LINE__, cv) + + /* Annotations for user-defined synchronization mechanisms. */ + #define ANNOTATE_HAPPENS_BEFORE(obj) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateHappensBefore)(__FILE__, __LINE__, obj) + #define ANNOTATE_HAPPENS_AFTER(obj) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateHappensAfter)(__FILE__, __LINE__, obj) + + /* DEPRECATED. Don't use it. */ + #define ANNOTATE_PUBLISH_MEMORY_RANGE(pointer, size) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotatePublishMemoryRange)(__FILE__, __LINE__, \ + pointer, size) + + /* DEPRECATED. Don't use it. */ + #define ANNOTATE_UNPUBLISH_MEMORY_RANGE(pointer, size) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateUnpublishMemoryRange)(__FILE__, __LINE__, \ + pointer, size) + + /* DEPRECATED. Don't use it. */ + #define ANNOTATE_SWAP_MEMORY_RANGE(pointer, size) \ + do { \ + ANNOTATE_UNPUBLISH_MEMORY_RANGE(pointer, size); \ + ANNOTATE_PUBLISH_MEMORY_RANGE(pointer, size); \ + } while (0) + + /* Instruct the tool to create a happens-before arc between mu->Unlock() and + mu->Lock(). This annotation may slow down the race detector and hide real + races. Normally it is used only when it would be difficult to annotate each + of the mutex's critical sections individually using the annotations above. + This annotation makes sense only for hybrid race detectors. For pure + happens-before detectors this is a no-op. For more details see + http://code.google.com/p/data-race-test/wiki/PureHappensBeforeVsHybrid . */ + #define ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX(mu) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateMutexIsUsedAsCondVar)(__FILE__, __LINE__, \ + mu) + + /* Opposite to ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX. + Instruct the tool to NOT create h-b arcs between Unlock and Lock, even in + pure happens-before mode. For a hybrid mode this is a no-op. */ + #define ANNOTATE_NOT_HAPPENS_BEFORE_MUTEX(mu) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateMutexIsNotPHB)(__FILE__, __LINE__, mu) + + /* Deprecated. Use ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX. */ + #define ANNOTATE_MUTEX_IS_USED_AS_CONDVAR(mu) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateMutexIsUsedAsCondVar)(__FILE__, __LINE__, \ + mu) + + /* ------------------------------------------------------------- + Annotations useful when defining memory allocators, or when memory that + was protected in one way starts to be protected in another. */ + + /* Report that a new memory at "address" of size "size" has been allocated. + This might be used when the memory has been retrieved from a free list and + is about to be reused, or when a the locking discipline for a variable + changes. */ + #define ANNOTATE_NEW_MEMORY(address, size) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateNewMemory)(__FILE__, __LINE__, address, \ + size) + + /* ------------------------------------------------------------- + Annotations useful when defining FIFO queues that transfer data between + threads. */ + + /* Report that the producer-consumer queue (such as ProducerConsumerQueue) at + address "pcq" has been created. The ANNOTATE_PCQ_* annotations + should be used only for FIFO queues. For non-FIFO queues use + ANNOTATE_HAPPENS_BEFORE (for put) and ANNOTATE_HAPPENS_AFTER (for get). */ + #define ANNOTATE_PCQ_CREATE(pcq) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQCreate)(__FILE__, __LINE__, pcq) + + /* Report that the queue at address "pcq" is about to be destroyed. */ + #define ANNOTATE_PCQ_DESTROY(pcq) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQDestroy)(__FILE__, __LINE__, pcq) + + /* Report that we are about to put an element into a FIFO queue at address + "pcq". */ + #define ANNOTATE_PCQ_PUT(pcq) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQPut)(__FILE__, __LINE__, pcq) + + /* Report that we've just got an element from a FIFO queue at address + "pcq". */ + #define ANNOTATE_PCQ_GET(pcq) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQGet)(__FILE__, __LINE__, pcq) + + /* ------------------------------------------------------------- + Annotations that suppress errors. It is usually better to express the + program's synchronization using the other annotations, but these can + be used when all else fails. */ + + /* Report that we may have a benign race at "pointer", with size + "sizeof(*(pointer))". "pointer" must be a non-void* pointer. Insert at the + point where "pointer" has been allocated, preferably close to the point + where the race happens. See also ANNOTATE_BENIGN_RACE_STATIC. */ + #define ANNOTATE_BENIGN_RACE(pointer, description) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateBenignRaceSized)(__FILE__, __LINE__, \ + pointer, sizeof(*(pointer)), description) + + /* Same as ANNOTATE_BENIGN_RACE(address, description), but applies to + the memory range [address, address+size). */ + #define ANNOTATE_BENIGN_RACE_SIZED(address, size, description) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateBenignRaceSized)(__FILE__, __LINE__, \ + address, size, description) + + /* Request the analysis tool to ignore all reads in the current thread + until ANNOTATE_IGNORE_READS_END is called. + Useful to ignore intentional racey reads, while still checking + other reads and all writes. + See also ANNOTATE_UNPROTECTED_READ. */ + #define ANNOTATE_IGNORE_READS_BEGIN() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreReadsBegin)(__FILE__, __LINE__) + + /* Stop ignoring reads. */ + #define ANNOTATE_IGNORE_READS_END() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreReadsEnd)(__FILE__, __LINE__) + + /* Similar to ANNOTATE_IGNORE_READS_BEGIN, but ignore writes. */ + #define ANNOTATE_IGNORE_WRITES_BEGIN() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreWritesBegin)(__FILE__, __LINE__) + + /* Stop ignoring writes. */ + #define ANNOTATE_IGNORE_WRITES_END() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreWritesEnd)(__FILE__, __LINE__) + + /* Start ignoring all memory accesses (reads and writes). */ + #define ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN() \ + do {\ + ANNOTATE_IGNORE_READS_BEGIN();\ + ANNOTATE_IGNORE_WRITES_BEGIN();\ + }while(0)\ + + /* Stop ignoring all memory accesses. */ + #define ANNOTATE_IGNORE_READS_AND_WRITES_END() \ + do {\ + ANNOTATE_IGNORE_WRITES_END();\ + ANNOTATE_IGNORE_READS_END();\ + }while(0)\ + + /* Similar to ANNOTATE_IGNORE_READS_BEGIN, but ignore synchronization events: + RWLOCK* and CONDVAR*. */ + #define ANNOTATE_IGNORE_SYNC_BEGIN() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreSyncBegin)(__FILE__, __LINE__) + + /* Stop ignoring sync events. */ + #define ANNOTATE_IGNORE_SYNC_END() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreSyncEnd)(__FILE__, __LINE__) + + + /* Enable (enable!=0) or disable (enable==0) race detection for all threads. + This annotation could be useful if you want to skip expensive race analysis + during some period of program execution, e.g. during initialization. */ + #define ANNOTATE_ENABLE_RACE_DETECTION(enable) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateEnableRaceDetection)(__FILE__, __LINE__, \ + enable) + + /* ------------------------------------------------------------- + Annotations useful for debugging. */ + + /* Request to trace every access to "address". */ + #define ANNOTATE_TRACE_MEMORY(address) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateTraceMemory)(__FILE__, __LINE__, address) + + /* Report the current thread name to a race detector. */ + #define ANNOTATE_THREAD_NAME(name) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateThreadName)(__FILE__, __LINE__, name) + + /* ------------------------------------------------------------- + Annotations useful when implementing locks. They are not + normally needed by modules that merely use locks. + The "lock" argument is a pointer to the lock object. */ + + /* Report that a lock has been created at address "lock". */ + #define ANNOTATE_RWLOCK_CREATE(lock) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockCreate)(__FILE__, __LINE__, lock) + + /* Report that the lock at address "lock" is about to be destroyed. */ + #define ANNOTATE_RWLOCK_DESTROY(lock) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockDestroy)(__FILE__, __LINE__, lock) + + /* Report that the lock at address "lock" has been acquired. + is_w=1 for writer lock, is_w=0 for reader lock. */ + #define ANNOTATE_RWLOCK_ACQUIRED(lock, is_w) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockAcquired)(__FILE__, __LINE__, lock, \ + is_w) + + /* Report that the lock at address "lock" is about to be released. */ + #define ANNOTATE_RWLOCK_RELEASED(lock, is_w) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockReleased)(__FILE__, __LINE__, lock, \ + is_w) + + /* ------------------------------------------------------------- + Annotations useful when implementing barriers. They are not + normally needed by modules that merely use barriers. + The "barrier" argument is a pointer to the barrier object. */ + + /* Report that the "barrier" has been initialized with initial "count". + If 'reinitialization_allowed' is true, initialization is allowed to happen + multiple times w/o calling barrier_destroy() */ + #define ANNOTATE_BARRIER_INIT(barrier, count, reinitialization_allowed) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierInit)(__FILE__, __LINE__, barrier, \ + count, reinitialization_allowed) + + /* Report that we are about to enter barrier_wait("barrier"). */ + #define ANNOTATE_BARRIER_WAIT_BEFORE(barrier) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierWaitBefore)(__FILE__, __LINE__, \ + barrier) + + /* Report that we just exited barrier_wait("barrier"). */ + #define ANNOTATE_BARRIER_WAIT_AFTER(barrier) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierWaitAfter)(__FILE__, __LINE__, \ + barrier) + + /* Report that the "barrier" has been destroyed. */ + #define ANNOTATE_BARRIER_DESTROY(barrier) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierDestroy)(__FILE__, __LINE__, \ + barrier) + + /* ------------------------------------------------------------- + Annotations useful for testing race detectors. */ + + /* Report that we expect a race on the variable at "address". + Use only in unit tests for a race detector. */ + #define ANNOTATE_EXPECT_RACE(address, description) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateExpectRace)(__FILE__, __LINE__, address, \ + description) + + #define ANNOTATE_FLUSH_EXPECTED_RACES() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateFlushExpectedRaces)(__FILE__, __LINE__) + + /* A no-op. Insert where you like to test the interceptors. */ + #define ANNOTATE_NO_OP(arg) \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateNoOp)(__FILE__, __LINE__, arg) + + /* Force the race detector to flush its state. The actual effect depends on + * the implementation of the detector. */ + #define ANNOTATE_FLUSH_STATE() \ + DYNAMIC_ANNOTATIONS_NAME(AnnotateFlushState)(__FILE__, __LINE__) + + +#else /* DYNAMIC_ANNOTATIONS_ENABLED == 0 */ + + #define ANNOTATE_RWLOCK_CREATE(lock) /* empty */ + #define ANNOTATE_RWLOCK_DESTROY(lock) /* empty */ + #define ANNOTATE_RWLOCK_ACQUIRED(lock, is_w) /* empty */ + #define ANNOTATE_RWLOCK_RELEASED(lock, is_w) /* empty */ + #define ANNOTATE_BARRIER_INIT(barrier, count, reinitialization_allowed) /* */ + #define ANNOTATE_BARRIER_WAIT_BEFORE(barrier) /* empty */ + #define ANNOTATE_BARRIER_WAIT_AFTER(barrier) /* empty */ + #define ANNOTATE_BARRIER_DESTROY(barrier) /* empty */ + #define ANNOTATE_CONDVAR_LOCK_WAIT(cv, lock) /* empty */ + #define ANNOTATE_CONDVAR_WAIT(cv) /* empty */ + #define ANNOTATE_CONDVAR_SIGNAL(cv) /* empty */ + #define ANNOTATE_CONDVAR_SIGNAL_ALL(cv) /* empty */ + #define ANNOTATE_HAPPENS_BEFORE(obj) /* empty */ + #define ANNOTATE_HAPPENS_AFTER(obj) /* empty */ + #define ANNOTATE_PUBLISH_MEMORY_RANGE(address, size) /* empty */ + #define ANNOTATE_UNPUBLISH_MEMORY_RANGE(address, size) /* empty */ + #define ANNOTATE_SWAP_MEMORY_RANGE(address, size) /* empty */ + #define ANNOTATE_PCQ_CREATE(pcq) /* empty */ + #define ANNOTATE_PCQ_DESTROY(pcq) /* empty */ + #define ANNOTATE_PCQ_PUT(pcq) /* empty */ + #define ANNOTATE_PCQ_GET(pcq) /* empty */ + #define ANNOTATE_NEW_MEMORY(address, size) /* empty */ + #define ANNOTATE_EXPECT_RACE(address, description) /* empty */ + #define ANNOTATE_FLUSH_EXPECTED_RACES(address, description) /* empty */ + #define ANNOTATE_BENIGN_RACE(address, description) /* empty */ + #define ANNOTATE_BENIGN_RACE_SIZED(address, size, description) /* empty */ + #define ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX(mu) /* empty */ + #define ANNOTATE_MUTEX_IS_USED_AS_CONDVAR(mu) /* empty */ + #define ANNOTATE_TRACE_MEMORY(arg) /* empty */ + #define ANNOTATE_THREAD_NAME(name) /* empty */ + #define ANNOTATE_IGNORE_READS_BEGIN() /* empty */ + #define ANNOTATE_IGNORE_READS_END() /* empty */ + #define ANNOTATE_IGNORE_WRITES_BEGIN() /* empty */ + #define ANNOTATE_IGNORE_WRITES_END() /* empty */ + #define ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN() /* empty */ + #define ANNOTATE_IGNORE_READS_AND_WRITES_END() /* empty */ + #define ANNOTATE_IGNORE_SYNC_BEGIN() /* empty */ + #define ANNOTATE_IGNORE_SYNC_END() /* empty */ + #define ANNOTATE_ENABLE_RACE_DETECTION(enable) /* empty */ + #define ANNOTATE_NO_OP(arg) /* empty */ + #define ANNOTATE_FLUSH_STATE() /* empty */ + +#endif /* DYNAMIC_ANNOTATIONS_ENABLED */ + +/* Use the macros above rather than using these functions directly. */ +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(COMPILER_GHS) +#pragma ghs nowarning 76 // Argument to macro is empty. +#endif + +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockCreate)( + const char *file, int line, + const volatile void *lock) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockDestroy)( + const char *file, int line, + const volatile void *lock) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockAcquired)( + const char *file, int line, + const volatile void *lock, long is_w) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateRWLockReleased)( + const char *file, int line, + const volatile void *lock, long is_w) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierInit)( + const char *file, int line, const volatile void *barrier, long count, + long reinitialization_allowed) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierWaitBefore)( + const char *file, int line, + const volatile void *barrier) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierWaitAfter)( + const char *file, int line, + const volatile void *barrier) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBarrierDestroy)( + const char *file, int line, + const volatile void *barrier) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarWait)( + const char *file, int line, const volatile void *cv, + const volatile void *lock) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarSignal)( + const char *file, int line, + const volatile void *cv) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateCondVarSignalAll)( + const char *file, int line, + const volatile void *cv) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateHappensBefore)( + const char *file, int line, + const volatile void *obj) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateHappensAfter)( + const char *file, int line, + const volatile void *obj) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePublishMemoryRange)( + const char *file, int line, + const volatile void *address, long size) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateUnpublishMemoryRange)( + const char *file, int line, + const volatile void *address, long size) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQCreate)( + const char *file, int line, + const volatile void *pcq) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQDestroy)( + const char *file, int line, + const volatile void *pcq) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQPut)( + const char *file, int line, + const volatile void *pcq) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotatePCQGet)( + const char *file, int line, + const volatile void *pcq) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateNewMemory)( + const char *file, int line, + const volatile void *mem, long size) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateExpectRace)( + const char *file, int line, const volatile void *mem, + const char *description) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateFlushExpectedRaces)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBenignRace)( + const char *file, int line, const volatile void *mem, + const char *description) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateBenignRaceSized)( + const char *file, int line, const volatile void *mem, long size, + const char *description) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateMutexIsUsedAsCondVar)( + const char *file, int line, + const volatile void *mu) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateMutexIsNotPHB)( + const char *file, int line, + const volatile void *mu) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateTraceMemory)( + const char *file, int line, + const volatile void *arg) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateThreadName)( + const char *file, int line, + const char *name) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreReadsBegin)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreReadsEnd)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreWritesBegin)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreWritesEnd)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreSyncBegin)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateIgnoreSyncEnd)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateEnableRaceDetection)( + const char *file, int line, int enable) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateNoOp)( + const char *file, int line, + const volatile void *arg) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +void DYNAMIC_ANNOTATIONS_NAME(AnnotateFlushState)( + const char *file, int line) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; + +#if defined(COMPILER_GHS) +#pragma ghs endnowarning +#endif + +#if DYNAMIC_ANNOTATIONS_PROVIDE_RUNNING_ON_VALGRIND == 1 +/* Return non-zero value if running under valgrind. + + If "valgrind.h" is included into dynamic_annotations.c, + the regular valgrind mechanism will be used. + See http://valgrind.org/docs/manual/manual-core-adv.html about + RUNNING_ON_VALGRIND and other valgrind "client requests". + The file "valgrind.h" may be obtained by doing + svn co svn://svn.valgrind.org/valgrind/trunk/include + + If for some reason you can't use "valgrind.h" or want to fake valgrind, + there are two ways to make this function return non-zero: + - Use environment variable: export RUNNING_ON_VALGRIND=1 + - Make your tool intercept the function RunningOnValgrind() and + change its return value. + */ +int RunningOnValgrind(void) DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK; +#endif /* DYNAMIC_ANNOTATIONS_PROVIDE_RUNNING_ON_VALGRIND == 1 */ + +#ifdef __cplusplus +} +#endif + +#if DYNAMIC_ANNOTATIONS_ENABLED != 0 && defined(__cplusplus) + + /* ANNOTATE_UNPROTECTED_READ is the preferred way to annotate racey reads. + + Instead of doing + ANNOTATE_IGNORE_READS_BEGIN(); + ... = x; + ANNOTATE_IGNORE_READS_END(); + one can use + ... = ANNOTATE_UNPROTECTED_READ(x); */ + template <class T> + inline T ANNOTATE_UNPROTECTED_READ(const volatile T &x) { + ANNOTATE_IGNORE_READS_BEGIN(); + T res = x; + ANNOTATE_IGNORE_READS_END(); + return res; + } + /* Apply ANNOTATE_BENIGN_RACE_SIZED to a static variable. */ + #define ANNOTATE_BENIGN_RACE_STATIC(static_var, description) \ + namespace { \ + class static_var ## _annotator { \ + public: \ + static_var ## _annotator() { \ + ANNOTATE_BENIGN_RACE_SIZED(&static_var, \ + sizeof(static_var), \ + # static_var ": " description); \ + } \ + }; \ + static static_var ## _annotator the ## static_var ## _annotator;\ + } +#else /* DYNAMIC_ANNOTATIONS_ENABLED == 0 */ + + #define ANNOTATE_UNPROTECTED_READ(x) (x) + #define ANNOTATE_BENIGN_RACE_STATIC(static_var, description) /* empty */ + +#endif /* DYNAMIC_ANNOTATIONS_ENABLED */ + +#endif /* __DYNAMIC_ANNOTATIONS_H__ */
diff --git a/src/base/third_party/icu/LICENSE b/src/base/third_party/icu/LICENSE new file mode 100644 index 0000000..40282f4 --- /dev/null +++ b/src/base/third_party/icu/LICENSE
@@ -0,0 +1,32 @@ +ICU License - ICU 1.8.1 and later + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1995-2009 International Business Machines Corporation and others + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, and/or sell copies of the Software, and to permit persons +to whom the Software is furnished to do so, provided that the above +copyright notice(s) and this permission notice appear in all copies of +the Software and that both the above copyright notice(s) and this +permission notice appear in supporting documentation. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY +SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, use +or other dealings in this Software without prior written authorization +of the copyright holder.
diff --git a/src/base/third_party/icu/README.chromium b/src/base/third_party/icu/README.chromium new file mode 100644 index 0000000..6a9a15a --- /dev/null +++ b/src/base/third_party/icu/README.chromium
@@ -0,0 +1,16 @@ +Name: ICU +URL: http://site.icu-project.org/ +License: MIT +License File: NOT_SHIPPED + +This file has the relevant components from ICU copied to handle basic +UTF8/16/32 conversions. Components are copied from utf.h utf8.h utf16.h and +utf_impl.c + +The same module appears in third_party/icu, so we don't repeat the license +file here. + +The main change is that U_/U8_/U16_ prefixes have been replaced with +CBU_/CBU8_/CBU16_ (for "Chrome Base") to avoid confusion with the "real" ICU +macros should ICU be in use on the system. For the same reason, the functions +and types have been put in the "base_icu" namespace.
diff --git a/src/base/third_party/icu/icu_utf.cc b/src/base/third_party/icu/icu_utf.cc new file mode 100644 index 0000000..b47c8ac --- /dev/null +++ b/src/base/third_party/icu/icu_utf.cc
@@ -0,0 +1,228 @@ +/* +****************************************************************************** +* +* Copyright (C) 1999-2006, International Business Machines +* Corporation and others. All Rights Reserved. +* +****************************************************************************** +* file name: utf_impl.c +* encoding: US-ASCII +* tab size: 8 (not used) +* indentation:4 +* +* created on: 1999sep13 +* created by: Markus W. Scherer +* +* This file provides implementation functions for macros in the utfXX.h +* that would otherwise be too long as macros. +*/ + +#include "base/third_party/icu/icu_utf.h" + +namespace base_icu { + +/** + * UTF8_ERROR_VALUE_1 and UTF8_ERROR_VALUE_2 are special error values for UTF-8, + * which need 1 or 2 bytes in UTF-8: + * \code + * U+0015 = NAK = Negative Acknowledge, C0 control character + * U+009f = highest C1 control character + * \endcode + * + * These are used by UTF8_..._SAFE macros so that they can return an error value + * that needs the same number of code units (bytes) as were seen by + * a macro. They should be tested with UTF_IS_ERROR() or UTF_IS_VALID(). + * + * @deprecated ICU 2.4. Obsolete, see utf_old.h. + */ +#define CBUTF8_ERROR_VALUE_1 0x15 + +/** + * See documentation on UTF8_ERROR_VALUE_1 for details. + * + * @deprecated ICU 2.4. Obsolete, see utf_old.h. + */ +#define CBUTF8_ERROR_VALUE_2 0x9f + + +/** + * Error value for all UTFs. This code point value will be set by macros with e> + * checking if an error is detected. + * + * @deprecated ICU 2.4. Obsolete, see utf_old.h. + */ +#define CBUTF_ERROR_VALUE 0xffff + +/* + * This table could be replaced on many machines by + * a few lines of assembler code using an + * "index of first 0-bit from msb" instruction and + * one or two more integer instructions. + * + * For example, on an i386, do something like + * - MOV AL, leadByte + * - NOT AL (8-bit, leave b15..b8==0..0, reverse only b7..b0) + * - MOV AH, 0 + * - BSR BX, AX (16-bit) + * - MOV AX, 6 (result) + * - JZ finish (ZF==1 if leadByte==0xff) + * - SUB AX, BX (result) + * -finish: + * (BSR: Bit Scan Reverse, scans for a 1-bit, starting from the MSB) + * + * In Unicode, all UTF-8 byte sequences with more than 4 bytes are illegal; + * lead bytes above 0xf4 are illegal. + * We keep them in this table for skipping long ISO 10646-UTF-8 sequences. + */ +const uint8 +utf8_countTrailBytes[256]={ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, + 3, 3, 3, /* illegal in Unicode */ + 4, 4, 4, 4, /* illegal in Unicode */ + 5, 5, /* illegal in Unicode */ + 0, 0 /* illegal bytes 0xfe and 0xff */ +}; + +static const UChar32 +utf8_minLegal[4]={ 0, 0x80, 0x800, 0x10000 }; + +static const UChar32 +utf8_errorValue[6]={ + CBUTF8_ERROR_VALUE_1, CBUTF8_ERROR_VALUE_2, CBUTF_ERROR_VALUE, 0x10ffff, + 0x3ffffff, 0x7fffffff +}; + +/* + * Handle the non-inline part of the U8_NEXT() macro and its obsolete sibling + * UTF8_NEXT_CHAR_SAFE(). + * + * The "strict" parameter controls the error behavior: + * <0 "Safe" behavior of U8_NEXT(): All illegal byte sequences yield a negative + * code point result. + * 0 Obsolete "safe" behavior of UTF8_NEXT_CHAR_SAFE(..., FALSE): + * All illegal byte sequences yield a positive code point such that this + * result code point would be encoded with the same number of bytes as + * the illegal sequence. + * >0 Obsolete "strict" behavior of UTF8_NEXT_CHAR_SAFE(..., TRUE): + * Same as the obsolete "safe" behavior, but non-characters are also treated + * like illegal sequences. + * + * The special negative (<0) value -2 is used for lenient treatment of surrogate + * code points as legal. Some implementations use this for roundtripping of + * Unicode 16-bit strings that are not well-formed UTF-16, that is, they + * contain unpaired surrogates. + * + * Note that a UBool is the same as an int8_t. + */ +UChar32 +utf8_nextCharSafeBody(const uint8 *s, int32 *pi, int32 length, UChar32 c, UBool strict) { + int32 i=*pi; + uint8 count=CBU8_COUNT_TRAIL_BYTES(c); + if((i)+count<=(length)) { + uint8 trail, illegal=0; + + CBU8_MASK_LEAD_BYTE((c), count); + /* count==0 for illegally leading trail bytes and the illegal bytes 0xfe and 0xff */ + switch(count) { + /* each branch falls through to the next one */ + case 5: + case 4: + /* count>=4 is always illegal: no more than 3 trail bytes in Unicode's UTF-8 */ + illegal=1; + break; + case 3: + trail=s[(i)++]; + (c)=((c)<<6)|(trail&0x3f); + if(c<0x110) { + illegal|=(trail&0xc0)^0x80; + } else { + /* code point>0x10ffff, outside Unicode */ + illegal=1; + break; + } + case 2: + trail=s[(i)++]; + (c)=((c)<<6)|(trail&0x3f); + illegal|=(trail&0xc0)^0x80; + case 1: + trail=s[(i)++]; + (c)=((c)<<6)|(trail&0x3f); + illegal|=(trail&0xc0)^0x80; + break; + case 0: + if(strict>=0) { + return CBUTF8_ERROR_VALUE_1; + } else { + return CBU_SENTINEL; + } + /* no default branch to optimize switch() - all values are covered */ + } + + /* + * All the error handling should return a value + * that needs count bytes so that UTF8_GET_CHAR_SAFE() works right. + * + * Starting with Unicode 3.0.1, non-shortest forms are illegal. + * Starting with Unicode 3.2, surrogate code points must not be + * encoded in UTF-8, and there are no irregular sequences any more. + * + * U8_ macros (new in ICU 2.4) return negative values for error conditions. + */ + + /* correct sequence - all trail bytes have (b7..b6)==(10)? */ + /* illegal is also set if count>=4 */ + if(illegal || (c)<utf8_minLegal[count] || (CBU_IS_SURROGATE(c) && strict!=-2)) { + /* error handling */ + uint8 errorCount=count; + /* don't go beyond this sequence */ + i=*pi; + while(count>0 && CBU8_IS_TRAIL(s[i])) { + ++(i); + --count; + } + if(strict>=0) { + c=utf8_errorValue[errorCount-count]; + } else { + c=CBU_SENTINEL; + } + } else if((strict)>0 && CBU_IS_UNICODE_NONCHAR(c)) { + /* strict: forbid non-characters like U+fffe */ + c=utf8_errorValue[count]; + } + } else /* too few bytes left */ { + /* error handling */ + int32 i0=i; + /* don't just set (i)=(length) in case there is an illegal sequence */ + while((i)<(length) && CBU8_IS_TRAIL(s[i])) { + ++(i); + } + if(strict>=0) { + c=utf8_errorValue[i-i0]; + } else { + c=CBU_SENTINEL; + } + } + *pi=i; + return c; +} + +} // namespace base_icu
diff --git a/src/base/third_party/icu/icu_utf.h b/src/base/third_party/icu/icu_utf.h new file mode 100644 index 0000000..9cb1247 --- /dev/null +++ b/src/base/third_party/icu/icu_utf.h
@@ -0,0 +1,388 @@ +/* +******************************************************************************* +* +* Copyright (C) 1999-2004, International Business Machines +* Corporation and others. All Rights Reserved. +* +******************************************************************************* +* file name: utf.h +* encoding: US-ASCII +* tab size: 8 (not used) +* indentation:4 +* +* created on: 1999sep09 +* created by: Markus W. Scherer +*/ + +#ifndef BASE_THIRD_PARTY_ICU_ICU_UTF_H_ +#define BASE_THIRD_PARTY_ICU_ICU_UTF_H_ + +#include "base/basictypes.h" + +namespace base_icu { + +typedef uint32 UChar32; +typedef int8 UBool; + +// General --------------------------------------------------------------------- +// from utf.h + +/** + * This value is intended for sentinel values for APIs that + * (take or) return single code points (UChar32). + * It is outside of the Unicode code point range 0..0x10ffff. + * + * For example, a "done" or "error" value in a new API + * could be indicated with CBU_SENTINEL. + * + * ICU APIs designed before ICU 2.4 usually define service-specific "done" + * values, mostly 0xffff. + * Those may need to be distinguished from + * actual U+ffff text contents by calling functions like + * CharacterIterator::hasNext() or UnicodeString::length(). + * + * @return -1 + * @see UChar32 + * @stable ICU 2.4 + */ +#define CBU_SENTINEL (-1) + +/** + * Is this code point a Unicode noncharacter? + * @param c 32-bit code point + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU_IS_UNICODE_NONCHAR(c) \ + ((c)>=0xfdd0 && \ + ((uint32)(c)<=0xfdef || ((c)&0xfffe)==0xfffe) && \ + (uint32)(c)<=0x10ffff) + +/** + * Is c a Unicode code point value (0..U+10ffff) + * that can be assigned a character? + * + * Code points that are not characters include: + * - single surrogate code points (U+d800..U+dfff, 2048 code points) + * - the last two code points on each plane (U+__fffe and U+__ffff, 34 code points) + * - U+fdd0..U+fdef (new with Unicode 3.1, 32 code points) + * - the highest Unicode code point value is U+10ffff + * + * This means that all code points below U+d800 are character code points, + * and that boundary is tested first for performance. + * + * @param c 32-bit code point + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU_IS_UNICODE_CHAR(c) \ + ((uint32)(c)<0xd800 || \ + ((uint32)(c)>0xdfff && \ + (uint32)(c)<=0x10ffff && \ + !CBU_IS_UNICODE_NONCHAR(c))) + +/** + * Is this code point a surrogate (U+d800..U+dfff)? + * @param c 32-bit code point + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU_IS_SURROGATE(c) (((c)&0xfffff800)==0xd800) + +/** + * Assuming c is a surrogate code point (U_IS_SURROGATE(c)), + * is it a lead surrogate? + * @param c 32-bit code point + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU_IS_SURROGATE_LEAD(c) (((c)&0x400)==0) + + +// UTF-8 macros ---------------------------------------------------------------- +// from utf8.h + +extern const uint8 utf8_countTrailBytes[256]; + +/** + * Count the trail bytes for a UTF-8 lead byte. + * @internal + */ +#define CBU8_COUNT_TRAIL_BYTES(leadByte) (base_icu::utf8_countTrailBytes[(uint8)leadByte]) + +/** + * Mask a UTF-8 lead byte, leave only the lower bits that form part of the code point value. + * @internal + */ +#define CBU8_MASK_LEAD_BYTE(leadByte, countTrailBytes) ((leadByte)&=(1<<(6-(countTrailBytes)))-1) + +/** + * Does this code unit (byte) encode a code point by itself (US-ASCII 0..0x7f)? + * @param c 8-bit code unit (byte) + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU8_IS_SINGLE(c) (((c)&0x80)==0) + +/** + * Is this code unit (byte) a UTF-8 lead byte? + * @param c 8-bit code unit (byte) + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU8_IS_LEAD(c) ((uint8)((c)-0xc0)<0x3e) + +/** + * Is this code unit (byte) a UTF-8 trail byte? + * @param c 8-bit code unit (byte) + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU8_IS_TRAIL(c) (((c)&0xc0)==0x80) + +/** + * How many code units (bytes) are used for the UTF-8 encoding + * of this Unicode code point? + * @param c 32-bit code point + * @return 1..4, or 0 if c is a surrogate or not a Unicode code point + * @stable ICU 2.4 + */ +#define CBU8_LENGTH(c) \ + ((uint32)(c)<=0x7f ? 1 : \ + ((uint32)(c)<=0x7ff ? 2 : \ + ((uint32)(c)<=0xd7ff ? 3 : \ + ((uint32)(c)<=0xdfff || (uint32)(c)>0x10ffff ? 0 : \ + ((uint32)(c)<=0xffff ? 3 : 4)\ + ) \ + ) \ + ) \ + ) + +/** + * The maximum number of UTF-8 code units (bytes) per Unicode code point (U+0000..U+10ffff). + * @return 4 + * @stable ICU 2.4 + */ +#define CBU8_MAX_LENGTH 4 + +/** + * Function for handling "next code point" with error-checking. + * @internal + */ +UChar32 utf8_nextCharSafeBody(const uint8 *s, int32 *pi, int32 length, UChar32 c, UBool strict); + +/** + * Get a code point from a string at a code point boundary offset, + * and advance the offset to the next code point boundary. + * (Post-incrementing forward iteration.) + * "Safe" macro, checks for illegal sequences and for string boundaries. + * + * The offset may point to the lead byte of a multi-byte sequence, + * in which case the macro will read the whole sequence. + * If the offset points to a trail byte or an illegal UTF-8 sequence, then + * c is set to a negative value. + * + * @param s const uint8 * string + * @param i string offset, i<length + * @param length string length + * @param c output UChar32 variable, set to <0 in case of an error + * @see CBU8_NEXT_UNSAFE + * @stable ICU 2.4 + */ +#define CBU8_NEXT(s, i, length, c) { \ + (c)=(s)[(i)++]; \ + if(((uint8)(c))>=0x80) { \ + if(CBU8_IS_LEAD(c)) { \ + (c)=base_icu::utf8_nextCharSafeBody((const uint8 *)s, &(i), (int32)(length), c, -1); \ + } else { \ + (c)=CBU_SENTINEL; \ + } \ + } \ +} + +/** + * Append a code point to a string, overwriting 1 to 4 bytes. + * The offset points to the current end of the string contents + * and is advanced (post-increment). + * "Unsafe" macro, assumes a valid code point and sufficient space in the string. + * Otherwise, the result is undefined. + * + * @param s const uint8 * string buffer + * @param i string offset + * @param c code point to append + * @see CBU8_APPEND + * @stable ICU 2.4 + */ +#define CBU8_APPEND_UNSAFE(s, i, c) { \ + if((uint32)(c)<=0x7f) { \ + (s)[(i)++]=(uint8)(c); \ + } else { \ + if((uint32)(c)<=0x7ff) { \ + (s)[(i)++]=(uint8)(((c)>>6)|0xc0); \ + } else { \ + if((uint32)(c)<=0xffff) { \ + (s)[(i)++]=(uint8)(((c)>>12)|0xe0); \ + } else { \ + (s)[(i)++]=(uint8)(((c)>>18)|0xf0); \ + (s)[(i)++]=(uint8)((((c)>>12)&0x3f)|0x80); \ + } \ + (s)[(i)++]=(uint8)((((c)>>6)&0x3f)|0x80); \ + } \ + (s)[(i)++]=(uint8)(((c)&0x3f)|0x80); \ + } \ +} + +// UTF-16 macros --------------------------------------------------------------- +// from utf16.h + +/** + * Does this code unit alone encode a code point (BMP, not a surrogate)? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU16_IS_SINGLE(c) !CBU_IS_SURROGATE(c) + +/** + * Is this code unit a lead surrogate (U+d800..U+dbff)? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU16_IS_LEAD(c) (((c)&0xfffffc00)==0xd800) + +/** + * Is this code unit a trail surrogate (U+dc00..U+dfff)? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU16_IS_TRAIL(c) (((c)&0xfffffc00)==0xdc00) + +/** + * Is this code unit a surrogate (U+d800..U+dfff)? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU16_IS_SURROGATE(c) CBU_IS_SURROGATE(c) + +/** + * Assuming c is a surrogate code point (U16_IS_SURROGATE(c)), + * is it a lead surrogate? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define CBU16_IS_SURROGATE_LEAD(c) (((c)&0x400)==0) + +/** + * Helper constant for CBU16_GET_SUPPLEMENTARY. + * @internal + */ +#define CBU16_SURROGATE_OFFSET ((0xd800<<10UL)+0xdc00-0x10000) + +/** + * Get a supplementary code point value (U+10000..U+10ffff) + * from its lead and trail surrogates. + * The result is undefined if the input values are not + * lead and trail surrogates. + * + * @param lead lead surrogate (U+d800..U+dbff) + * @param trail trail surrogate (U+dc00..U+dfff) + * @return supplementary code point (U+10000..U+10ffff) + * @stable ICU 2.4 + */ +#define CBU16_GET_SUPPLEMENTARY(lead, trail) \ + (((base_icu::UChar32)(lead)<<10UL)+(base_icu::UChar32)(trail)-CBU16_SURROGATE_OFFSET) + + +/** + * Get the lead surrogate (0xd800..0xdbff) for a + * supplementary code point (0x10000..0x10ffff). + * @param supplementary 32-bit code point (U+10000..U+10ffff) + * @return lead surrogate (U+d800..U+dbff) for supplementary + * @stable ICU 2.4 + */ +#define CBU16_LEAD(supplementary) (UChar)(((supplementary)>>10)+0xd7c0) + +/** + * Get the trail surrogate (0xdc00..0xdfff) for a + * supplementary code point (0x10000..0x10ffff). + * @param supplementary 32-bit code point (U+10000..U+10ffff) + * @return trail surrogate (U+dc00..U+dfff) for supplementary + * @stable ICU 2.4 + */ +#define CBU16_TRAIL(supplementary) (UChar)(((supplementary)&0x3ff)|0xdc00) + +/** + * How many 16-bit code units are used to encode this Unicode code point? (1 or 2) + * The result is not defined if c is not a Unicode code point (U+0000..U+10ffff). + * @param c 32-bit code point + * @return 1 or 2 + * @stable ICU 2.4 + */ +#define CBU16_LENGTH(c) ((uint32)(c)<=0xffff ? 1 : 2) + +/** + * The maximum number of 16-bit code units per Unicode code point (U+0000..U+10ffff). + * @return 2 + * @stable ICU 2.4 + */ +#define CBU16_MAX_LENGTH 2 + +/** + * Get a code point from a string at a code point boundary offset, + * and advance the offset to the next code point boundary. + * (Post-incrementing forward iteration.) + * "Safe" macro, handles unpaired surrogates and checks for string boundaries. + * + * The offset may point to the lead surrogate unit + * for a supplementary code point, in which case the macro will read + * the following trail surrogate as well. + * If the offset points to a trail surrogate or + * to a single, unpaired lead surrogate, then that itself + * will be returned as the code point. + * + * @param s const UChar * string + * @param i string offset, i<length + * @param length string length + * @param c output UChar32 variable + * @stable ICU 2.4 + */ +#define CBU16_NEXT(s, i, length, c) { \ + (c)=(s)[(i)++]; \ + if(CBU16_IS_LEAD(c)) { \ + uint16 __c2; \ + if((i)<(length) && CBU16_IS_TRAIL(__c2=(s)[(i)])) { \ + ++(i); \ + (c)=CBU16_GET_SUPPLEMENTARY((c), __c2); \ + } \ + } \ +} + +/** + * Append a code point to a string, overwriting 1 or 2 code units. + * The offset points to the current end of the string contents + * and is advanced (post-increment). + * "Unsafe" macro, assumes a valid code point and sufficient space in the string. + * Otherwise, the result is undefined. + * + * @param s const UChar * string buffer + * @param i string offset + * @param c code point to append + * @see CBU16_APPEND + * @stable ICU 2.4 + */ +#define CBU16_APPEND_UNSAFE(s, i, c) { \ + if((uint32)(c)<=0xffff) { \ + (s)[(i)++]=(uint16)(c); \ + } else { \ + (s)[(i)++]=(uint16)(((c)>>10)+0xd7c0); \ + (s)[(i)++]=(uint16)(((c)&0x3ff)|0xdc00); \ + } \ +} + +} // namesapce base_icu + +#endif // BASE_THIRD_PARTY_ICU_ICU_UTF_H_
diff --git a/src/base/third_party/nspr/LICENSE b/src/base/third_party/nspr/LICENSE new file mode 100644 index 0000000..eba7b77 --- /dev/null +++ b/src/base/third_party/nspr/LICENSE
@@ -0,0 +1,35 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape Portable Runtime (NSPR). + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */
diff --git a/src/base/third_party/nspr/README.chromium b/src/base/third_party/nspr/README.chromium new file mode 100644 index 0000000..3659a2c --- /dev/null +++ b/src/base/third_party/nspr/README.chromium
@@ -0,0 +1,3 @@ +Name: Netscape Portable Runtime (NSPR) +URL: http://www.mozilla.org/projects/nspr/ +License: MPL 1.1/GPL 2.0/LGPL 2.1
diff --git a/src/base/third_party/nspr/prcpucfg.h b/src/base/third_party/nspr/prcpucfg.h new file mode 100644 index 0000000..c362f4b --- /dev/null +++ b/src/base/third_party/nspr/prcpucfg.h
@@ -0,0 +1,53 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_THIRD_PARTY_NSPR_PRCPUCFG_H__ +#define BASE_THIRD_PARTY_NSPR_PRCPUCFG_H__ + +#if defined(OS_STARBOARD) +#include "base/third_party/nspr/prcpucfg_starboard.h" +#elif defined(WIN32) || defined (COBALT_WIN) +#include "base/third_party/nspr/prcpucfg_win.h" +#elif defined(__APPLE__) +#include "base/third_party/nspr/prcpucfg_mac.h" +#elif defined(__native_client__) +#include "base/third_party/nspr/prcpucfg_nacl.h" +#elif defined(__linux__) || defined(ANDROID) || defined(__LB_SHELL__) +#include "base/third_party/nspr/prcpucfg_linux.h" +#elif defined(__FreeBSD__) +#include "base/third_party/nspr/prcpucfg_freebsd.h" +#elif defined(__OpenBSD__) +#include "base/third_party/nspr/prcpucfg_openbsd.h" +#elif defined(__sun) +#include "base/third_party/nspr/prcpucfg_solaris.h" +#else +#error Provide a prcpucfg.h appropriate for your platform +#endif + +#endif // BASE_THIRD_PARTY_NSPR_PRCPUCFG_H__
diff --git a/src/base/third_party/nspr/prcpucfg_freebsd.h b/src/base/third_party/nspr/prcpucfg_freebsd.h new file mode 100644 index 0000000..76d3542 --- /dev/null +++ b/src/base/third_party/nspr/prcpucfg_freebsd.h
@@ -0,0 +1,337 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape Portable Runtime (NSPR). + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nspr_cpucfg___ +#define nspr_cpucfg___ + +#ifndef XP_UNIX +#define XP_UNIX +#endif + +#ifndef FREEBSD +#define FREEBSD +#endif + +#define PR_AF_INET6 28 /* same as AF_INET6 */ + +#ifndef HAVE_LONG_LONG +#define HAVE_LONG_LONG +#endif + +#if defined(__i386__) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#undef HAVE_ALIGNED_DOUBLES +#undef HAVE_ALIGNED_LONGLONGS + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 4 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 4 +#define PR_ALIGN_OF_POINTER 4 + +#elif defined(__alpha__) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define HAVE_ALIGNED_DOUBLES +#define HAVE_ALIGNED_LONGLONGS +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 + +#elif defined(__sparc__) + +#undef IS_LITTLE_ENDIAN +#define IS_BIG_ENDIAN 1 +#define HAVE_ALIGNED_DOUBLES +#define HAVE_ALIGNED_LONGLONGS +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 + +#elif defined(__ia64__) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define HAVE_ALIGNED_DOUBLES +#define HAVE_ALIGNED_LONGLONGS +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 +#define PR_ALIGN_OF_WORD 8 + +#elif defined(__amd64__) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define HAVE_ALIGNED_DOUBLES +#define HAVE_ALIGNED_LONGLONGS +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 +#define PR_ALIGN_OF_WORD 8 + +#else + +#error "Unknown CPU architecture" + +#endif + +#ifndef NO_NSPR_10_SUPPORT + +#define BYTES_PER_BYTE PR_BYTES_PER_BYTE +#define BYTES_PER_SHORT PR_BYTES_PER_SHORT +#define BYTES_PER_INT PR_BYTES_PER_INT +#define BYTES_PER_INT64 PR_BYTES_PER_INT64 +#define BYTES_PER_LONG PR_BYTES_PER_LONG +#define BYTES_PER_FLOAT PR_BYTES_PER_FLOAT +#define BYTES_PER_DOUBLE PR_BYTES_PER_DOUBLE +#define BYTES_PER_WORD PR_BYTES_PER_WORD +#define BYTES_PER_DWORD PR_BYTES_PER_DWORD + +#define BITS_PER_BYTE PR_BITS_PER_BYTE +#define BITS_PER_SHORT PR_BITS_PER_SHORT +#define BITS_PER_INT PR_BITS_PER_INT +#define BITS_PER_INT64 PR_BITS_PER_INT64 +#define BITS_PER_LONG PR_BITS_PER_LONG +#define BITS_PER_FLOAT PR_BITS_PER_FLOAT +#define BITS_PER_DOUBLE PR_BITS_PER_DOUBLE +#define BITS_PER_WORD PR_BITS_PER_WORD + +#define BITS_PER_BYTE_LOG2 PR_BITS_PER_BYTE_LOG2 +#define BITS_PER_SHORT_LOG2 PR_BITS_PER_SHORT_LOG2 +#define BITS_PER_INT_LOG2 PR_BITS_PER_INT_LOG2 +#define BITS_PER_INT64_LOG2 PR_BITS_PER_INT64_LOG2 +#define BITS_PER_LONG_LOG2 PR_BITS_PER_LONG_LOG2 +#define BITS_PER_FLOAT_LOG2 PR_BITS_PER_FLOAT_LOG2 +#define BITS_PER_DOUBLE_LOG2 PR_BITS_PER_DOUBLE_LOG2 +#define BITS_PER_WORD_LOG2 PR_BITS_PER_WORD_LOG2 + +#define ALIGN_OF_SHORT PR_ALIGN_OF_SHORT +#define ALIGN_OF_INT PR_ALIGN_OF_INT +#define ALIGN_OF_LONG PR_ALIGN_OF_LONG +#define ALIGN_OF_INT64 PR_ALIGN_OF_INT64 +#define ALIGN_OF_FLOAT PR_ALIGN_OF_FLOAT +#define ALIGN_OF_DOUBLE PR_ALIGN_OF_DOUBLE +#define ALIGN_OF_POINTER PR_ALIGN_OF_POINTER +#define ALIGN_OF_WORD PR_ALIGN_OF_WORD + +#define BYTES_PER_WORD_LOG2 PR_BYTES_PER_WORD_LOG2 +#define BYTES_PER_DWORD_LOG2 PR_BYTES_PER_DWORD_LOG2 +#define WORDS_PER_DWORD_LOG2 PR_WORDS_PER_DWORD_LOG2 + +#endif /* NO_NSPR_10_SUPPORT */ + +#endif /* nspr_cpucfg___ */
diff --git a/src/base/third_party/nspr/prcpucfg_linux.h b/src/base/third_party/nspr/prcpucfg_linux.h new file mode 100644 index 0000000..776c21e --- /dev/null +++ b/src/base/third_party/nspr/prcpucfg_linux.h
@@ -0,0 +1,707 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape Portable Runtime (NSPR). + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nspr_cpucfg___ +#define nspr_cpucfg___ + +#ifndef XP_UNIX +#define XP_UNIX +#endif + +#ifndef LINUX +#define LINUX +#endif + +#define PR_AF_INET6 10 /* same as AF_INET6 */ + +#ifdef __powerpc64__ + +#undef IS_LITTLE_ENDIAN +#define IS_BIG_ENDIAN 1 +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 +#define PR_ALIGN_OF_WORD 8 + +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(__powerpc__) + +#undef IS_LITTLE_ENDIAN +#define IS_BIG_ENDIAN 1 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 4 +#define PR_ALIGN_OF_WORD 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(__alpha) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 +#define PR_ALIGN_OF_WORD 8 + +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(__ia64__) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 +#define PR_ALIGN_OF_WORD 8 + +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(__x86_64__) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 +#define PR_ALIGN_OF_WORD 8 + +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(__mc68000__) + +#undef IS_LITTLE_ENDIAN +#define IS_BIG_ENDIAN 1 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 2 +#define PR_ALIGN_OF_LONG 2 +#define PR_ALIGN_OF_INT64 2 +#define PR_ALIGN_OF_FLOAT 2 +#define PR_ALIGN_OF_DOUBLE 2 +#define PR_ALIGN_OF_POINTER 2 +#define PR_ALIGN_OF_WORD 2 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(__sparc__) + +#undef IS_LITTLE_ENDIAN +#define IS_BIG_ENDIAN 1 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 4 +#define PR_ALIGN_OF_WORD 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(__i386__) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 4 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 4 +#define PR_ALIGN_OF_POINTER 4 +#define PR_ALIGN_OF_WORD 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(__mips__) + +#ifdef __MIPSEB__ +#define IS_BIG_ENDIAN 1 +#undef IS_LITTLE_ENDIAN +#elif defined(__MIPSEL__) +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#else +#error "Unknown MIPS endianness." +#endif + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 4 +#define PR_ALIGN_OF_WORD 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(__arm__) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 4 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 4 +#define PR_ALIGN_OF_POINTER 4 +#define PR_ALIGN_OF_WORD 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(__hppa__) + +#undef IS_LITTLE_ENDIAN +#define IS_BIG_ENDIAN 1 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 4 +#define PR_ALIGN_OF_WORD 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(__s390x__) + +#define IS_BIG_ENDIAN 1 +#undef IS_LITTLE_ENDIAN +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 +#define PR_ALIGN_OF_WORD 8 + +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(__s390__) + +#define IS_BIG_ENDIAN 1 +#undef IS_LITTLE_ENDIAN + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 4 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 4 +#define PR_ALIGN_OF_POINTER 4 +#define PR_ALIGN_OF_WORD 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#else + +#error "Unknown CPU architecture" + +#endif + +#define HAVE_LONG_LONG +#if PR_ALIGN_OF_DOUBLE == 8 +#define HAVE_ALIGNED_DOUBLES +#endif +#if PR_ALIGN_OF_INT64 == 8 +#define HAVE_ALIGNED_LONGLONGS +#endif + +#ifndef NO_NSPR_10_SUPPORT + +#define BYTES_PER_BYTE PR_BYTES_PER_BYTE +#define BYTES_PER_SHORT PR_BYTES_PER_SHORT +#define BYTES_PER_INT PR_BYTES_PER_INT +#define BYTES_PER_INT64 PR_BYTES_PER_INT64 +#define BYTES_PER_LONG PR_BYTES_PER_LONG +#define BYTES_PER_FLOAT PR_BYTES_PER_FLOAT +#define BYTES_PER_DOUBLE PR_BYTES_PER_DOUBLE +#define BYTES_PER_WORD PR_BYTES_PER_WORD +#define BYTES_PER_DWORD PR_BYTES_PER_DWORD + +#define BITS_PER_BYTE PR_BITS_PER_BYTE +#define BITS_PER_SHORT PR_BITS_PER_SHORT +#define BITS_PER_INT PR_BITS_PER_INT +#define BITS_PER_INT64 PR_BITS_PER_INT64 +#define BITS_PER_LONG PR_BITS_PER_LONG +#define BITS_PER_FLOAT PR_BITS_PER_FLOAT +#define BITS_PER_DOUBLE PR_BITS_PER_DOUBLE +#define BITS_PER_WORD PR_BITS_PER_WORD + +#define BITS_PER_BYTE_LOG2 PR_BITS_PER_BYTE_LOG2 +#define BITS_PER_SHORT_LOG2 PR_BITS_PER_SHORT_LOG2 +#define BITS_PER_INT_LOG2 PR_BITS_PER_INT_LOG2 +#define BITS_PER_INT64_LOG2 PR_BITS_PER_INT64_LOG2 +#define BITS_PER_LONG_LOG2 PR_BITS_PER_LONG_LOG2 +#define BITS_PER_FLOAT_LOG2 PR_BITS_PER_FLOAT_LOG2 +#define BITS_PER_DOUBLE_LOG2 PR_BITS_PER_DOUBLE_LOG2 +#define BITS_PER_WORD_LOG2 PR_BITS_PER_WORD_LOG2 + +#define ALIGN_OF_SHORT PR_ALIGN_OF_SHORT +#define ALIGN_OF_INT PR_ALIGN_OF_INT +#define ALIGN_OF_LONG PR_ALIGN_OF_LONG +#define ALIGN_OF_INT64 PR_ALIGN_OF_INT64 +#define ALIGN_OF_FLOAT PR_ALIGN_OF_FLOAT +#define ALIGN_OF_DOUBLE PR_ALIGN_OF_DOUBLE +#define ALIGN_OF_POINTER PR_ALIGN_OF_POINTER +#define ALIGN_OF_WORD PR_ALIGN_OF_WORD + +#define BYTES_PER_WORD_LOG2 PR_BYTES_PER_WORD_LOG2 +#define BYTES_PER_DWORD_LOG2 PR_BYTES_PER_DWORD_LOG2 +#define WORDS_PER_DWORD_LOG2 PR_WORDS_PER_DWORD_LOG2 + +#endif /* NO_NSPR_10_SUPPORT */ + +#endif /* nspr_cpucfg___ */
diff --git a/src/base/third_party/nspr/prcpucfg_mac.h b/src/base/third_party/nspr/prcpucfg_mac.h new file mode 100644 index 0000000..60bea8e --- /dev/null +++ b/src/base/third_party/nspr/prcpucfg_mac.h
@@ -0,0 +1,197 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape Portable Runtime (NSPR). + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nspr_cpucfg___ +#define nspr_cpucfg___ + +#ifndef XP_UNIX +#define XP_UNIX +#endif + +#define PR_AF_INET6 30 /* same as AF_INET6 */ + +#ifdef __LITTLE_ENDIAN__ +#undef IS_BIG_ENDIAN +#define IS_LITTLE_ENDIAN 1 +#else +#undef IS_LITTLE_ENDIAN +#define IS_BIG_ENDIAN 1 +#endif + +#ifdef __x86_64__ +#define IS_64 +#endif + +#ifndef HAVE_LONG_LONG +#define HAVE_LONG_LONG +#endif +#undef HAVE_ALIGNED_DOUBLES +#define HAVE_ALIGNED_LONGLONGS 1 + +#ifdef IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 +#define PR_BITS_PER_DWORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 +#define PR_BITS_PER_DWORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 +#define PR_ALIGN_OF_WORD 8 +#define PR_ALIGN_OF_DWORD 8 + +#else /* IS_64 */ + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 +#define PR_BITS_PER_DWORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 4 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 4 +#define PR_ALIGN_OF_POINTER 4 +#define PR_ALIGN_OF_WORD 4 + +#endif /* IS_64 */ + +#ifndef NO_NSPR_10_SUPPORT + +#define BYTES_PER_BYTE PR_BYTES_PER_BYTE +#define BYTES_PER_SHORT PR_BYTES_PER_SHORT +#define BYTES_PER_INT PR_BYTES_PER_INT +#define BYTES_PER_INT64 PR_BYTES_PER_INT64 +#define BYTES_PER_LONG PR_BYTES_PER_LONG +#define BYTES_PER_FLOAT PR_BYTES_PER_FLOAT +#define BYTES_PER_DOUBLE PR_BYTES_PER_DOUBLE +#define BYTES_PER_WORD PR_BYTES_PER_WORD +#define BYTES_PER_DWORD PR_BYTES_PER_DWORD + +#define BITS_PER_BYTE PR_BITS_PER_BYTE +#define BITS_PER_SHORT PR_BITS_PER_SHORT +#define BITS_PER_INT PR_BITS_PER_INT +#define BITS_PER_INT64 PR_BITS_PER_INT64 +#define BITS_PER_LONG PR_BITS_PER_LONG +#define BITS_PER_FLOAT PR_BITS_PER_FLOAT +#define BITS_PER_DOUBLE PR_BITS_PER_DOUBLE +#define BITS_PER_WORD PR_BITS_PER_WORD + +#define BITS_PER_BYTE_LOG2 PR_BITS_PER_BYTE_LOG2 +#define BITS_PER_SHORT_LOG2 PR_BITS_PER_SHORT_LOG2 +#define BITS_PER_INT_LOG2 PR_BITS_PER_INT_LOG2 +#define BITS_PER_INT64_LOG2 PR_BITS_PER_INT64_LOG2 +#define BITS_PER_LONG_LOG2 PR_BITS_PER_LONG_LOG2 +#define BITS_PER_FLOAT_LOG2 PR_BITS_PER_FLOAT_LOG2 +#define BITS_PER_DOUBLE_LOG2 PR_BITS_PER_DOUBLE_LOG2 +#define BITS_PER_WORD_LOG2 PR_BITS_PER_WORD_LOG2 + +#define ALIGN_OF_SHORT PR_ALIGN_OF_SHORT +#define ALIGN_OF_INT PR_ALIGN_OF_INT +#define ALIGN_OF_LONG PR_ALIGN_OF_LONG +#define ALIGN_OF_INT64 PR_ALIGN_OF_INT64 +#define ALIGN_OF_FLOAT PR_ALIGN_OF_FLOAT +#define ALIGN_OF_DOUBLE PR_ALIGN_OF_DOUBLE +#define ALIGN_OF_POINTER PR_ALIGN_OF_POINTER +#define ALIGN_OF_WORD PR_ALIGN_OF_WORD + +#define BYTES_PER_WORD_LOG2 PR_BYTES_PER_WORD_LOG2 +#define BYTES_PER_DWORD_LOG2 PR_BYTES_PER_DWORD_LOG2 +#define WORDS_PER_DWORD_LOG2 PR_WORDS_PER_DWORD_LOG2 + +#endif /* NO_NSPR_10_SUPPORT */ + +#endif /* nspr_cpucfg___ */ +
diff --git a/src/base/third_party/nspr/prcpucfg_nacl.h b/src/base/third_party/nspr/prcpucfg_nacl.h new file mode 100644 index 0000000..d8602d3 --- /dev/null +++ b/src/base/third_party/nspr/prcpucfg_nacl.h
@@ -0,0 +1,246 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape Portable Runtime (NSPR). + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nspr_cpucfg___ +#define nspr_cpucfg___ + +#ifndef XP_UNIX +#define XP_UNIX +#endif + +#ifndef LINUX +#define LINUX +#endif + +#define PR_AF_INET6 10 /* same as AF_INET6 */ + +#if defined(__x86_64__) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 +#define PR_ALIGN_OF_WORD 8 + +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(__i386__) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 4 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 4 +#define PR_ALIGN_OF_POINTER 4 +#define PR_ALIGN_OF_WORD 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(__arm__) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 4 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 4 +#define PR_ALIGN_OF_POINTER 4 +#define PR_ALIGN_OF_WORD 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#else + +#error "Unknown CPU architecture" + +#endif + +#define HAVE_LONG_LONG +#if PR_ALIGN_OF_DOUBLE == 8 +#define HAVE_ALIGNED_DOUBLES +#endif +#if PR_ALIGN_OF_INT64 == 8 +#define HAVE_ALIGNED_LONGLONGS +#endif + +#ifndef NO_NSPR_10_SUPPORT + +#define BYTES_PER_BYTE PR_BYTES_PER_BYTE +#define BYTES_PER_SHORT PR_BYTES_PER_SHORT +#define BYTES_PER_INT PR_BYTES_PER_INT +#define BYTES_PER_INT64 PR_BYTES_PER_INT64 +#define BYTES_PER_LONG PR_BYTES_PER_LONG +#define BYTES_PER_FLOAT PR_BYTES_PER_FLOAT +#define BYTES_PER_DOUBLE PR_BYTES_PER_DOUBLE +#define BYTES_PER_WORD PR_BYTES_PER_WORD +#define BYTES_PER_DWORD PR_BYTES_PER_DWORD + +#define BITS_PER_BYTE PR_BITS_PER_BYTE +#define BITS_PER_SHORT PR_BITS_PER_SHORT +#define BITS_PER_INT PR_BITS_PER_INT +#define BITS_PER_INT64 PR_BITS_PER_INT64 +#define BITS_PER_LONG PR_BITS_PER_LONG +#define BITS_PER_FLOAT PR_BITS_PER_FLOAT +#define BITS_PER_DOUBLE PR_BITS_PER_DOUBLE +#define BITS_PER_WORD PR_BITS_PER_WORD + +#define BITS_PER_BYTE_LOG2 PR_BITS_PER_BYTE_LOG2 +#define BITS_PER_SHORT_LOG2 PR_BITS_PER_SHORT_LOG2 +#define BITS_PER_INT_LOG2 PR_BITS_PER_INT_LOG2 +#define BITS_PER_INT64_LOG2 PR_BITS_PER_INT64_LOG2 +#define BITS_PER_LONG_LOG2 PR_BITS_PER_LONG_LOG2 +#define BITS_PER_FLOAT_LOG2 PR_BITS_PER_FLOAT_LOG2 +#define BITS_PER_DOUBLE_LOG2 PR_BITS_PER_DOUBLE_LOG2 +#define BITS_PER_WORD_LOG2 PR_BITS_PER_WORD_LOG2 + +#define ALIGN_OF_SHORT PR_ALIGN_OF_SHORT +#define ALIGN_OF_INT PR_ALIGN_OF_INT +#define ALIGN_OF_LONG PR_ALIGN_OF_LONG +#define ALIGN_OF_INT64 PR_ALIGN_OF_INT64 +#define ALIGN_OF_FLOAT PR_ALIGN_OF_FLOAT +#define ALIGN_OF_DOUBLE PR_ALIGN_OF_DOUBLE +#define ALIGN_OF_POINTER PR_ALIGN_OF_POINTER +#define ALIGN_OF_WORD PR_ALIGN_OF_WORD + +#define BYTES_PER_WORD_LOG2 PR_BYTES_PER_WORD_LOG2 +#define BYTES_PER_DWORD_LOG2 PR_BYTES_PER_DWORD_LOG2 +#define WORDS_PER_DWORD_LOG2 PR_WORDS_PER_DWORD_LOG2 + +#endif /* NO_NSPR_10_SUPPORT */ + +#endif /* nspr_cpucfg___ */
diff --git a/src/base/third_party/nspr/prcpucfg_openbsd.h b/src/base/third_party/nspr/prcpucfg_openbsd.h new file mode 100644 index 0000000..93c5b32 --- /dev/null +++ b/src/base/third_party/nspr/prcpucfg_openbsd.h
@@ -0,0 +1,337 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape Portable Runtime (NSPR). + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nspr_cpucfg___ +#define nspr_cpucfg___ + +#ifndef XP_UNIX +#define XP_UNIX +#endif + +#ifndef OPENBSD +#define OPENBSD +#endif + +#define PR_AF_INET6 28 /* same as AF_INET6 */ + +#ifndef HAVE_LONG_LONG +#define HAVE_LONG_LONG +#endif + +#if defined(__i386__) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#undef HAVE_ALIGNED_DOUBLES +#undef HAVE_ALIGNED_LONGLONGS + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 4 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 4 +#define PR_ALIGN_OF_POINTER 4 + +#elif defined(__alpha__) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define HAVE_ALIGNED_DOUBLES +#define HAVE_ALIGNED_LONGLONGS +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 + +#elif defined(__sparc__) + +#undef IS_LITTLE_ENDIAN +#define IS_BIG_ENDIAN 1 +#define HAVE_ALIGNED_DOUBLES +#define HAVE_ALIGNED_LONGLONGS +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 + +#elif defined(__ia64__) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define HAVE_ALIGNED_DOUBLES +#define HAVE_ALIGNED_LONGLONGS +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 +#define PR_ALIGN_OF_WORD 8 + +#elif defined(__amd64__) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define HAVE_ALIGNED_DOUBLES +#define HAVE_ALIGNED_LONGLONGS +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 +#define PR_ALIGN_OF_WORD 8 + +#else + +#error "Unknown CPU architecture" + +#endif + +#ifndef NO_NSPR_10_SUPPORT + +#define BYTES_PER_BYTE PR_BYTES_PER_BYTE +#define BYTES_PER_SHORT PR_BYTES_PER_SHORT +#define BYTES_PER_INT PR_BYTES_PER_INT +#define BYTES_PER_INT64 PR_BYTES_PER_INT64 +#define BYTES_PER_LONG PR_BYTES_PER_LONG +#define BYTES_PER_FLOAT PR_BYTES_PER_FLOAT +#define BYTES_PER_DOUBLE PR_BYTES_PER_DOUBLE +#define BYTES_PER_WORD PR_BYTES_PER_WORD +#define BYTES_PER_DWORD PR_BYTES_PER_DWORD + +#define BITS_PER_BYTE PR_BITS_PER_BYTE +#define BITS_PER_SHORT PR_BITS_PER_SHORT +#define BITS_PER_INT PR_BITS_PER_INT +#define BITS_PER_INT64 PR_BITS_PER_INT64 +#define BITS_PER_LONG PR_BITS_PER_LONG +#define BITS_PER_FLOAT PR_BITS_PER_FLOAT +#define BITS_PER_DOUBLE PR_BITS_PER_DOUBLE +#define BITS_PER_WORD PR_BITS_PER_WORD + +#define BITS_PER_BYTE_LOG2 PR_BITS_PER_BYTE_LOG2 +#define BITS_PER_SHORT_LOG2 PR_BITS_PER_SHORT_LOG2 +#define BITS_PER_INT_LOG2 PR_BITS_PER_INT_LOG2 +#define BITS_PER_INT64_LOG2 PR_BITS_PER_INT64_LOG2 +#define BITS_PER_LONG_LOG2 PR_BITS_PER_LONG_LOG2 +#define BITS_PER_FLOAT_LOG2 PR_BITS_PER_FLOAT_LOG2 +#define BITS_PER_DOUBLE_LOG2 PR_BITS_PER_DOUBLE_LOG2 +#define BITS_PER_WORD_LOG2 PR_BITS_PER_WORD_LOG2 + +#define ALIGN_OF_SHORT PR_ALIGN_OF_SHORT +#define ALIGN_OF_INT PR_ALIGN_OF_INT +#define ALIGN_OF_LONG PR_ALIGN_OF_LONG +#define ALIGN_OF_INT64 PR_ALIGN_OF_INT64 +#define ALIGN_OF_FLOAT PR_ALIGN_OF_FLOAT +#define ALIGN_OF_DOUBLE PR_ALIGN_OF_DOUBLE +#define ALIGN_OF_POINTER PR_ALIGN_OF_POINTER +#define ALIGN_OF_WORD PR_ALIGN_OF_WORD + +#define BYTES_PER_WORD_LOG2 PR_BYTES_PER_WORD_LOG2 +#define BYTES_PER_DWORD_LOG2 PR_BYTES_PER_DWORD_LOG2 +#define WORDS_PER_DWORD_LOG2 PR_WORDS_PER_DWORD_LOG2 + +#endif /* NO_NSPR_10_SUPPORT */ + +#endif /* nspr_cpucfg___ */
diff --git a/src/base/third_party/nspr/prcpucfg_solaris.h b/src/base/third_party/nspr/prcpucfg_solaris.h new file mode 100644 index 0000000..81313e5 --- /dev/null +++ b/src/base/third_party/nspr/prcpucfg_solaris.h
@@ -0,0 +1,203 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape Portable Runtime (NSPR). + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nspr_cpucfg___ +#define nspr_cpucfg___ + +#ifndef XP_UNIX +#define XP_UNIX +#endif + +#ifndef SOLARIS +#define SOLARIS +#endif + +#define PR_AF_INET6 26 /* same as AF_INET6 */ + +#if defined(sparc) || defined(__sparc) +#undef IS_LITTLE_ENDIAN +#define IS_BIG_ENDIAN 1 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_DOUBLE 8 +#if defined(__sparcv9) +#define IS_64 +#endif +#elif defined(__x86_64) +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_DOUBLE 8 +#define IS_64 +#elif defined(i386) || defined(__i386) +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define PR_ALIGN_OF_INT64 4 +#define PR_ALIGN_OF_DOUBLE 4 +#else +#error unknown processor +#endif + +#ifdef IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_POINTER 8 + +#else /* IS_64 */ + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_POINTER 4 + +#endif /* IS_64 */ + +#ifndef HAVE_LONG_LONG +#define HAVE_LONG_LONG +#endif +#define HAVE_ALIGNED_DOUBLES +#define HAVE_ALIGNED_LONGLONGS + +#ifndef NO_NSPR_10_SUPPORT + +#define BYTES_PER_BYTE PR_BYTES_PER_BYTE +#define BYTES_PER_SHORT PR_BYTES_PER_SHORT +#define BYTES_PER_INT PR_BYTES_PER_INT +#define BYTES_PER_INT64 PR_BYTES_PER_INT64 +#define BYTES_PER_LONG PR_BYTES_PER_LONG +#define BYTES_PER_FLOAT PR_BYTES_PER_FLOAT +#define BYTES_PER_DOUBLE PR_BYTES_PER_DOUBLE +#define BYTES_PER_WORD PR_BYTES_PER_WORD +#define BYTES_PER_DWORD PR_BYTES_PER_DWORD + +#define BITS_PER_BYTE PR_BITS_PER_BYTE +#define BITS_PER_SHORT PR_BITS_PER_SHORT +#define BITS_PER_INT PR_BITS_PER_INT +#define BITS_PER_INT64 PR_BITS_PER_INT64 +#define BITS_PER_LONG PR_BITS_PER_LONG +#define BITS_PER_FLOAT PR_BITS_PER_FLOAT +#define BITS_PER_DOUBLE PR_BITS_PER_DOUBLE +#define BITS_PER_WORD PR_BITS_PER_WORD + +#define BITS_PER_BYTE_LOG2 PR_BITS_PER_BYTE_LOG2 +#define BITS_PER_SHORT_LOG2 PR_BITS_PER_SHORT_LOG2 +#define BITS_PER_INT_LOG2 PR_BITS_PER_INT_LOG2 +#define BITS_PER_INT64_LOG2 PR_BITS_PER_INT64_LOG2 +#define BITS_PER_LONG_LOG2 PR_BITS_PER_LONG_LOG2 +#define BITS_PER_FLOAT_LOG2 PR_BITS_PER_FLOAT_LOG2 +#define BITS_PER_DOUBLE_LOG2 PR_BITS_PER_DOUBLE_LOG2 +#define BITS_PER_WORD_LOG2 PR_BITS_PER_WORD_LOG2 + +#define ALIGN_OF_SHORT PR_ALIGN_OF_SHORT +#define ALIGN_OF_INT PR_ALIGN_OF_INT +#define ALIGN_OF_LONG PR_ALIGN_OF_LONG +#define ALIGN_OF_INT64 PR_ALIGN_OF_INT64 +#define ALIGN_OF_FLOAT PR_ALIGN_OF_FLOAT +#define ALIGN_OF_DOUBLE PR_ALIGN_OF_DOUBLE +#define ALIGN_OF_POINTER PR_ALIGN_OF_POINTER +#define ALIGN_OF_WORD PR_ALIGN_OF_WORD + +#define BYTES_PER_WORD_LOG2 PR_BYTES_PER_WORD_LOG2 +#define BYTES_PER_DWORD_LOG2 PR_BYTES_PER_DWORD_LOG2 +#define WORDS_PER_DWORD_LOG2 PR_WORDS_PER_DWORD_LOG2 + +#endif /* NO_NSPR_10_SUPPORT */ + +#endif /* ifndef nspr_cpucfg___ */
diff --git a/src/base/third_party/nspr/prcpucfg_starboard.h b/src/base/third_party/nspr/prcpucfg_starboard.h new file mode 100644 index 0000000..5bdf40d --- /dev/null +++ b/src/base/third_party/nspr/prcpucfg_starboard.h
@@ -0,0 +1,312 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape Portable Runtime (NSPR). + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nspr_cpucfg___ +#define nspr_cpucfg___ + +#include "starboard/configuration.h" + +// This is only used by third_party/nss/ssl +#define PR_AF_INET6 23 /* same as AF_INET6 */ + +#if SB_IS(BIG_ENDIAN) +# undef IS_LITTLE_ENDIAN +# define IS_BIG_ENDIAN 1 +#else +# define IS_LITTLE_ENDIAN 1 +# undef IS_BIG_ENDIAN +#endif + +#if SB_IS(64_BIT) +# define IS_64 +#endif + +#if SB_IS(ARCH_PPC) && SB_IS(32_BIT) +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 4 +#define PR_ALIGN_OF_WORD 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 +#elif SB_IS(ARCH_X86) && SB_IS(64_BIT) +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 8 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 64 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 6 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 8 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 +#define PR_ALIGN_OF_WORD 8 + +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 +#elif SB_IS(ARCH_X86) && SB_IS(32_BIT) +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 4 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 4 +#define PR_ALIGN_OF_POINTER 4 +#define PR_ALIGN_OF_WORD 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 +#elif SB_IS(ARCH_MIPS) && SB_IS(32_BIT) +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 4 +#define PR_ALIGN_OF_WORD 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 +#elif SB_IS(ARCH_ARM) && SB_IS(32_BIT) +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_DOUBLE 8 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_DOUBLE 64 +#define PR_BITS_PER_WORD 32 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_DOUBLE_LOG2 6 +#define PR_BITS_PER_WORD_LOG2 5 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 4 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_DOUBLE 4 +#define PR_ALIGN_OF_POINTER 4 +#define PR_ALIGN_OF_WORD 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 3 +#else +# error "No configuration definition for Starboard ARCH + BITS." +#endif + +// TODO: Base this off of Starboard configuration defines. +#ifndef HAVE_LONG_LONG +#define HAVE_LONG_LONG +#endif + +#ifndef NO_NSPR_10_SUPPORT + +#define BYTES_PER_BYTE PR_BYTES_PER_BYTE +#define BYTES_PER_SHORT PR_BYTES_PER_SHORT +#define BYTES_PER_INT PR_BYTES_PER_INT +#define BYTES_PER_INT64 PR_BYTES_PER_INT64 +#define BYTES_PER_LONG PR_BYTES_PER_LONG +#define BYTES_PER_FLOAT PR_BYTES_PER_FLOAT +#define BYTES_PER_DOUBLE PR_BYTES_PER_DOUBLE +#define BYTES_PER_WORD PR_BYTES_PER_WORD +#define BYTES_PER_DWORD PR_BYTES_PER_DWORD + +#define BITS_PER_BYTE PR_BITS_PER_BYTE +#define BITS_PER_SHORT PR_BITS_PER_SHORT +#define BITS_PER_INT PR_BITS_PER_INT +#define BITS_PER_INT64 PR_BITS_PER_INT64 +#define BITS_PER_LONG PR_BITS_PER_LONG +#define BITS_PER_FLOAT PR_BITS_PER_FLOAT +#define BITS_PER_DOUBLE PR_BITS_PER_DOUBLE +#define BITS_PER_WORD PR_BITS_PER_WORD + +#define BITS_PER_BYTE_LOG2 PR_BITS_PER_BYTE_LOG2 +#define BITS_PER_SHORT_LOG2 PR_BITS_PER_SHORT_LOG2 +#define BITS_PER_INT_LOG2 PR_BITS_PER_INT_LOG2 +#define BITS_PER_INT64_LOG2 PR_BITS_PER_INT64_LOG2 +#define BITS_PER_LONG_LOG2 PR_BITS_PER_LONG_LOG2 +#define BITS_PER_FLOAT_LOG2 PR_BITS_PER_FLOAT_LOG2 +#define BITS_PER_DOUBLE_LOG2 PR_BITS_PER_DOUBLE_LOG2 +#define BITS_PER_WORD_LOG2 PR_BITS_PER_WORD_LOG2 + +#define ALIGN_OF_SHORT PR_ALIGN_OF_SHORT +#define ALIGN_OF_INT PR_ALIGN_OF_INT +#define ALIGN_OF_LONG PR_ALIGN_OF_LONG +#define ALIGN_OF_INT64 PR_ALIGN_OF_INT64 +#define ALIGN_OF_FLOAT PR_ALIGN_OF_FLOAT +#define ALIGN_OF_DOUBLE PR_ALIGN_OF_DOUBLE +#define ALIGN_OF_POINTER PR_ALIGN_OF_POINTER +#define ALIGN_OF_WORD PR_ALIGN_OF_WORD + +#define BYTES_PER_WORD_LOG2 PR_BYTES_PER_WORD_LOG2 +#define BYTES_PER_DWORD_LOG2 PR_BYTES_PER_DWORD_LOG2 +#define WORDS_PER_DWORD_LOG2 PR_WORDS_PER_DWORD_LOG2 + +#endif /* NO_NSPR_10_SUPPORT */ + +#endif /* nspr_cpucfg___ */
diff --git a/src/base/third_party/nspr/prcpucfg_win.h b/src/base/third_party/nspr/prcpucfg_win.h new file mode 100644 index 0000000..d08df97 --- /dev/null +++ b/src/base/third_party/nspr/prcpucfg_win.h
@@ -0,0 +1,304 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape Portable Runtime (NSPR). + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nspr_cpucfg___ +#define nspr_cpucfg___ + +#ifndef XP_PC +#define XP_PC +#endif + +#ifndef WIN32 +#define WIN32 +#endif + +#ifndef WIN95 +#define WIN95 +#endif + +#define PR_AF_INET6 23 /* same as AF_INET6 */ + +#if defined(_M_IX86) || defined(_X86_) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_DOUBLE 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_WORD 32 +#define PR_BITS_PER_DWORD 64 +#define PR_BITS_PER_DOUBLE 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_WORD_LOG2 5 +#define PR_BITS_PER_DWORD_LOG2 6 +#define PR_BITS_PER_DOUBLE_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_WORD 4 +#define PR_ALIGN_OF_DWORD 8 +#define PR_ALIGN_OF_DOUBLE 4 +#define PR_ALIGN_OF_POINTER 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 2 + +#elif defined(_M_X64) || defined(_M_AMD64) || defined(_AMD64_) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_DOUBLE 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_WORD 64 +#define PR_BITS_PER_DWORD 64 +#define PR_BITS_PER_DOUBLE 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_WORD_LOG2 6 +#define PR_BITS_PER_DWORD_LOG2 6 +#define PR_BITS_PER_DOUBLE_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_WORD 8 +#define PR_ALIGN_OF_DWORD 8 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 + +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(_M_IA64) || defined(_IA64_) + +#define IS_LITTLE_ENDIAN 1 +#undef IS_BIG_ENDIAN +#define IS_64 + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_WORD 8 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_DOUBLE 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_WORD 64 +#define PR_BITS_PER_DWORD 64 +#define PR_BITS_PER_DOUBLE 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_WORD_LOG2 6 +#define PR_BITS_PER_DWORD_LOG2 6 +#define PR_BITS_PER_DOUBLE_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_WORD 8 +#define PR_ALIGN_OF_DWORD 8 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 8 + +#define PR_BYTES_PER_WORD_LOG2 3 +#define PR_BYTES_PER_DWORD_LOG2 3 + +#elif defined(_M_PPC) || defined(_PPC_) + +#define IS_BIG_ENDIAN 1 +#undef IS_LITTLE_ENDIAN + +#define PR_BYTES_PER_BYTE 1 +#define PR_BYTES_PER_SHORT 2 +#define PR_BYTES_PER_INT 4 +#define PR_BYTES_PER_INT64 8 +#define PR_BYTES_PER_LONG 4 +#define PR_BYTES_PER_FLOAT 4 +#define PR_BYTES_PER_WORD 4 +#define PR_BYTES_PER_DWORD 8 +#define PR_BYTES_PER_DOUBLE 8 + +#define PR_BITS_PER_BYTE 8 +#define PR_BITS_PER_SHORT 16 +#define PR_BITS_PER_INT 32 +#define PR_BITS_PER_INT64 64 +#define PR_BITS_PER_LONG 32 +#define PR_BITS_PER_FLOAT 32 +#define PR_BITS_PER_WORD 32 +#define PR_BITS_PER_DWORD 64 +#define PR_BITS_PER_DOUBLE 64 + +#define PR_BITS_PER_BYTE_LOG2 3 +#define PR_BITS_PER_SHORT_LOG2 4 +#define PR_BITS_PER_INT_LOG2 5 +#define PR_BITS_PER_INT64_LOG2 6 +#define PR_BITS_PER_LONG_LOG2 5 +#define PR_BITS_PER_FLOAT_LOG2 5 +#define PR_BITS_PER_WORD_LOG2 5 +#define PR_BITS_PER_DWORD_LOG2 6 +#define PR_BITS_PER_DOUBLE_LOG2 6 + +#define PR_ALIGN_OF_SHORT 2 +#define PR_ALIGN_OF_INT 4 +#define PR_ALIGN_OF_LONG 4 +#define PR_ALIGN_OF_INT64 8 +#define PR_ALIGN_OF_FLOAT 4 +#define PR_ALIGN_OF_WORD 4 +#define PR_ALIGN_OF_DWORD 8 +#define PR_ALIGN_OF_DOUBLE 8 +#define PR_ALIGN_OF_POINTER 4 + +#define PR_BYTES_PER_WORD_LOG2 2 +#define PR_BYTES_PER_DWORD_LOG2 2 + +#else /* defined(_M_IX86) || defined(_X86_) */ + +#error unknown processor architecture + +#endif /* defined(_M_IX86) || defined(_X86_) */ + +#ifndef HAVE_LONG_LONG +#define HAVE_LONG_LONG +#endif + +#ifndef NO_NSPR_10_SUPPORT + +#define BYTES_PER_BYTE PR_BYTES_PER_BYTE +#define BYTES_PER_SHORT PR_BYTES_PER_SHORT +#define BYTES_PER_INT PR_BYTES_PER_INT +#define BYTES_PER_INT64 PR_BYTES_PER_INT64 +#define BYTES_PER_LONG PR_BYTES_PER_LONG +#define BYTES_PER_FLOAT PR_BYTES_PER_FLOAT +#define BYTES_PER_DOUBLE PR_BYTES_PER_DOUBLE +#define BYTES_PER_WORD PR_BYTES_PER_WORD +#define BYTES_PER_DWORD PR_BYTES_PER_DWORD + +#define BITS_PER_BYTE PR_BITS_PER_BYTE +#define BITS_PER_SHORT PR_BITS_PER_SHORT +#define BITS_PER_INT PR_BITS_PER_INT +#define BITS_PER_INT64 PR_BITS_PER_INT64 +#define BITS_PER_LONG PR_BITS_PER_LONG +#define BITS_PER_FLOAT PR_BITS_PER_FLOAT +#define BITS_PER_DOUBLE PR_BITS_PER_DOUBLE +#define BITS_PER_WORD PR_BITS_PER_WORD + +#define BITS_PER_BYTE_LOG2 PR_BITS_PER_BYTE_LOG2 +#define BITS_PER_SHORT_LOG2 PR_BITS_PER_SHORT_LOG2 +#define BITS_PER_INT_LOG2 PR_BITS_PER_INT_LOG2 +#define BITS_PER_INT64_LOG2 PR_BITS_PER_INT64_LOG2 +#define BITS_PER_LONG_LOG2 PR_BITS_PER_LONG_LOG2 +#define BITS_PER_FLOAT_LOG2 PR_BITS_PER_FLOAT_LOG2 +#define BITS_PER_DOUBLE_LOG2 PR_BITS_PER_DOUBLE_LOG2 +#define BITS_PER_WORD_LOG2 PR_BITS_PER_WORD_LOG2 + +#define ALIGN_OF_SHORT PR_ALIGN_OF_SHORT +#define ALIGN_OF_INT PR_ALIGN_OF_INT +#define ALIGN_OF_LONG PR_ALIGN_OF_LONG +#define ALIGN_OF_INT64 PR_ALIGN_OF_INT64 +#define ALIGN_OF_FLOAT PR_ALIGN_OF_FLOAT +#define ALIGN_OF_DOUBLE PR_ALIGN_OF_DOUBLE +#define ALIGN_OF_POINTER PR_ALIGN_OF_POINTER +#define ALIGN_OF_WORD PR_ALIGN_OF_WORD + +#define BYTES_PER_WORD_LOG2 PR_BYTES_PER_WORD_LOG2 +#define BYTES_PER_DWORD_LOG2 PR_BYTES_PER_DWORD_LOG2 +#define WORDS_PER_DWORD_LOG2 PR_WORDS_PER_DWORD_LOG2 + +#endif /* NO_NSPR_10_SUPPORT */ + +#endif /* nspr_cpucfg___ */
diff --git a/src/base/third_party/nspr/prtime.cc b/src/base/third_party/nspr/prtime.cc new file mode 100644 index 0000000..133913f --- /dev/null +++ b/src/base/third_party/nspr/prtime.cc
@@ -0,0 +1,1258 @@ +/* Portions are Copyright (C) 2011 Google Inc */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape Portable Runtime (NSPR). + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * prtime.cc -- + * NOTE: The original nspr file name is prtime.c + * + * NSPR date and time functions + * + * CVS revision 3.37 + */ + +/* + * The following functions were copied from the NSPR prtime.c file. + * PR_ParseTimeString + * We inlined the new PR_ParseTimeStringToExplodedTime function to avoid + * copying PR_ExplodeTime and PR_LocalTimeParameters. (The PR_ExplodeTime + * and PR_ImplodeTime calls cancel each other out.) + * PR_NormalizeTime + * PR_GMTParameters + * PR_ImplodeTime + * This was modified to use the Win32 SYSTEMTIME/FILETIME structures + * and the timezone offsets are applied to the FILETIME structure. + * All types and macros are defined in the base/third_party/prtime.h file. + * These have been copied from the following nspr files. We have only copied + * over the types we need. + * 1. prtime.h + * 2. prtypes.h + * 3. prlong.h + */ + +#include "base/logging.h" +#include "base/third_party/nspr/prtime.h" +#include "build/build_config.h" + +#if defined(OS_WIN) || defined(COBALT_WIN) +#include <windows.h> +#elif defined(OS_MACOSX) +#include <CoreFoundation/CoreFoundation.h> +#elif defined(OS_ANDROID) || defined(__LB_ANDROID__) +#include <ctype.h> +#include "base/os_compat_android.h" // For timegm() +#elif defined(OS_NACL) +#include "base/os_compat_nacl.h" // For timegm() +#else +#define PRTIME_USE_BASE_TIME +#include "base/time.h" +#endif + +#if !defined(OS_STARBOARD) +#include <errno.h> /* for EINVAL */ +#include <time.h> +#endif + +/* Implements the Unix localtime_r() function for windows */ +#if defined(OS_WIN) +static void localtime_r(const time_t* secs, struct tm* time) { + (void) localtime_s(time, secs); +} +#endif + +#if defined(PRTIME_USE_BASE_TIME) +// Implodes |exploded| using base::Time's implosion methods. |is_local| states +// whether to ignore the time zone params and just interpret as a local time, as +// opposed to treating like a UTC exploded time and then adjusting by the TZ +// params. +static PRTime BaseImplode(const PRExplodedTime* exploded, bool is_local) { + static const PRTime kSecondsToMicroseconds = static_cast<PRTime>(1000000); + base::Time::Exploded base_exploded; + base_exploded.year = exploded->tm_year; + base_exploded.month = exploded->tm_month + 1; + base_exploded.day_of_week = 0; + base_exploded.day_of_month = exploded->tm_mday; + base_exploded.hour = exploded->tm_hour; + base_exploded.minute = exploded->tm_min; + base_exploded.second = exploded->tm_sec; + base_exploded.millisecond = 0; + base::Time base_time; + if (is_local) { + base_time = base::Time::FromLocalExploded(base_exploded); + } else { + base_time = base::Time::FromUTCExploded(base_exploded); + } + PRTime result = static_cast<PRTime>( + (base_time - base::Time::UnixEpoch()).InMicroseconds()); + if (!is_local) { + result -= (exploded->tm_params.tp_gmt_offset + + exploded->tm_params.tp_dst_offset) * + kSecondsToMicroseconds; + } + result += exploded->tm_usec; + return result; +} +#endif // defined(PRTIME_USE_BASE_TIME) + +/* + *------------------------------------------------------------------------ + * + * PR_ImplodeTime -- + * + * Cf. time_t mktime(struct tm *tp) + * Note that 1 year has < 2^25 seconds. So an PRInt32 is large enough. + * + *------------------------------------------------------------------------ + */ +PRTime +PR_ImplodeTime(const PRExplodedTime *exploded) +{ + // This is important, we want to make sure multiplications are + // done with the correct precision. + static const PRTime kSecondsToMicroseconds = static_cast<PRTime>(1000000); +#if defined(OS_WIN) || defined(COBALT_WIN) + // Create the system struct representing our exploded time. + SYSTEMTIME st = {0}; + FILETIME ft = {0}; + ULARGE_INTEGER uli = {0}; + + st.wYear = exploded->tm_year; + st.wMonth = exploded->tm_month + 1; + st.wDayOfWeek = exploded->tm_wday; + st.wDay = exploded->tm_mday; + st.wHour = exploded->tm_hour; + st.wMinute = exploded->tm_min; + st.wSecond = exploded->tm_sec; + st.wMilliseconds = exploded->tm_usec/1000; + // Convert to FILETIME. + if (!SystemTimeToFileTime(&st, &ft)) { + NOTREACHED() << "Unable to convert time"; + return 0; + } + // Apply offsets. + uli.LowPart = ft.dwLowDateTime; + uli.HighPart = ft.dwHighDateTime; + // Convert from Windows epoch to NSPR epoch, and 100-nanoseconds units + // to microsecond units. + PRTime result = + static_cast<PRTime>((uli.QuadPart / 10) - 11644473600000000i64); + // Adjust for time zone and dst. Convert from seconds to microseconds. + result -= (exploded->tm_params.tp_gmt_offset + + exploded->tm_params.tp_dst_offset) * kSecondsToMicroseconds; + return result; +#elif defined(OS_MACOSX) + // Create the system struct representing our exploded time. + CFGregorianDate gregorian_date; + gregorian_date.year = exploded->tm_year; + gregorian_date.month = exploded->tm_month + 1; + gregorian_date.day = exploded->tm_mday; + gregorian_date.hour = exploded->tm_hour; + gregorian_date.minute = exploded->tm_min; + gregorian_date.second = exploded->tm_sec; + + // Compute |absolute_time| in seconds, correct for gmt and dst + // (note the combined offset will be negative when we need to add it), then + // convert to microseconds which is what PRTime expects. + CFAbsoluteTime absolute_time = + CFGregorianDateGetAbsoluteTime(gregorian_date, NULL); + PRTime result = static_cast<PRTime>(absolute_time); + result -= exploded->tm_params.tp_gmt_offset + + exploded->tm_params.tp_dst_offset; + result += kCFAbsoluteTimeIntervalSince1970; // PRTime epoch is 1970 + result *= kSecondsToMicroseconds; + result += exploded->tm_usec; + return result; +#elif defined(OS_POSIX) + struct tm exp_tm = {0}; + exp_tm.tm_sec = exploded->tm_sec; + exp_tm.tm_min = exploded->tm_min; + exp_tm.tm_hour = exploded->tm_hour; + exp_tm.tm_mday = exploded->tm_mday; + exp_tm.tm_mon = exploded->tm_month; + exp_tm.tm_year = exploded->tm_year - 1900; + time_t absolute_time = timegm(&exp_tm); + + // If timegm returned -1. Since we don't pass it a time zone, the only + // valid case of returning -1 is 1 second before Epoch (Dec 31, 1969). + if (absolute_time == -1 && + !(exploded->tm_year == 1969 && exploded->tm_month == 11 && + exploded->tm_mday == 31 && exploded->tm_hour == 23 && + exploded->tm_min == 59 && exploded->tm_sec == 59)) { + // If we get here, time_t must be 32 bits. + // Date was possibly too far in the future and would overflow. Return + // the most future date possible (year 2038). + if (exploded->tm_year >= 1970) + return INT_MAX * kSecondsToMicroseconds; + // Date was possibly too far in the past and would underflow. Return + // the most past date possible (year 1901). + return INT_MIN * kSecondsToMicroseconds; + } + + PRTime result = static_cast<PRTime>(absolute_time); + result -= exploded->tm_params.tp_gmt_offset + + exploded->tm_params.tp_dst_offset; + result *= kSecondsToMicroseconds; + result += exploded->tm_usec; + return result; +#elif defined(PRTIME_USE_BASE_TIME) + return BaseImplode(exploded, false /*is_local*/); +#else +#error No PR_ImplodeTime implemented on your platform. +#endif +} + +/* + * The COUNT_LEAPS macro counts the number of leap years passed by + * till the start of the given year Y. At the start of the year 4 + * A.D. the number of leap years passed by is 0, while at the start of + * the year 5 A.D. this count is 1. The number of years divisible by + * 100 but not divisible by 400 (the non-leap years) is deducted from + * the count to get the correct number of leap years. + * + * The COUNT_DAYS macro counts the number of days since 01/01/01 till the + * start of the given year Y. The number of days at the start of the year + * 1 is 0 while the number of days at the start of the year 2 is 365 + * (which is ((2)-1) * 365) and so on. The reference point is 01/01/01 + * midnight 00:00:00. + */ + +#define COUNT_LEAPS(Y) ( ((Y)-1)/4 - ((Y)-1)/100 + ((Y)-1)/400 ) +#define COUNT_DAYS(Y) ( ((Y)-1)*365 + COUNT_LEAPS(Y) ) +#define DAYS_BETWEEN_YEARS(A, B) (COUNT_DAYS(B) - COUNT_DAYS(A)) + +/* + * Static variables used by functions in this file + */ + +/* + * The following array contains the day of year for the last day of + * each month, where index 1 is January, and day 0 is January 1. + */ + +static const int lastDayOfMonth[2][13] = { + {-1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364}, + {-1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365} +}; + +/* + * The number of days in a month + */ + +static const PRInt8 nDays[2][12] = { + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} +}; + +/* + *------------------------------------------------------------------------- + * + * IsLeapYear -- + * + * Returns 1 if the year is a leap year, 0 otherwise. + * + *------------------------------------------------------------------------- + */ + +static int IsLeapYear(PRInt16 year) +{ + if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) + return 1; + else + return 0; +} + +/* + * 'secOffset' should be less than 86400 (i.e., a day). + * 'time' should point to a normalized PRExplodedTime. + */ + +static void +ApplySecOffset(PRExplodedTime *time, PRInt32 secOffset) +{ + time->tm_sec += secOffset; + + /* Note that in this implementation we do not count leap seconds */ + if (time->tm_sec < 0 || time->tm_sec >= 60) { + time->tm_min += time->tm_sec / 60; + time->tm_sec %= 60; + if (time->tm_sec < 0) { + time->tm_sec += 60; + time->tm_min--; + } + } + + if (time->tm_min < 0 || time->tm_min >= 60) { + time->tm_hour += time->tm_min / 60; + time->tm_min %= 60; + if (time->tm_min < 0) { + time->tm_min += 60; + time->tm_hour--; + } + } + + if (time->tm_hour < 0) { + /* Decrement mday, yday, and wday */ + time->tm_hour += 24; + time->tm_mday--; + time->tm_yday--; + if (time->tm_mday < 1) { + time->tm_month--; + if (time->tm_month < 0) { + time->tm_month = 11; + time->tm_year--; + if (IsLeapYear(time->tm_year)) + time->tm_yday = 365; + else + time->tm_yday = 364; + } + time->tm_mday = nDays[IsLeapYear(time->tm_year)][time->tm_month]; + } + time->tm_wday--; + if (time->tm_wday < 0) + time->tm_wday = 6; + } else if (time->tm_hour > 23) { + /* Increment mday, yday, and wday */ + time->tm_hour -= 24; + time->tm_mday++; + time->tm_yday++; + if (time->tm_mday > + nDays[IsLeapYear(time->tm_year)][time->tm_month]) { + time->tm_mday = 1; + time->tm_month++; + if (time->tm_month > 11) { + time->tm_month = 0; + time->tm_year++; + time->tm_yday = 0; + } + } + time->tm_wday++; + if (time->tm_wday > 6) + time->tm_wday = 0; + } +} + +void +PR_NormalizeTime(PRExplodedTime *time, PRTimeParamFn params) +{ + int daysInMonth; + PRInt32 numDays; + + /* Get back to GMT */ + time->tm_sec -= time->tm_params.tp_gmt_offset + + time->tm_params.tp_dst_offset; + time->tm_params.tp_gmt_offset = 0; + time->tm_params.tp_dst_offset = 0; + + /* Now normalize GMT */ + + if (time->tm_usec < 0 || time->tm_usec >= 1000000) { + time->tm_sec += time->tm_usec / 1000000; + time->tm_usec %= 1000000; + if (time->tm_usec < 0) { + time->tm_usec += 1000000; + time->tm_sec--; + } + } + + /* Note that we do not count leap seconds in this implementation */ + if (time->tm_sec < 0 || time->tm_sec >= 60) { + time->tm_min += time->tm_sec / 60; + time->tm_sec %= 60; + if (time->tm_sec < 0) { + time->tm_sec += 60; + time->tm_min--; + } + } + + if (time->tm_min < 0 || time->tm_min >= 60) { + time->tm_hour += time->tm_min / 60; + time->tm_min %= 60; + if (time->tm_min < 0) { + time->tm_min += 60; + time->tm_hour--; + } + } + + if (time->tm_hour < 0 || time->tm_hour >= 24) { + time->tm_mday += time->tm_hour / 24; + time->tm_hour %= 24; + if (time->tm_hour < 0) { + time->tm_hour += 24; + time->tm_mday--; + } + } + + /* Normalize month and year before mday */ + if (time->tm_month < 0 || time->tm_month >= 12) { + time->tm_year += time->tm_month / 12; + time->tm_month %= 12; + if (time->tm_month < 0) { + time->tm_month += 12; + time->tm_year--; + } + } + + /* Now that month and year are in proper range, normalize mday */ + + if (time->tm_mday < 1) { + /* mday too small */ + do { + /* the previous month */ + time->tm_month--; + if (time->tm_month < 0) { + time->tm_month = 11; + time->tm_year--; + } + time->tm_mday += nDays[IsLeapYear(time->tm_year)][time->tm_month]; + } while (time->tm_mday < 1); + } else { + daysInMonth = nDays[IsLeapYear(time->tm_year)][time->tm_month]; + while (time->tm_mday > daysInMonth) { + /* mday too large */ + time->tm_mday -= daysInMonth; + time->tm_month++; + if (time->tm_month > 11) { + time->tm_month = 0; + time->tm_year++; + } + daysInMonth = nDays[IsLeapYear(time->tm_year)][time->tm_month]; + } + } + + /* Recompute yday and wday */ + time->tm_yday = time->tm_mday + + lastDayOfMonth[IsLeapYear(time->tm_year)][time->tm_month]; + + numDays = DAYS_BETWEEN_YEARS(1970, time->tm_year) + time->tm_yday; + time->tm_wday = (numDays + 4) % 7; + if (time->tm_wday < 0) { + time->tm_wday += 7; + } + + /* Recompute time parameters */ + + time->tm_params = params(time); + + ApplySecOffset(time, time->tm_params.tp_gmt_offset + + time->tm_params.tp_dst_offset); +} + +/* + *------------------------------------------------------------------------ + * + * PR_GMTParameters -- + * + * Returns the PRTimeParameters for Greenwich Mean Time. + * Trivially, both the tp_gmt_offset and tp_dst_offset fields are 0. + * + *------------------------------------------------------------------------ + */ + +PRTimeParameters +PR_GMTParameters(const PRExplodedTime *gmt) +{ +#if defined(XP_MAC) +#pragma unused (gmt) +#endif + + PRTimeParameters retVal = { 0, 0 }; + return retVal; +} + +/* + * The following code implements PR_ParseTimeString(). It is based on + * ns/lib/xp/xp_time.c, revision 1.25, by Jamie Zawinski <jwz@netscape.com>. + */ + +/* + * We only recognize the abbreviations of a small subset of time zones + * in North America, Europe, and Japan. + * + * PST/PDT: Pacific Standard/Daylight Time + * MST/MDT: Mountain Standard/Daylight Time + * CST/CDT: Central Standard/Daylight Time + * EST/EDT: Eastern Standard/Daylight Time + * AST: Atlantic Standard Time + * NST: Newfoundland Standard Time + * GMT: Greenwich Mean Time + * BST: British Summer Time + * MET: Middle Europe Time + * EET: Eastern Europe Time + * JST: Japan Standard Time + */ + +typedef enum +{ + TT_UNKNOWN, + + TT_SUN, TT_MON, TT_TUE, TT_WED, TT_THU, TT_FRI, TT_SAT, + + TT_JAN, TT_FEB, TT_MAR, TT_APR, TT_MAY, TT_JUN, + TT_JUL, TT_AUG, TT_SEP, TT_OCT, TT_NOV, TT_DEC, + + TT_PST, TT_PDT, TT_MST, TT_MDT, TT_CST, TT_CDT, TT_EST, TT_EDT, + TT_AST, TT_NST, TT_GMT, TT_BST, TT_MET, TT_EET, TT_JST +} TIME_TOKEN; + +/* + * This parses a time/date string into a PRTime + * (microseconds after "1-Jan-1970 00:00:00 GMT"). + * It returns PR_SUCCESS on success, and PR_FAILURE + * if the time/date string can't be parsed. + * + * Many formats are handled, including: + * + * 14 Apr 89 03:20:12 + * 14 Apr 89 03:20 GMT + * Fri, 17 Mar 89 4:01:33 + * Fri, 17 Mar 89 4:01 GMT + * Mon Jan 16 16:12 PDT 1989 + * Mon Jan 16 16:12 +0130 1989 + * 6 May 1992 16:41-JST (Wednesday) + * 22-AUG-1993 10:59:12.82 + * 22-AUG-1993 10:59pm + * 22-AUG-1993 12:59am + * 22-AUG-1993 12:59 PM + * Friday, August 04, 1995 3:54 PM + * 06/21/95 04:24:34 PM + * 20/06/95 21:07 + * 95-06-08 19:32:48 EDT + * + * If the input string doesn't contain a description of the timezone, + * we consult the `default_to_gmt' to decide whether the string should + * be interpreted relative to the local time zone (PR_FALSE) or GMT (PR_TRUE). + * The correct value for this argument depends on what standard specified + * the time string which you are parsing. + */ + +PRStatus +PR_ParseTimeString( + const char *string, + PRBool default_to_gmt, + PRTime *result_imploded) +{ + PRExplodedTime tm; + PRExplodedTime *result = &tm; + TIME_TOKEN dotw = TT_UNKNOWN; + TIME_TOKEN month = TT_UNKNOWN; + TIME_TOKEN zone = TT_UNKNOWN; + int zone_offset = -1; + int dst_offset = 0; + int date = -1; + PRInt32 year = -1; + int hour = -1; + int min = -1; + int sec = -1; + + const char *rest = string; + + int iterations = 0; + + PR_ASSERT(string && result); + if (!string || !result) return PR_FAILURE; + + while (*rest) + { + + if (iterations++ > 1000) + { + return PR_FAILURE; + } + + switch (*rest) + { + case 'a': case 'A': + if (month == TT_UNKNOWN && + (rest[1] == 'p' || rest[1] == 'P') && + (rest[2] == 'r' || rest[2] == 'R')) + month = TT_APR; + else if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 'S') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_AST; + else if (month == TT_UNKNOWN && + (rest[1] == 'u' || rest[1] == 'U') && + (rest[2] == 'g' || rest[2] == 'G')) + month = TT_AUG; + break; + case 'b': case 'B': + if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 'S') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_BST; + break; + case 'c': case 'C': + if (zone == TT_UNKNOWN && + (rest[1] == 'd' || rest[1] == 'D') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_CDT; + else if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 'S') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_CST; + break; + case 'd': case 'D': + if (month == TT_UNKNOWN && + (rest[1] == 'e' || rest[1] == 'E') && + (rest[2] == 'c' || rest[2] == 'C')) + month = TT_DEC; + break; + case 'e': case 'E': + if (zone == TT_UNKNOWN && + (rest[1] == 'd' || rest[1] == 'D') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_EDT; + else if (zone == TT_UNKNOWN && + (rest[1] == 'e' || rest[1] == 'E') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_EET; + else if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 'S') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_EST; + break; + case 'f': case 'F': + if (month == TT_UNKNOWN && + (rest[1] == 'e' || rest[1] == 'E') && + (rest[2] == 'b' || rest[2] == 'B')) + month = TT_FEB; + else if (dotw == TT_UNKNOWN && + (rest[1] == 'r' || rest[1] == 'R') && + (rest[2] == 'i' || rest[2] == 'I')) + dotw = TT_FRI; + break; + case 'g': case 'G': + if (zone == TT_UNKNOWN && + (rest[1] == 'm' || rest[1] == 'M') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_GMT; + break; + case 'j': case 'J': + if (month == TT_UNKNOWN && + (rest[1] == 'a' || rest[1] == 'A') && + (rest[2] == 'n' || rest[2] == 'N')) + month = TT_JAN; + else if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 'S') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_JST; + else if (month == TT_UNKNOWN && + (rest[1] == 'u' || rest[1] == 'U') && + (rest[2] == 'l' || rest[2] == 'L')) + month = TT_JUL; + else if (month == TT_UNKNOWN && + (rest[1] == 'u' || rest[1] == 'U') && + (rest[2] == 'n' || rest[2] == 'N')) + month = TT_JUN; + break; + case 'm': case 'M': + if (month == TT_UNKNOWN && + (rest[1] == 'a' || rest[1] == 'A') && + (rest[2] == 'r' || rest[2] == 'R')) + month = TT_MAR; + else if (month == TT_UNKNOWN && + (rest[1] == 'a' || rest[1] == 'A') && + (rest[2] == 'y' || rest[2] == 'Y')) + month = TT_MAY; + else if (zone == TT_UNKNOWN && + (rest[1] == 'd' || rest[1] == 'D') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_MDT; + else if (zone == TT_UNKNOWN && + (rest[1] == 'e' || rest[1] == 'E') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_MET; + else if (dotw == TT_UNKNOWN && + (rest[1] == 'o' || rest[1] == 'O') && + (rest[2] == 'n' || rest[2] == 'N')) + dotw = TT_MON; + else if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 'S') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_MST; + break; + case 'n': case 'N': + if (month == TT_UNKNOWN && + (rest[1] == 'o' || rest[1] == 'O') && + (rest[2] == 'v' || rest[2] == 'V')) + month = TT_NOV; + else if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 'S') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_NST; + break; + case 'o': case 'O': + if (month == TT_UNKNOWN && + (rest[1] == 'c' || rest[1] == 'C') && + (rest[2] == 't' || rest[2] == 'T')) + month = TT_OCT; + break; + case 'p': case 'P': + if (zone == TT_UNKNOWN && + (rest[1] == 'd' || rest[1] == 'D') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_PDT; + else if (zone == TT_UNKNOWN && + (rest[1] == 's' || rest[1] == 'S') && + (rest[2] == 't' || rest[2] == 'T')) + zone = TT_PST; + break; + case 's': case 'S': + if (dotw == TT_UNKNOWN && + (rest[1] == 'a' || rest[1] == 'A') && + (rest[2] == 't' || rest[2] == 'T')) + dotw = TT_SAT; + else if (month == TT_UNKNOWN && + (rest[1] == 'e' || rest[1] == 'E') && + (rest[2] == 'p' || rest[2] == 'P')) + month = TT_SEP; + else if (dotw == TT_UNKNOWN && + (rest[1] == 'u' || rest[1] == 'U') && + (rest[2] == 'n' || rest[2] == 'N')) + dotw = TT_SUN; + break; + case 't': case 'T': + if (dotw == TT_UNKNOWN && + (rest[1] == 'h' || rest[1] == 'H') && + (rest[2] == 'u' || rest[2] == 'U')) + dotw = TT_THU; + else if (dotw == TT_UNKNOWN && + (rest[1] == 'u' || rest[1] == 'U') && + (rest[2] == 'e' || rest[2] == 'E')) + dotw = TT_TUE; + break; + case 'u': case 'U': + if (zone == TT_UNKNOWN && + (rest[1] == 't' || rest[1] == 'T') && + !(rest[2] >= 'A' && rest[2] <= 'Z') && + !(rest[2] >= 'a' && rest[2] <= 'z')) + /* UT is the same as GMT but UTx is not. */ + zone = TT_GMT; + break; + case 'w': case 'W': + if (dotw == TT_UNKNOWN && + (rest[1] == 'e' || rest[1] == 'E') && + (rest[2] == 'd' || rest[2] == 'D')) + dotw = TT_WED; + break; + + case '+': case '-': + { + const char *end; + int sign; + if (zone_offset != -1) + { + /* already got one... */ + rest++; + break; + } + if (zone != TT_UNKNOWN && zone != TT_GMT) + { + /* GMT+0300 is legal, but PST+0300 is not. */ + rest++; + break; + } + + sign = ((*rest == '+') ? 1 : -1); + rest++; /* move over sign */ + end = rest; + while (*end >= '0' && *end <= '9') + end++; + if (rest == end) /* no digits here */ + break; + + if ((end - rest) == 4) + /* offset in HHMM */ + zone_offset = (((((rest[0]-'0')*10) + (rest[1]-'0')) * 60) + + (((rest[2]-'0')*10) + (rest[3]-'0'))); + else if ((end - rest) == 2) + /* offset in hours */ + zone_offset = (((rest[0]-'0')*10) + (rest[1]-'0')) * 60; + else if ((end - rest) == 1) + /* offset in hours */ + zone_offset = (rest[0]-'0') * 60; + else + /* 3 or >4 */ + break; + + zone_offset *= sign; + zone = TT_GMT; + break; + } + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + int tmp_hour = -1; + int tmp_min = -1; + int tmp_sec = -1; + const char *end = rest + 1; + while (*end >= '0' && *end <= '9') + end++; + + /* end is now the first character after a range of digits. */ + + if (*end == ':') + { + if (hour >= 0 && min >= 0) /* already got it */ + break; + + /* We have seen "[0-9]+:", so this is probably HH:MM[:SS] */ + if ((end - rest) > 2) + /* it is [0-9][0-9][0-9]+: */ + break; + else if ((end - rest) == 2) + tmp_hour = ((rest[0]-'0')*10 + + (rest[1]-'0')); + else + tmp_hour = (rest[0]-'0'); + + /* move over the colon, and parse minutes */ + + rest = ++end; + while (*end >= '0' && *end <= '9') + end++; + + if (end == rest) + /* no digits after first colon? */ + break; + else if ((end - rest) > 2) + /* it is [0-9][0-9][0-9]+: */ + break; + else if ((end - rest) == 2) + tmp_min = ((rest[0]-'0')*10 + + (rest[1]-'0')); + else + tmp_min = (rest[0]-'0'); + + /* now go for seconds */ + rest = end; + if (*rest == ':') + rest++; + end = rest; + while (*end >= '0' && *end <= '9') + end++; + + if (end == rest) + /* no digits after second colon - that's ok. */ + ; + else if ((end - rest) > 2) + /* it is [0-9][0-9][0-9]+: */ + break; + else if ((end - rest) == 2) + tmp_sec = ((rest[0]-'0')*10 + + (rest[1]-'0')); + else + tmp_sec = (rest[0]-'0'); + + /* If we made it here, we've parsed hour and min, + and possibly sec, so it worked as a unit. */ + + /* skip over whitespace and see if there's an AM or PM + directly following the time. + */ + if (tmp_hour <= 12) + { + const char *s = end; + while (*s && (*s == ' ' || *s == '\t')) + s++; + if ((s[0] == 'p' || s[0] == 'P') && + (s[1] == 'm' || s[1] == 'M')) + /* 10:05pm == 22:05, and 12:05pm == 12:05 */ + tmp_hour = (tmp_hour == 12 ? 12 : tmp_hour + 12); + else if (tmp_hour == 12 && + (s[0] == 'a' || s[0] == 'A') && + (s[1] == 'm' || s[1] == 'M')) + /* 12:05am == 00:05 */ + tmp_hour = 0; + } + + hour = tmp_hour; + min = tmp_min; + sec = tmp_sec; + rest = end; + break; + } + else if ((*end == '/' || *end == '-') && + end[1] >= '0' && end[1] <= '9') + { + /* Perhaps this is 6/16/95, 16/6/95, 6-16-95, or 16-6-95 + or even 95-06-05... + #### But it doesn't handle 1995-06-22. + */ + int n1, n2, n3; + const char *s; + + if (month != TT_UNKNOWN) + /* if we saw a month name, this can't be. */ + break; + + s = rest; + + n1 = (*s++ - '0'); /* first 1 or 2 digits */ + if (*s >= '0' && *s <= '9') + n1 = n1*10 + (*s++ - '0'); + + if (*s != '/' && *s != '-') /* slash */ + break; + s++; + + if (*s < '0' || *s > '9') /* second 1 or 2 digits */ + break; + n2 = (*s++ - '0'); + if (*s >= '0' && *s <= '9') + n2 = n2*10 + (*s++ - '0'); + + if (*s != '/' && *s != '-') /* slash */ + break; + s++; + + if (*s < '0' || *s > '9') /* third 1, 2, 4, or 5 digits */ + break; + n3 = (*s++ - '0'); + if (*s >= '0' && *s <= '9') + n3 = n3*10 + (*s++ - '0'); + + if (*s >= '0' && *s <= '9') /* optional digits 3, 4, and 5 */ + { + n3 = n3*10 + (*s++ - '0'); + if (*s < '0' || *s > '9') + break; + n3 = n3*10 + (*s++ - '0'); + if (*s >= '0' && *s <= '9') + n3 = n3*10 + (*s++ - '0'); + } + + if ((*s >= '0' && *s <= '9') || /* followed by non-alphanum */ + (*s >= 'A' && *s <= 'Z') || + (*s >= 'a' && *s <= 'z')) + break; + + /* Ok, we parsed three 1-2 digit numbers, with / or - + between them. Now decide what the hell they are + (DD/MM/YY or MM/DD/YY or YY/MM/DD.) + */ + + if (n1 > 31 || n1 == 0) /* must be YY/MM/DD */ + { + if (n2 > 12) break; + if (n3 > 31) break; + year = n1; + if (year < 70) + year += 2000; + else if (year < 100) + year += 1900; + month = (TIME_TOKEN)(n2 + ((int)TT_JAN) - 1); + date = n3; + rest = s; + break; + } + + if (n1 > 12 && n2 > 12) /* illegal */ + { + rest = s; + break; + } + + if (n3 < 70) + n3 += 2000; + else if (n3 < 100) + n3 += 1900; + + if (n1 > 12) /* must be DD/MM/YY */ + { + date = n1; + month = (TIME_TOKEN)(n2 + ((int)TT_JAN) - 1); + year = n3; + } + else /* assume MM/DD/YY */ + { + /* #### In the ambiguous case, should we consult the + locale to find out the local default? */ + month = (TIME_TOKEN)(n1 + ((int)TT_JAN) - 1); + date = n2; + year = n3; + } + rest = s; + } + else if ((*end >= 'A' && *end <= 'Z') || + (*end >= 'a' && *end <= 'z')) + /* Digits followed by non-punctuation - what's that? */ + ; + else if ((end - rest) == 5) /* five digits is a year */ + year = (year < 0 + ? ((rest[0]-'0')*10000L + + (rest[1]-'0')*1000L + + (rest[2]-'0')*100L + + (rest[3]-'0')*10L + + (rest[4]-'0')) + : year); + else if ((end - rest) == 4) /* four digits is a year */ + year = (year < 0 + ? ((rest[0]-'0')*1000L + + (rest[1]-'0')*100L + + (rest[2]-'0')*10L + + (rest[3]-'0')) + : year); + else if ((end - rest) == 2) /* two digits - date or year */ + { + int n = ((rest[0]-'0')*10 + + (rest[1]-'0')); + /* If we don't have a date (day of the month) and we see a number + less than 32, then assume that is the date. + + Otherwise, if we have a date and not a year, assume this is the + year. If it is less than 70, then assume it refers to the 21st + century. If it is two digits (>= 70), assume it refers to this + century. Otherwise, assume it refers to an unambiguous year. + + The world will surely end soon. + */ + if (date < 0 && n < 32) + date = n; + else if (year < 0) + { + if (n < 70) + year = 2000 + n; + else if (n < 100) + year = 1900 + n; + else + year = n; + } + /* else what the hell is this. */ + } + else if ((end - rest) == 1) /* one digit - date */ + date = (date < 0 ? (rest[0]-'0') : date); + /* else, three or more than five digits - what's that? */ + + break; + } + } + + /* Skip to the end of this token, whether we parsed it or not. + Tokens are delimited by whitespace, or ,;-/ + But explicitly not :+-. + */ + while (*rest && + *rest != ' ' && *rest != '\t' && + *rest != ',' && *rest != ';' && + *rest != '-' && *rest != '+' && + *rest != '/' && + *rest != '(' && *rest != ')' && *rest != '[' && *rest != ']') + rest++; + /* skip over uninteresting chars. */ + SKIP_MORE: + while (*rest && + (*rest == ' ' || *rest == '\t' || + *rest == ',' || *rest == ';' || *rest == '/' || + *rest == '(' || *rest == ')' || *rest == '[' || *rest == ']')) + rest++; + + /* "-" is ignored at the beginning of a token if we have not yet + parsed a year (e.g., the second "-" in "30-AUG-1966"), or if + the character after the dash is not a digit. */ + if (*rest == '-' && ((rest > string && + isalpha((unsigned char)rest[-1]) && year < 0) || + rest[1] < '0' || rest[1] > '9')) + { + rest++; + goto SKIP_MORE; + } + + } + + if (zone != TT_UNKNOWN && zone_offset == -1) + { + switch (zone) + { + case TT_PST: zone_offset = -8 * 60; break; + case TT_PDT: zone_offset = -8 * 60; dst_offset = 1 * 60; break; + case TT_MST: zone_offset = -7 * 60; break; + case TT_MDT: zone_offset = -7 * 60; dst_offset = 1 * 60; break; + case TT_CST: zone_offset = -6 * 60; break; + case TT_CDT: zone_offset = -6 * 60; dst_offset = 1 * 60; break; + case TT_EST: zone_offset = -5 * 60; break; + case TT_EDT: zone_offset = -5 * 60; dst_offset = 1 * 60; break; + case TT_AST: zone_offset = -4 * 60; break; + case TT_NST: zone_offset = -3 * 60 - 30; break; + case TT_GMT: zone_offset = 0 * 60; break; + case TT_BST: zone_offset = 0 * 60; dst_offset = 1 * 60; break; + case TT_MET: zone_offset = 1 * 60; break; + case TT_EET: zone_offset = 2 * 60; break; + case TT_JST: zone_offset = 9 * 60; break; + default: + PR_ASSERT (0); + break; + } + } + + /* If we didn't find a year, month, or day-of-the-month, we can't + possibly parse this, and in fact, mktime() will do something random + (I'm seeing it return "Tue Feb 5 06:28:16 2036", which is no doubt + a numerologically significant date... */ + if (month == TT_UNKNOWN || date == -1 || year == -1 || year > PR_INT16_MAX) + return PR_FAILURE; + + memset(result, 0, sizeof(*result)); + if (sec != -1) + result->tm_sec = sec; + if (min != -1) + result->tm_min = min; + if (hour != -1) + result->tm_hour = hour; + if (date != -1) + result->tm_mday = date; + if (month != TT_UNKNOWN) + result->tm_month = (((int)month) - ((int)TT_JAN)); + if (year != -1) + result->tm_year = year; + if (dotw != TT_UNKNOWN) + result->tm_wday = (((int)dotw) - ((int)TT_SUN)); + /* + * Mainly to compute wday and yday, but normalized time is also required + * by the check below that works around a Visual C++ 2005 mktime problem. + */ + PR_NormalizeTime(result, PR_GMTParameters); + /* The remaining work is to set the gmt and dst offsets in tm_params. */ + + if (zone == TT_UNKNOWN && default_to_gmt) + { + /* No zone was specified, so pretend the zone was GMT. */ + zone = TT_GMT; + zone_offset = 0; + } + + if (zone_offset == -1) + { + /* no zone was specified, and we're to assume that everything + is local. */ +#if !defined(PRTIME_USE_BASE_TIME) + struct tm localTime; + time_t secs; +#endif + + PR_ASSERT(result->tm_month > -1 && + result->tm_mday > 0 && + result->tm_hour > -1 && + result->tm_min > -1 && + result->tm_sec > -1); + +#if defined(PRTIME_USE_BASE_TIME) + *result_imploded = BaseImplode(result, true /*is_local*/); + return PR_SUCCESS; +#else // defined(PRTIME_USE_BASE_TIME) + /* + * To obtain time_t from a tm structure representing the local + * time, we call mktime(). However, we need to see if we are + * on 1-Jan-1970 or before. If we are, we can't call mktime() + * because mktime() will crash on win16. In that case, we + * calculate zone_offset based on the zone offset at + * 00:00:00, 2 Jan 1970 GMT, and subtract zone_offset from the + * date we are parsing to transform the date to GMT. We also + * do so if mktime() returns (time_t) -1 (time out of range). + */ + + /* month, day, hours, mins and secs are always non-negative + so we dont need to worry about them. */ + if(result->tm_year >= 1970) + { + PRInt64 usec_per_sec; + + localTime.tm_sec = result->tm_sec; + localTime.tm_min = result->tm_min; + localTime.tm_hour = result->tm_hour; + localTime.tm_mday = result->tm_mday; + localTime.tm_mon = result->tm_month; + localTime.tm_year = result->tm_year - 1900; + /* Set this to -1 to tell mktime "I don't care". If you set + it to 0 or 1, you are making assertions about whether the + date you are handing it is in daylight savings mode or not; + and if you're wrong, it will "fix" it for you. */ + localTime.tm_isdst = -1; + +#if _MSC_VER == 1400 /* 1400 = Visual C++ 2005 (8.0) */ + /* + * mktime will return (time_t) -1 if the input is a date + * after 23:59:59, December 31, 3000, US Pacific Time (not + * UTC as documented): + * http://msdn.microsoft.com/en-us/library/d1y53h2a(VS.80).aspx + * But if the year is 3001, mktime also invokes the invalid + * parameter handler, causing the application to crash. This + * problem has been reported in + * http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=266036. + * We avoid this crash by not calling mktime if the date is + * out of range. To use a simple test that works in any time + * zone, we consider year 3000 out of range as well. (See + * bug 480740.) + */ + if (result->tm_year >= 3000) { + /* Emulate what mktime would have done. */ + errno = EINVAL; + secs = (time_t) -1; + } else { + secs = mktime(&localTime); + } +#else + secs = mktime(&localTime); +#endif + if (secs != (time_t) -1) + { + PRTime usecs64; + LL_I2L(usecs64, secs); + LL_I2L(usec_per_sec, PR_USEC_PER_SEC); + LL_MUL(usecs64, usecs64, usec_per_sec); + *result_imploded = usecs64; + return PR_SUCCESS; + } + } + + /* So mktime() can't handle this case. We assume the + zone_offset for the date we are parsing is the same as + the zone offset on 00:00:00 2 Jan 1970 GMT. */ + secs = 86400; + localtime_r(&secs, &localTime); + zone_offset = localTime.tm_min + + 60 * localTime.tm_hour + + 1440 * (localTime.tm_mday - 2); +#endif // defined(PRTIME_USE_BASE_TIME) + } + + result->tm_params.tp_gmt_offset = zone_offset * 60; + result->tm_params.tp_dst_offset = dst_offset * 60; + + *result_imploded = PR_ImplodeTime(result); + return PR_SUCCESS; +}
diff --git a/src/base/third_party/nspr/prtime.h b/src/base/third_party/nspr/prtime.h new file mode 100644 index 0000000..ffbedec --- /dev/null +++ b/src/base/third_party/nspr/prtime.h
@@ -0,0 +1,238 @@ +/* Portions are Copyright (C) 2011 Google Inc */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape Portable Runtime (NSPR). + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + *--------------------------------------------------------------------------- + * + * prtime.h -- + * + * NSPR date and time functions + * CVS revision 3.10 + * This file contains definitions of NSPR's basic types required by + * prtime.cc. These types have been copied over from the following NSPR + * files prtime.h, prtypes.h(CVS revision 3.35), prlong.h(CVS revision 3.13) + * + *--------------------------------------------------------------------------- + */ + +#ifndef BASE_PRTIME_H__ +#define BASE_PRTIME_H__ + +#include "base/base_export.h" +#include "base/third_party/nspr/prtypes.h" + +#define PR_ASSERT DCHECK + +#define LL_I2L(l, i) ((l) = (PRInt64)(i)) +#define LL_MUL(r, a, b) ((r) = (a) * (b)) + +/**********************************************************************/ +/************************* TYPES AND CONSTANTS ************************/ +/**********************************************************************/ + +#define PR_MSEC_PER_SEC 1000UL +#define PR_USEC_PER_SEC 1000000UL +#define PR_NSEC_PER_SEC 1000000000UL +#define PR_USEC_PER_MSEC 1000UL +#define PR_NSEC_PER_MSEC 1000000UL + +/* + * PRTime -- + * + * NSPR represents basic time as 64-bit signed integers relative + * to midnight (00:00:00), January 1, 1970 Greenwich Mean Time (GMT). + * (GMT is also known as Coordinated Universal Time, UTC.) + * The units of time are in microseconds. Negative times are allowed + * to represent times prior to the January 1970 epoch. Such values are + * intended to be exported to other systems or converted to human + * readable form. + * + * Notes on porting: PRTime corresponds to time_t in ANSI C. NSPR 1.0 + * simply uses PRInt64. + */ + +typedef PRInt64 PRTime; + +/* + * Time zone and daylight saving time corrections applied to GMT to + * obtain the local time of some geographic location + */ + +typedef struct PRTimeParameters { + PRInt32 tp_gmt_offset; /* the offset from GMT in seconds */ + PRInt32 tp_dst_offset; /* contribution of DST in seconds */ +} PRTimeParameters; + +/* + * PRExplodedTime -- + * + * Time broken down into human-readable components such as year, month, + * day, hour, minute, second, and microsecond. Time zone and daylight + * saving time corrections may be applied. If they are applied, the + * offsets from the GMT must be saved in the 'tm_params' field so that + * all the information is available to reconstruct GMT. + * + * Notes on porting: PRExplodedTime corrresponds to struct tm in + * ANSI C, with the following differences: + * - an additional field tm_usec; + * - replacing tm_isdst by tm_params; + * - the month field is spelled tm_month, not tm_mon; + * - we use absolute year, AD, not the year since 1900. + * The corresponding type in NSPR 1.0 is called PRTime. Below is + * a table of date/time type correspondence in the three APIs: + * API time since epoch time in components + * ANSI C time_t struct tm + * NSPR 1.0 PRInt64 PRTime + * NSPR 2.0 PRTime PRExplodedTime + */ + +typedef struct PRExplodedTime { + PRInt32 tm_usec; /* microseconds past tm_sec (0-99999) */ + PRInt32 tm_sec; /* seconds past tm_min (0-61, accomodating + up to two leap seconds) */ + PRInt32 tm_min; /* minutes past tm_hour (0-59) */ + PRInt32 tm_hour; /* hours past tm_day (0-23) */ + PRInt32 tm_mday; /* days past tm_mon (1-31, note that it + starts from 1) */ + PRInt32 tm_month; /* months past tm_year (0-11, Jan = 0) */ + PRInt16 tm_year; /* absolute year, AD (note that we do not + count from 1900) */ + + PRInt8 tm_wday; /* calculated day of the week + (0-6, Sun = 0) */ + PRInt16 tm_yday; /* calculated day of the year + (0-365, Jan 1 = 0) */ + + PRTimeParameters tm_params; /* time parameters used by conversion */ +} PRExplodedTime; + +/* + * PRTimeParamFn -- + * + * A function of PRTimeParamFn type returns the time zone and + * daylight saving time corrections for some geographic location, + * given the current time in GMT. The input argument gmt should + * point to a PRExplodedTime that is in GMT, i.e., whose + * tm_params contains all 0's. + * + * For any time zone other than GMT, the computation is intended to + * consist of two steps: + * - Figure out the time zone correction, tp_gmt_offset. This number + * usually depends on the geographic location only. But it may + * also depend on the current time. For example, all of China + * is one time zone right now. But this situation may change + * in the future. + * - Figure out the daylight saving time correction, tp_dst_offset. + * This number depends on both the geographic location and the + * current time. Most of the DST rules are expressed in local + * current time. If so, one should apply the time zone correction + * to GMT before applying the DST rules. + */ + +typedef PRTimeParameters (PR_CALLBACK *PRTimeParamFn)(const PRExplodedTime *gmt); + +/**********************************************************************/ +/****************************** FUNCTIONS *****************************/ +/**********************************************************************/ + +NSPR_API(PRTime) +PR_ImplodeTime(const PRExplodedTime *exploded); + +/* + * Adjust exploded time to normalize field overflows after manipulation. + * Note that the following fields of PRExplodedTime should not be + * manipulated: + * - tm_month and tm_year: because the number of days in a month and + * number of days in a year are not constant, it is ambiguous to + * manipulate the month and year fields, although one may be tempted + * to. For example, what does "a month from January 31st" mean? + * - tm_wday and tm_yday: these fields are calculated by NSPR. Users + * should treat them as "read-only". + */ + +NSPR_API(void) PR_NormalizeTime( + PRExplodedTime *exploded, PRTimeParamFn params); + +/**********************************************************************/ +/*********************** TIME PARAMETER FUNCTIONS *********************/ +/**********************************************************************/ + +/* Time parameters that represent Greenwich Mean Time */ +NSPR_API(PRTimeParameters) PR_GMTParameters(const PRExplodedTime *gmt); + +/* + * This parses a time/date string into a PRTime + * (microseconds after "1-Jan-1970 00:00:00 GMT"). + * It returns PR_SUCCESS on success, and PR_FAILURE + * if the time/date string can't be parsed. + * + * Many formats are handled, including: + * + * 14 Apr 89 03:20:12 + * 14 Apr 89 03:20 GMT + * Fri, 17 Mar 89 4:01:33 + * Fri, 17 Mar 89 4:01 GMT + * Mon Jan 16 16:12 PDT 1989 + * Mon Jan 16 16:12 +0130 1989 + * 6 May 1992 16:41-JST (Wednesday) + * 22-AUG-1993 10:59:12.82 + * 22-AUG-1993 10:59pm + * 22-AUG-1993 12:59am + * 22-AUG-1993 12:59 PM + * Friday, August 04, 1995 3:54 PM + * 06/21/95 04:24:34 PM + * 20/06/95 21:07 + * 95-06-08 19:32:48 EDT + * + * If the input string doesn't contain a description of the timezone, + * we consult the `default_to_gmt' to decide whether the string should + * be interpreted relative to the local time zone (PR_FALSE) or GMT (PR_TRUE). + * The correct value for this argument depends on what standard specified + * the time string which you are parsing. + */ + +/* + * This is the only funtion that should be called from outside base, and only + * from the unit test. + */ + +BASE_EXPORT PRStatus PR_ParseTimeString ( + const char *string, + PRBool default_to_gmt, + PRTime *result); + +#endif // BASE_PRTIME_H__
diff --git a/src/base/third_party/nspr/prtypes.h b/src/base/third_party/nspr/prtypes.h new file mode 100644 index 0000000..630df81 --- /dev/null +++ b/src/base/third_party/nspr/prtypes.h
@@ -0,0 +1,558 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape Portable Runtime (NSPR). + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* +** File: prtypes.h +** Description: Definitions of NSPR's basic types +** +** Prototypes and macros used to make up for deficiencies that we have found +** in ANSI environments. +** +** Since we do not wrap <stdlib.h> and all the other standard headers, authors +** of portable code will not know in general that they need these definitions. +** Instead of requiring these authors to find the dependent uses in their code +** and take the following steps only in those C files, we take steps once here +** for all C files. +**/ + +#ifndef prtypes_h___ +#define prtypes_h___ + +#ifdef MDCPUCFG +#include MDCPUCFG +#else +#include "base/third_party/nspr/prcpucfg.h" +#endif + +#include <stddef.h> + +/*********************************************************************** +** MACROS: PR_EXTERN +** PR_IMPLEMENT +** DESCRIPTION: +** These are only for externally visible routines and globals. For +** internal routines, just use "extern" for type checking and that +** will not export internal cross-file or forward-declared symbols. +** Define a macro for declaring procedures return types. We use this to +** deal with windoze specific type hackery for DLL definitions. Use +** PR_EXTERN when the prototype for the method is declared. Use +** PR_IMPLEMENT for the implementation of the method. +** +** Example: +** in dowhim.h +** PR_EXTERN( void ) DoWhatIMean( void ); +** in dowhim.c +** PR_IMPLEMENT( void ) DoWhatIMean( void ) { return; } +** +** +***********************************************************************/ +#if 1 + +/* +** Local change: the portions of NSPR used by the base module are +** implementation details. NSPR symbols do not need to be exported beyond +** the base module. For all platforms, avoid decorating functions with +** specific visibility and access keywords. +*/ + +#define PR_EXPORT(__type) extern __type +#define PR_EXPORT_DATA(__type) extern __type +#define PR_IMPORT(__type) extern __type +#define PR_IMPORT_DATA(__type) extern __type + +#define PR_EXTERN(__type) extern __type +#define PR_IMPLEMENT(__type) __type +#define PR_EXTERN_DATA(__type) extern __type +#define PR_IMPLEMENT_DATA(__type) __type + +#define PR_CALLBACK +#define PR_CALLBACK_DECL +#define PR_STATIC_CALLBACK(__x) static __x + +#elif defined(WIN32) + +#define PR_EXPORT(__type) extern __declspec(dllexport) __type +#define PR_EXPORT_DATA(__type) extern __declspec(dllexport) __type +#define PR_IMPORT(__type) __declspec(dllimport) __type +#define PR_IMPORT_DATA(__type) __declspec(dllimport) __type + +#define PR_EXTERN(__type) extern __declspec(dllexport) __type +#define PR_IMPLEMENT(__type) __declspec(dllexport) __type +#define PR_EXTERN_DATA(__type) extern __declspec(dllexport) __type +#define PR_IMPLEMENT_DATA(__type) __declspec(dllexport) __type + +#define PR_CALLBACK +#define PR_CALLBACK_DECL +#define PR_STATIC_CALLBACK(__x) static __x + +#elif defined(XP_BEOS) + +#define PR_EXPORT(__type) extern __declspec(dllexport) __type +#define PR_EXPORT_DATA(__type) extern __declspec(dllexport) __type +#define PR_IMPORT(__type) extern __declspec(dllexport) __type +#define PR_IMPORT_DATA(__type) extern __declspec(dllexport) __type + +#define PR_EXTERN(__type) extern __declspec(dllexport) __type +#define PR_IMPLEMENT(__type) __declspec(dllexport) __type +#define PR_EXTERN_DATA(__type) extern __declspec(dllexport) __type +#define PR_IMPLEMENT_DATA(__type) __declspec(dllexport) __type + +#define PR_CALLBACK +#define PR_CALLBACK_DECL +#define PR_STATIC_CALLBACK(__x) static __x + +#elif defined(XP_OS2) && defined(__declspec) + +#define PR_EXPORT(__type) extern __declspec(dllexport) __type +#define PR_EXPORT_DATA(__type) extern __declspec(dllexport) __type +#define PR_IMPORT(__type) extern __declspec(dllimport) __type +#define PR_IMPORT_DATA(__type) extern __declspec(dllimport) __type + +#define PR_EXTERN(__type) extern __declspec(dllexport) __type +#define PR_IMPLEMENT(__type) __declspec(dllexport) __type +#define PR_EXTERN_DATA(__type) extern __declspec(dllexport) __type +#define PR_IMPLEMENT_DATA(__type) __declspec(dllexport) __type + +#define PR_CALLBACK +#define PR_CALLBACK_DECL +#define PR_STATIC_CALLBACK(__x) static __x + +#elif defined(SYMBIAN) + +#define PR_EXPORT(__type) extern __declspec(dllexport) __type +#define PR_EXPORT_DATA(__type) extern __declspec(dllexport) __type +#ifdef __WINS__ +#define PR_IMPORT(__type) extern __declspec(dllexport) __type +#define PR_IMPORT_DATA(__type) extern __declspec(dllexport) __type +#else +#define PR_IMPORT(__type) extern __declspec(dllimport) __type +#define PR_IMPORT_DATA(__type) extern __declspec(dllimport) __type +#endif + +#define PR_EXTERN(__type) extern __type +#define PR_IMPLEMENT(__type) __type +#define PR_EXTERN_DATA(__type) extern __type +#define PR_IMPLEMENT_DATA(__type) __type + +#define PR_CALLBACK +#define PR_CALLBACK_DECL +#define PR_STATIC_CALLBACK(__x) static __x + +#else /* Unix */ + +/* GCC 3.3 and later support the visibility attribute. */ +#if (__GNUC__ >= 4) || \ + (__GNUC__ == 3 && __GNUC_MINOR__ >= 3) +#define PR_VISIBILITY_DEFAULT __attribute__((visibility("default"))) +#else +#define PR_VISIBILITY_DEFAULT +#endif + +#define PR_EXPORT(__type) extern PR_VISIBILITY_DEFAULT __type +#define PR_EXPORT_DATA(__type) extern PR_VISIBILITY_DEFAULT __type +#define PR_IMPORT(__type) extern PR_VISIBILITY_DEFAULT __type +#define PR_IMPORT_DATA(__type) extern PR_VISIBILITY_DEFAULT __type + +#define PR_EXTERN(__type) extern PR_VISIBILITY_DEFAULT __type +#define PR_IMPLEMENT(__type) PR_VISIBILITY_DEFAULT __type +#define PR_EXTERN_DATA(__type) extern PR_VISIBILITY_DEFAULT __type +#define PR_IMPLEMENT_DATA(__type) PR_VISIBILITY_DEFAULT __type +#define PR_CALLBACK +#define PR_CALLBACK_DECL +#define PR_STATIC_CALLBACK(__x) static __x + +#endif + +#if defined(_NSPR_BUILD_) +#define NSPR_API(__type) PR_EXPORT(__type) +#define NSPR_DATA_API(__type) PR_EXPORT_DATA(__type) +#else +#define NSPR_API(__type) PR_IMPORT(__type) +#define NSPR_DATA_API(__type) PR_IMPORT_DATA(__type) +#endif + +/*********************************************************************** +** MACROS: PR_BEGIN_MACRO +** PR_END_MACRO +** DESCRIPTION: +** Macro body brackets so that macros with compound statement definitions +** behave syntactically more like functions when called. +***********************************************************************/ +#define PR_BEGIN_MACRO do { +#define PR_END_MACRO } while (0) + +/*********************************************************************** +** MACROS: PR_BEGIN_EXTERN_C +** PR_END_EXTERN_C +** DESCRIPTION: +** Macro shorthands for conditional C++ extern block delimiters. +***********************************************************************/ +#ifdef __cplusplus +#define PR_BEGIN_EXTERN_C extern "C" { +#define PR_END_EXTERN_C } +#else +#define PR_BEGIN_EXTERN_C +#define PR_END_EXTERN_C +#endif + +/*********************************************************************** +** MACROS: PR_BIT +** PR_BITMASK +** DESCRIPTION: +** Bit masking macros. XXX n must be <= 31 to be portable +***********************************************************************/ +#define PR_BIT(n) ((PRUint32)1 << (n)) +#define PR_BITMASK(n) (PR_BIT(n) - 1) + +/*********************************************************************** +** MACROS: PR_ROUNDUP +** PR_MIN +** PR_MAX +** PR_ABS +** DESCRIPTION: +** Commonly used macros for operations on compatible types. +***********************************************************************/ +#define PR_ROUNDUP(x,y) ((((x)+((y)-1))/(y))*(y)) +#define PR_MIN(x,y) ((x)<(y)?(x):(y)) +#define PR_MAX(x,y) ((x)>(y)?(x):(y)) +#define PR_ABS(x) ((x)<0?-(x):(x)) + +PR_BEGIN_EXTERN_C + +/************************************************************************ +** TYPES: PRUint8 +** PRInt8 +** DESCRIPTION: +** The int8 types are known to be 8 bits each. There is no type that +** is equivalent to a plain "char". +************************************************************************/ +#if PR_BYTES_PER_BYTE == 1 +typedef unsigned char PRUint8; +/* +** Some cfront-based C++ compilers do not like 'signed char' and +** issue the warning message: +** warning: "signed" not implemented (ignored) +** For these compilers, we have to define PRInt8 as plain 'char'. +** Make sure that plain 'char' is indeed signed under these compilers. +*/ +#if (defined(HPUX) && defined(__cplusplus) \ + && !defined(__GNUC__) && __cplusplus < 199707L) \ + || (defined(SCO) && defined(__cplusplus) \ + && !defined(__GNUC__) && __cplusplus == 1L) +typedef char PRInt8; +#else +typedef signed char PRInt8; +#endif +#else +#error No suitable type for PRInt8/PRUint8 +#endif + +/************************************************************************ + * MACROS: PR_INT8_MAX + * PR_INT8_MIN + * PR_UINT8_MAX + * DESCRIPTION: + * The maximum and minimum values of a PRInt8 or PRUint8. +************************************************************************/ + +#define PR_INT8_MAX 127 +#define PR_INT8_MIN (-128) +#define PR_UINT8_MAX 255U + +/************************************************************************ +** TYPES: PRUint16 +** PRInt16 +** DESCRIPTION: +** The int16 types are known to be 16 bits each. +************************************************************************/ +#if PR_BYTES_PER_SHORT == 2 +typedef unsigned short PRUint16; +typedef short PRInt16; +#else +#error No suitable type for PRInt16/PRUint16 +#endif + +/************************************************************************ + * MACROS: PR_INT16_MAX + * PR_INT16_MIN + * PR_UINT16_MAX + * DESCRIPTION: + * The maximum and minimum values of a PRInt16 or PRUint16. +************************************************************************/ + +#define PR_INT16_MAX 32767 +#define PR_INT16_MIN (-32768) +#define PR_UINT16_MAX 65535U + +/************************************************************************ +** TYPES: PRUint32 +** PRInt32 +** DESCRIPTION: +** The int32 types are known to be 32 bits each. +************************************************************************/ +#if PR_BYTES_PER_INT == 4 +typedef unsigned int PRUint32; +typedef int PRInt32; +#define PR_INT32(x) x +#define PR_UINT32(x) x ## U +#elif PR_BYTES_PER_LONG == 4 +typedef unsigned long PRUint32; +typedef long PRInt32; +#define PR_INT32(x) x ## L +#define PR_UINT32(x) x ## UL +#else +#error No suitable type for PRInt32/PRUint32 +#endif + +/************************************************************************ + * MACROS: PR_INT32_MAX + * PR_INT32_MIN + * PR_UINT32_MAX + * DESCRIPTION: + * The maximum and minimum values of a PRInt32 or PRUint32. +************************************************************************/ + +#define PR_INT32_MAX PR_INT32(2147483647) +#define PR_INT32_MIN (-PR_INT32_MAX - 1) +#define PR_UINT32_MAX PR_UINT32(4294967295) + +/************************************************************************ +** TYPES: PRUint64 +** PRInt64 +** DESCRIPTION: +** The int64 types are known to be 64 bits each. Care must be used when +** declaring variables of type PRUint64 or PRInt64. Different hardware +** architectures and even different compilers have varying support for +** 64 bit values. The only guaranteed portability requires the use of +** the LL_ macros (see prlong.h). +************************************************************************/ +#ifdef HAVE_LONG_LONG +/* Keep this in sync with prlong.h. */ +/* + * On 64-bit Mac OS X, uint64 needs to be defined as unsigned long long to + * match uint64_t, otherwise our uint64 typedef conflicts with the uint64 + * typedef in cssmconfig.h, which CoreServices.h includes indirectly. + */ +#if PR_BYTES_PER_LONG == 8 && !defined(__APPLE__) +typedef long PRInt64; +typedef unsigned long PRUint64; +#elif defined(WIN32) && !defined(__GNUC__) +typedef __int64 PRInt64; +typedef unsigned __int64 PRUint64; +#else +typedef long long PRInt64; +typedef unsigned long long PRUint64; +#endif /* PR_BYTES_PER_LONG == 8 */ +#else /* !HAVE_LONG_LONG */ +typedef struct { +#ifdef IS_LITTLE_ENDIAN + PRUint32 lo, hi; +#else + PRUint32 hi, lo; +#endif +} PRInt64; +typedef PRInt64 PRUint64; +#endif /* !HAVE_LONG_LONG */ + +/************************************************************************ +** TYPES: PRUintn +** PRIntn +** DESCRIPTION: +** The PRIntn types are most appropriate for automatic variables. They are +** guaranteed to be at least 16 bits, though various architectures may +** define them to be wider (e.g., 32 or even 64 bits). These types are +** never valid for fields of a structure. +************************************************************************/ +#if PR_BYTES_PER_INT >= 2 +typedef int PRIntn; +typedef unsigned int PRUintn; +#else +#error 'sizeof(int)' not sufficient for platform use +#endif + +/************************************************************************ +** TYPES: PRFloat64 +** DESCRIPTION: +** NSPR's floating point type is always 64 bits. +************************************************************************/ +typedef double PRFloat64; + +/************************************************************************ +** TYPES: PRSize +** DESCRIPTION: +** A type for representing the size of objects. +************************************************************************/ +typedef size_t PRSize; + + +/************************************************************************ +** TYPES: PROffset32, PROffset64 +** DESCRIPTION: +** A type for representing byte offsets from some location. +************************************************************************/ +typedef PRInt32 PROffset32; +typedef PRInt64 PROffset64; + +/************************************************************************ +** TYPES: PRPtrDiff +** DESCRIPTION: +** A type for pointer difference. Variables of this type are suitable +** for storing a pointer or pointer subtraction. +************************************************************************/ +typedef ptrdiff_t PRPtrdiff; + +/************************************************************************ +** TYPES: PRUptrdiff +** DESCRIPTION: +** A type for pointer difference. Variables of this type are suitable +** for storing a pointer or pointer sutraction. +************************************************************************/ +#ifdef _WIN64 +typedef PRUint64 PRUptrdiff; +#else +typedef unsigned long PRUptrdiff; +#endif + +/************************************************************************ +** TYPES: PRBool +** DESCRIPTION: +** Use PRBool for variables and parameter types. Use PR_FALSE and PR_TRUE +** for clarity of target type in assignments and actual arguments. Use +** 'if (bool)', 'while (!bool)', '(bool) ? x : y' etc., to test booleans +** just as you would C int-valued conditions. +************************************************************************/ +typedef PRIntn PRBool; +#define PR_TRUE 1 +#define PR_FALSE 0 + +/************************************************************************ +** TYPES: PRPackedBool +** DESCRIPTION: +** Use PRPackedBool within structs where bitfields are not desirable +** but minimum and consistant overhead matters. +************************************************************************/ +typedef PRUint8 PRPackedBool; + +/* +** Status code used by some routines that have a single point of failure or +** special status return. +*/ +typedef enum { PR_FAILURE = -1, PR_SUCCESS = 0 } PRStatus; + +#ifndef __PRUNICHAR__ +#define __PRUNICHAR__ +#ifdef WIN32 +typedef wchar_t PRUnichar; +#else +typedef PRUint16 PRUnichar; +#endif +#endif + +/* +** WARNING: The undocumented data types PRWord and PRUword are +** only used in the garbage collection and arena code. Do not +** use PRWord and PRUword in new code. +** +** A PRWord is an integer that is the same size as a void*. +** It implements the notion of a "word" in the Java Virtual +** Machine. (See Sec. 3.4 "Words", The Java Virtual Machine +** Specification, Addison-Wesley, September 1996. +** http://java.sun.com/docs/books/vmspec/index.html.) +*/ +#ifdef _WIN64 +typedef PRInt64 PRWord; +typedef PRUint64 PRUword; +#else +typedef long PRWord; +typedef unsigned long PRUword; +#endif + +#if defined(NO_NSPR_10_SUPPORT) +#else +/********* ???????????????? FIX ME ??????????????????????????? *****/ +/********************** Some old definitions until pr=>ds transition is done ***/ +/********************** Also, we are still using NSPR 1.0. GC ******************/ +/* +** Fundamental NSPR macros, used nearly everywhere. +*/ + +#define PR_PUBLIC_API PR_IMPLEMENT + +/* +** Macro body brackets so that macros with compound statement definitions +** behave syntactically more like functions when called. +*/ +#define NSPR_BEGIN_MACRO do { +#define NSPR_END_MACRO } while (0) + +/* +** Macro shorthands for conditional C++ extern block delimiters. +*/ +#ifdef NSPR_BEGIN_EXTERN_C +#undef NSPR_BEGIN_EXTERN_C +#endif +#ifdef NSPR_END_EXTERN_C +#undef NSPR_END_EXTERN_C +#endif + +#ifdef __cplusplus +#define NSPR_BEGIN_EXTERN_C extern "C" { +#define NSPR_END_EXTERN_C } +#else +#define NSPR_BEGIN_EXTERN_C +#define NSPR_END_EXTERN_C +#endif + +/********* ????????????? End Fix me ?????????????????????????????? *****/ +#endif /* NO_NSPR_10_SUPPORT */ + +/* +** Compile-time assert. "condition" must be a constant expression. +** The macro can be used only in places where an "extern" declaration is +** allowed. +*/ +#define PR_STATIC_ASSERT(condition) \ + extern void pr_static_assert(int arg[(condition) ? 1 : -1]) + +PR_END_EXTERN_C + +#if !defined(NO_NSPR_10_SUPPORT) +#include "base/basictypes.h" +#endif + +#endif /* prtypes_h___ */ +
diff --git a/src/base/third_party/symbolize/LICENSE b/src/base/third_party/symbolize/LICENSE new file mode 100644 index 0000000..433a3d1 --- /dev/null +++ b/src/base/third_party/symbolize/LICENSE
@@ -0,0 +1,28 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/base/third_party/symbolize/README.chromium b/src/base/third_party/symbolize/README.chromium new file mode 100644 index 0000000..b86b4e0 --- /dev/null +++ b/src/base/third_party/symbolize/README.chromium
@@ -0,0 +1,18 @@ +Name: google-glog's symbolization library +URL: http://code.google.com/p/google-glog/ +License: BSD + +The following files are copied AS-IS from: +http://code.google.com/p/google-glog/source/browse/#svn/trunk/src (r76) + +- demangle.cc +- demangle.h +- symbolize.cc +- symbolize.h + +The following files are minimal stubs created for use in Chromium: + +- config.h +- glog/logging.h +- glog/raw_logging.h +- utilities.h
diff --git a/src/base/third_party/symbolize/config.h b/src/base/third_party/symbolize/config.h new file mode 100644 index 0000000..945f5a6 --- /dev/null +++ b/src/base/third_party/symbolize/config.h
@@ -0,0 +1,7 @@ +// Copyright (c) 2010 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. + +#define GOOGLE_NAMESPACE google +#define _END_GOOGLE_NAMESPACE_ } +#define _START_GOOGLE_NAMESPACE_ namespace google {
diff --git a/src/base/third_party/symbolize/demangle.cc b/src/base/third_party/symbolize/demangle.cc new file mode 100644 index 0000000..46556bf --- /dev/null +++ b/src/base/third_party/symbolize/demangle.cc
@@ -0,0 +1,1231 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Satoru Takabayashi + +#include <stdio.h> // for NULL +#include "demangle.h" + +_START_GOOGLE_NAMESPACE_ + +typedef struct { + const char *abbrev; + const char *real_name; +} AbbrevPair; + +// List of operators from Itanium C++ ABI. +static const AbbrevPair kOperatorList[] = { + { "nw", "new" }, + { "na", "new[]" }, + { "dl", "delete" }, + { "da", "delete[]" }, + { "ps", "+" }, + { "ng", "-" }, + { "ad", "&" }, + { "de", "*" }, + { "co", "~" }, + { "pl", "+" }, + { "mi", "-" }, + { "ml", "*" }, + { "dv", "/" }, + { "rm", "%" }, + { "an", "&" }, + { "or", "|" }, + { "eo", "^" }, + { "aS", "=" }, + { "pL", "+=" }, + { "mI", "-=" }, + { "mL", "*=" }, + { "dV", "/=" }, + { "rM", "%=" }, + { "aN", "&=" }, + { "oR", "|=" }, + { "eO", "^=" }, + { "ls", "<<" }, + { "rs", ">>" }, + { "lS", "<<=" }, + { "rS", ">>=" }, + { "eq", "==" }, + { "ne", "!=" }, + { "lt", "<" }, + { "gt", ">" }, + { "le", "<=" }, + { "ge", ">=" }, + { "nt", "!" }, + { "aa", "&&" }, + { "oo", "||" }, + { "pp", "++" }, + { "mm", "--" }, + { "cm", "," }, + { "pm", "->*" }, + { "pt", "->" }, + { "cl", "()" }, + { "ix", "[]" }, + { "qu", "?" }, + { "st", "sizeof" }, + { "sz", "sizeof" }, + { NULL, NULL }, +}; + +// List of builtin types from Itanium C++ ABI. +static const AbbrevPair kBuiltinTypeList[] = { + { "v", "void" }, + { "w", "wchar_t" }, + { "b", "bool" }, + { "c", "char" }, + { "a", "signed char" }, + { "h", "unsigned char" }, + { "s", "short" }, + { "t", "unsigned short" }, + { "i", "int" }, + { "j", "unsigned int" }, + { "l", "long" }, + { "m", "unsigned long" }, + { "x", "long long" }, + { "y", "unsigned long long" }, + { "n", "__int128" }, + { "o", "unsigned __int128" }, + { "f", "float" }, + { "d", "double" }, + { "e", "long double" }, + { "g", "__float128" }, + { "z", "ellipsis" }, + { NULL, NULL } +}; + +// List of substitutions Itanium C++ ABI. +static const AbbrevPair kSubstitutionList[] = { + { "St", "" }, + { "Sa", "allocator" }, + { "Sb", "basic_string" }, + // std::basic_string<char, std::char_traits<char>,std::allocator<char> > + { "Ss", "string"}, + // std::basic_istream<char, std::char_traits<char> > + { "Si", "istream" }, + // std::basic_ostream<char, std::char_traits<char> > + { "So", "ostream" }, + // std::basic_iostream<char, std::char_traits<char> > + { "Sd", "iostream" }, + { NULL, NULL } +}; + +// State needed for demangling. +typedef struct { + const char *mangled_cur; // Cursor of mangled name. + const char *mangled_end; // End of mangled name. + char *out_cur; // Cursor of output string. + const char *out_begin; // Beginning of output string. + const char *out_end; // End of output string. + const char *prev_name; // For constructors/destructors. + int prev_name_length; // For constructors/destructors. + int nest_level; // For nested names. + int number; // Remember the previous number. + bool append; // Append flag. + bool overflowed; // True if output gets overflowed. +} State; + +// We don't use strlen() in libc since it's not guaranteed to be async +// signal safe. +static size_t StrLen(const char *str) { + size_t len = 0; + while (*str != '\0') { + ++str; + ++len; + } + return len; +} + +// Returns true if "str" has "prefix" as a prefix. +static bool StrPrefix(const char *str, const char *prefix) { + size_t i = 0; + while (str[i] != '\0' && prefix[i] != '\0' && + str[i] == prefix[i]) { + ++i; + } + return prefix[i] == '\0'; // Consumed everything in "prefix". +} + +static void InitState(State *state, const char *mangled, + char *out, int out_size) { + state->mangled_cur = mangled; + state->mangled_end = mangled + StrLen(mangled); + state->out_cur = out; + state->out_begin = out; + state->out_end = out + out_size; + state->prev_name = NULL; + state->prev_name_length = -1; + state->nest_level = -1; + state->number = -1; + state->append = true; + state->overflowed = false; +} + +// Calculates the remaining length of the mangled name. +static int RemainingLength(State *state) { + return state->mangled_end - state->mangled_cur; +} + +// Returns true and advances "mangled_cur" if we find "c" at +// "mangled_cur" position. +static bool ParseChar(State *state, const char c) { + if (RemainingLength(state) >= 1 && *state->mangled_cur == c) { + ++state->mangled_cur; + return true; + } + return false; +} + +// Returns true and advances "mangled_cur" if we find "two_chars" at +// "mangled_cur" position. +static bool ParseTwoChar(State *state, const char *two_chars) { + if (RemainingLength(state) >= 2 && + state->mangled_cur[0] == two_chars[0] && + state->mangled_cur[1] == two_chars[1]) { + state->mangled_cur += 2; + return true; + } + return false; +} + +// Returns true and advances "mangled_cur" if we find any character in +// "char_class" at "mangled_cur" position. +static bool ParseCharClass(State *state, const char *char_class) { + if (state->mangled_cur == state->mangled_end) { + return false; + } + const char *p = char_class; + for (; *p != '\0'; ++p) { + if (*state->mangled_cur == *p) { + state->mangled_cur += 1; + return true; + } + } + return false; +} + +// This function is used for handling an optional non-terminal. +static bool Optional(bool status) { + return true; +} + +// This function is used for handling <non-terminal>+ syntax. +typedef bool (*ParseFunc)(State *); +static bool OneOrMore(ParseFunc parse_func, State *state) { + if (parse_func(state)) { + while (parse_func(state)) { + } + return true; + } + return false; +} + +// Append "str" at "out_cur". If there is an overflow, "overflowed" +// is set to true for later use. The output string is ensured to +// always terminate with '\0' as long as there is no overflow. +static void Append(State *state, const char * const str, const int length) { + int i; + for (i = 0; i < length; ++i) { + if (state->out_cur + 1 < state->out_end) { // +1 for '\0' + *state->out_cur = str[i]; + ++state->out_cur; + } else { + state->overflowed = true; + break; + } + } + if (!state->overflowed) { + *state->out_cur = '\0'; // Terminate it with '\0' + } +} + +// We don't use equivalents in libc to avoid locale issues. +static bool IsLower(char c) { + return c >= 'a' && c <= 'z'; +} + +static bool IsAlpha(char c) { + return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); +} + +// Append "str" with some tweaks, iff "append" state is true. +// Returns true so that it can be placed in "if" conditions. +static void MaybeAppendWithLength(State *state, const char * const str, + const int length) { + if (state->append && length > 0) { + // Append a space if the output buffer ends with '<' and "str" + // starts with '<' to avoid <<<. + if (str[0] == '<' && state->out_begin < state->out_cur && + state->out_cur[-1] == '<') { + Append(state, " ", 1); + } + // Remember the last identifier name for ctors/dtors. + if (IsAlpha(str[0]) || str[0] == '_') { + state->prev_name = state->out_cur; + state->prev_name_length = length; + } + Append(state, str, length); + } +} + +// A convenient wrapper arount MaybeAppendWithLength(). +static bool MaybeAppend(State *state, const char * const str) { + if (state->append) { + int length = StrLen(str); + MaybeAppendWithLength(state, str, length); + } + return true; +} + +// This function is used for handling nested names. +static bool EnterNestedName(State *state) { + state->nest_level = 0; + return true; +} + +// This function is used for handling nested names. +static bool LeaveNestedName(State *state, int prev_value) { + state->nest_level = prev_value; + return true; +} + +// Disable the append mode not to print function parameters, etc. +static bool DisableAppend(State *state) { + state->append = false; + return true; +} + +// Restore the append mode to the previous state. +static bool RestoreAppend(State *state, bool prev_value) { + state->append = prev_value; + return true; +} + +// Increase the nest level for nested names. +static void MaybeIncreaseNestLevel(State *state) { + if (state->nest_level > -1) { + ++state->nest_level; + } +} + +// Appends :: for nested names if necessary. +static void MaybeAppendSeparator(State *state) { + if (state->nest_level >= 1) { + MaybeAppend(state, "::"); + } +} + +// Cancel the last separator if necessary. +static void MaybeCancelLastSeparator(State *state) { + if (state->nest_level >= 1 && state->append && + state->out_begin <= state->out_cur - 2) { + state->out_cur -= 2; + *state->out_cur = '\0'; + } +} + +// Returns true if identifier pointed by "mangled_cur" is anonymous +// namespace. +static bool IdentifierIsAnonymousNamespace(State *state) { + const char anon_prefix[] = "_GLOBAL__N_"; + return (state->number > sizeof(anon_prefix) - 1 && // Should be longer. + StrPrefix(state->mangled_cur, anon_prefix)); +} + +// Forward declarations of our parsing functions. +static bool ParseMangledName(State *state); +static bool ParseEncoding(State *state); +static bool ParseName(State *state); +static bool ParseUnscopedName(State *state); +static bool ParseUnscopedTemplateName(State *state); +static bool ParseNestedName(State *state); +static bool ParsePrefix(State *state); +static bool ParseUnqualifiedName(State *state); +static bool ParseSourceName(State *state); +static bool ParseLocalSourceName(State *state); +static bool ParseNumber(State *state); +static bool ParseFloatNumber(State *state); +static bool ParseSeqId(State *state); +static bool ParseIdentifier(State *state); +static bool ParseOperatorName(State *state); +static bool ParseSpecialName(State *state); +static bool ParseCallOffset(State *state); +static bool ParseNVOffset(State *state); +static bool ParseVOffset(State *state); +static bool ParseCtorDtorName(State *state); +static bool ParseType(State *state); +static bool ParseCVQualifiers(State *state); +static bool ParseBuiltinType(State *state); +static bool ParseFunctionType(State *state); +static bool ParseBareFunctionType(State *state); +static bool ParseClassEnumType(State *state); +static bool ParseArrayType(State *state); +static bool ParsePointerToMemberType(State *state); +static bool ParseTemplateParam(State *state); +static bool ParseTemplateTemplateParam(State *state); +static bool ParseTemplateArgs(State *state); +static bool ParseTemplateArg(State *state); +static bool ParseExpression(State *state); +static bool ParseExprPrimary(State *state); +static bool ParseLocalName(State *state); +static bool ParseDiscriminator(State *state); +static bool ParseSubstitution(State *state); + +// Implementation note: the following code is a straightforward +// translation of the Itanium C++ ABI defined in BNF with a couple of +// exceptions. +// +// - Support GNU extensions not defined in the Itanium C++ ABI +// - <prefix> and <template-prefix> are combined to avoid infinite loop +// - Reorder patterns to shorten the code +// - Reorder patterns to give greedier functions precedence +// We'll mark "Less greedy than" for these cases in the code +// +// Each parsing function changes the state and returns true on +// success. Otherwise, don't change the state and returns false. To +// ensure that the state isn't changed in the latter case, we save the +// original state before we call more than one parsing functions +// consecutively with &&, and restore the state if unsuccessful. See +// ParseEncoding() as an example of this convention. We follow the +// convention throughout the code. +// +// Originally we tried to do demangling without following the full ABI +// syntax but it turned out we needed to follow the full syntax to +// parse complicated cases like nested template arguments. Note that +// implementing a full-fledged demangler isn't trivial (libiberty's +// cp-demangle.c has +4300 lines). +// +// Note that (foo) in <(foo) ...> is a modifier to be ignored. +// +// Reference: +// - Itanium C++ ABI +// <http://www.codesourcery.com/cxx-abi/abi.html#mangling> + +// <mangled-name> ::= _Z <encoding> +static bool ParseMangledName(State *state) { + if (ParseTwoChar(state, "_Z") && ParseEncoding(state)) { + // Append trailing version suffix if any. + // ex. _Z3foo@@GLIBCXX_3.4 + if (state->mangled_cur < state->mangled_end && + state->mangled_cur[0] == '@') { + MaybeAppend(state, state->mangled_cur); + state->mangled_cur = state->mangled_end; + } + return true; + } + return false; +} + +// <encoding> ::= <(function) name> <bare-function-type> +// ::= <(data) name> +// ::= <special-name> +static bool ParseEncoding(State *state) { + State copy = *state; + if (ParseName(state) && ParseBareFunctionType(state)) { + return true; + } + *state = copy; + + if (ParseName(state) || ParseSpecialName(state)) { + return true; + } + return false; +} + +// <name> ::= <nested-name> +// ::= <unscoped-template-name> <template-args> +// ::= <unscoped-name> +// ::= <local-name> +static bool ParseName(State *state) { + if (ParseNestedName(state) || ParseLocalName(state)) { + return true; + } + + State copy = *state; + if (ParseUnscopedTemplateName(state) && + ParseTemplateArgs(state)) { + return true; + } + *state = copy; + + // Less greedy than <unscoped-template-name> <template-args>. + if (ParseUnscopedName(state)) { + return true; + } + return false; +} + +// <unscoped-name> ::= <unqualified-name> +// ::= St <unqualified-name> +static bool ParseUnscopedName(State *state) { + if (ParseUnqualifiedName(state)) { + return true; + } + + State copy = *state; + if (ParseTwoChar(state, "St") && + MaybeAppend(state, "std::") && + ParseUnqualifiedName(state)) { + return true; + } + *state = copy; + return false; +} + +// <unscoped-template-name> ::= <unscoped-name> +// ::= <substitution> +static bool ParseUnscopedTemplateName(State *state) { + return ParseUnscopedName(state) || ParseSubstitution(state); +} + +// <nested-name> ::= N [<CV-qualifiers>] <prefix> <unqualified-name> E +// ::= N [<CV-qualifiers>] <template-prefix> <template-args> E +static bool ParseNestedName(State *state) { + State copy = *state; + if (ParseChar(state, 'N') && + EnterNestedName(state) && + Optional(ParseCVQualifiers(state)) && + ParsePrefix(state) && + LeaveNestedName(state, copy.nest_level) && + ParseChar(state, 'E')) { + return true; + } + *state = copy; + return false; +} + +// This part is tricky. If we literally translate them to code, we'll +// end up infinite loop. Hence we merge them to avoid the case. +// +// <prefix> ::= <prefix> <unqualified-name> +// ::= <template-prefix> <template-args> +// ::= <template-param> +// ::= <substitution> +// ::= # empty +// <template-prefix> ::= <prefix> <(template) unqualified-name> +// ::= <template-param> +// ::= <substitution> +static bool ParsePrefix(State *state) { + bool has_something = false; + while (true) { + MaybeAppendSeparator(state); + if (ParseTemplateParam(state) || + ParseSubstitution(state) || + ParseUnscopedName(state)) { + has_something = true; + MaybeIncreaseNestLevel(state); + continue; + } + MaybeCancelLastSeparator(state); + if (has_something && ParseTemplateArgs(state)) { + return ParsePrefix(state); + } else { + break; + } + } + return true; +} + +// <unqualified-name> ::= <operator-name> +// ::= <ctor-dtor-name> +// ::= <source-name> +// ::= <local-source-name> +static bool ParseUnqualifiedName(State *state) { + return (ParseOperatorName(state) || + ParseCtorDtorName(state) || + ParseSourceName(state) || + ParseLocalSourceName(state)); +} + +// <source-name> ::= <positive length number> <identifier> +static bool ParseSourceName(State *state) { + State copy = *state; + if (ParseNumber(state) && ParseIdentifier(state)) { + return true; + } + *state = copy; + return false; +} + +// <local-source-name> ::= L <source-name> [<discriminator>] +// +// References: +// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=31775 +// http://gcc.gnu.org/viewcvs?view=rev&revision=124467 +static bool ParseLocalSourceName(State *state) { + State copy = *state; + if (ParseChar(state, 'L') && ParseSourceName(state) && + Optional(ParseDiscriminator(state))) { + return true; + } + *state = copy; + return false; +} + +// <number> ::= [n] <non-negative decimal integer> +static bool ParseNumber(State *state) { + int sign = 1; + if (ParseChar(state, 'n')) { + sign = -1; + } + const char *p = state->mangled_cur; + int number = 0; + for (;p < state->mangled_end; ++p) { + if ((*p >= '0' && *p <= '9')) { + number = number * 10 + (*p - '0'); + } else { + break; + } + } + if (p != state->mangled_cur) { // Conversion succeeded. + state->mangled_cur = p; + state->number = number * sign; + return true; + } + return false; +} + +// Floating-point literals are encoded using a fixed-length lowercase +// hexadecimal string. +static bool ParseFloatNumber(State *state) { + const char *p = state->mangled_cur; + int number = 0; + for (;p < state->mangled_end; ++p) { + if ((*p >= '0' && *p <= '9')) { + number = number * 16 + (*p - '0'); + } else if (*p >= 'a' && *p <= 'f') { + number = number * 16 + (*p - 'a' + 10); + } else { + break; + } + } + if (p != state->mangled_cur) { // Conversion succeeded. + state->mangled_cur = p; + state->number = number; + return true; + } + return false; +} + +// The <seq-id> is a sequence number in base 36, +// using digits and upper case letters +static bool ParseSeqId(State *state) { + const char *p = state->mangled_cur; + int number = 0; + for (;p < state->mangled_end; ++p) { + if ((*p >= '0' && *p <= '9')) { + number = number * 36 + (*p - '0'); + } else if (*p >= 'A' && *p <= 'Z') { + number = number * 36 + (*p - 'A' + 10); + } else { + break; + } + } + if (p != state->mangled_cur) { // Conversion succeeded. + state->mangled_cur = p; + state->number = number; + return true; + } + return false; +} + +// <identifier> ::= <unqualified source code identifier> +static bool ParseIdentifier(State *state) { + if (state->number == -1 || + RemainingLength(state) < state->number) { + return false; + } + if (IdentifierIsAnonymousNamespace(state)) { + MaybeAppend(state, "(anonymous namespace)"); + } else { + MaybeAppendWithLength(state, state->mangled_cur, state->number); + } + state->mangled_cur += state->number; + state->number = -1; // Reset the number. + return true; +} + +// <operator-name> ::= nw, and other two letters cases +// ::= cv <type> # (cast) +// ::= v <digit> <source-name> # vendor extended operator +static bool ParseOperatorName(State *state) { + if (RemainingLength(state) < 2) { + return false; + } + // First check with "cv" (cast) case. + State copy = *state; + if (ParseTwoChar(state, "cv") && + MaybeAppend(state, "operator ") && + EnterNestedName(state) && + ParseType(state) && + LeaveNestedName(state, copy.nest_level)) { + return true; + } + *state = copy; + + // Then vendor extended operators. + if (ParseChar(state, 'v') && ParseCharClass(state, "0123456789") && + ParseSourceName(state)) { + return true; + } + *state = copy; + + // Other operator names should start with a lower alphabet followed + // by a lower/upper alphabet. + if (!(IsLower(state->mangled_cur[0]) && + IsAlpha(state->mangled_cur[1]))) { + return false; + } + // We may want to perform a binary search if we really need speed. + const AbbrevPair *p; + for (p = kOperatorList; p->abbrev != NULL; ++p) { + if (state->mangled_cur[0] == p->abbrev[0] && + state->mangled_cur[1] == p->abbrev[1]) { + MaybeAppend(state, "operator"); + if (IsLower(*p->real_name)) { // new, delete, etc. + MaybeAppend(state, " "); + } + MaybeAppend(state, p->real_name); + state->mangled_cur += 2; + return true; + } + } + return false; +} + +// <special-name> ::= TV <type> +// ::= TT <type> +// ::= TI <type> +// ::= TS <type> +// ::= Tc <call-offset> <call-offset> <(base) encoding> +// ::= GV <(object) name> +// ::= T <call-offset> <(base) encoding> +// G++ extensions: +// ::= TC <type> <(offset) number> _ <(base) type> +// ::= TF <type> +// ::= TJ <type> +// ::= GR <name> +// ::= GA <encoding> +// ::= Th <call-offset> <(base) encoding> +// ::= Tv <call-offset> <(base) encoding> +// +// Note: we don't care much about them since they don't appear in +// stack traces. The are special data. +static bool ParseSpecialName(State *state) { + State copy = *state; + if (ParseChar(state, 'T') && + ParseCharClass(state, "VTIS") && + ParseType(state)) { + return true; + } + *state = copy; + + if (ParseTwoChar(state, "Tc") && ParseCallOffset(state) && + ParseCallOffset(state) && ParseEncoding(state)) { + return true; + } + *state = copy; + + if (ParseTwoChar(state, "GV") && + ParseName(state)) { + return true; + } + *state = copy; + + if (ParseChar(state, 'T') && ParseCallOffset(state) && + ParseEncoding(state)) { + return true; + } + *state = copy; + + // G++ extensions + if (ParseTwoChar(state, "TC") && ParseType(state) && + ParseNumber(state) && ParseChar(state, '_') && + DisableAppend(state) && + ParseType(state)) { + RestoreAppend(state, copy.append); + return true; + } + *state = copy; + + if (ParseChar(state, 'T') && ParseCharClass(state, "FJ") && + ParseType(state)) { + return true; + } + *state = copy; + + if (ParseTwoChar(state, "GR") && ParseName(state)) { + return true; + } + *state = copy; + + if (ParseTwoChar(state, "GA") && ParseEncoding(state)) { + return true; + } + *state = copy; + + if (ParseChar(state, 'T') && ParseCharClass(state, "hv") && + ParseCallOffset(state) && ParseEncoding(state)) { + return true; + } + *state = copy; + return false; +} + +// <call-offset> ::= h <nv-offset> _ +// ::= v <v-offset> _ +static bool ParseCallOffset(State *state) { + State copy = *state; + if (ParseChar(state, 'h') && + ParseNVOffset(state) && ParseChar(state, '_')) { + return true; + } + *state = copy; + + if (ParseChar(state, 'v') && + ParseVOffset(state) && ParseChar(state, '_')) { + return true; + } + *state = copy; + + return false; +} + +// <nv-offset> ::= <(offset) number> +static bool ParseNVOffset(State *state) { + return ParseNumber(state); +} + +// <v-offset> ::= <(offset) number> _ <(virtual offset) number> +static bool ParseVOffset(State *state) { + State copy = *state; + if (ParseNumber(state) && ParseChar(state, '_') && + ParseNumber(state)) { + return true; + } + *state = copy; + return false; +} + +// <ctor-dtor-name> ::= C1 | C2 | C3 +// ::= D0 | D1 | D2 +static bool ParseCtorDtorName(State *state) { + State copy = *state; + if (ParseChar(state, 'C') && + ParseCharClass(state, "123")) { + const char * const prev_name = state->prev_name; + const int prev_name_length = state->prev_name_length; + MaybeAppendWithLength(state, prev_name, prev_name_length); + return true; + } + *state = copy; + + if (ParseChar(state, 'D') && + ParseCharClass(state, "012")) { + const char * const prev_name = state->prev_name; + const int prev_name_length = state->prev_name_length; + MaybeAppend(state, "~"); + MaybeAppendWithLength(state, prev_name, prev_name_length); + return true; + } + *state = copy; + return false; +} + +// <type> ::= <CV-qualifiers> <type> +// ::= P <type> +// ::= R <type> +// ::= C <type> +// ::= G <type> +// ::= U <source-name> <type> +// ::= <builtin-type> +// ::= <function-type> +// ::= <class-enum-type> +// ::= <array-type> +// ::= <pointer-to-member-type> +// ::= <template-template-param> <template-args> +// ::= <template-param> +// ::= <substitution> +static bool ParseType(State *state) { + // We should check CV-qualifers, and PRGC things first. + State copy = *state; + if (ParseCVQualifiers(state) && ParseType(state)) { + return true; + } + *state = copy; + + if (ParseCharClass(state, "PRCG") && ParseType(state)) { + return true; + } + *state = copy; + + if (ParseChar(state, 'U') && ParseSourceName(state) && + ParseType(state)) { + return true; + } + *state = copy; + + if (ParseBuiltinType(state) || + ParseFunctionType(state) || + ParseClassEnumType(state) || + ParseArrayType(state) || + ParsePointerToMemberType(state) || + ParseSubstitution(state)) { + return true; + } + + if (ParseTemplateTemplateParam(state) && + ParseTemplateArgs(state)) { + return true; + } + *state = copy; + + // Less greedy than <template-template-param> <template-args>. + if (ParseTemplateParam(state)) { + return true; + } + + return false; +} + +// <CV-qualifiers> ::= [r] [V] [K] +// We don't allow empty <CV-qualifiers> to avoid infinite loop in +// ParseType(). +static bool ParseCVQualifiers(State *state) { + int num_cv_qualifiers = 0; + num_cv_qualifiers += ParseChar(state, 'r'); + num_cv_qualifiers += ParseChar(state, 'V'); + num_cv_qualifiers += ParseChar(state, 'K'); + return num_cv_qualifiers > 0; +} + +// <builtin-type> ::= v, etc. +// ::= u <source-name> +static bool ParseBuiltinType(State *state) { + const AbbrevPair *p; + for (p = kBuiltinTypeList; p->abbrev != NULL; ++p) { + if (state->mangled_cur[0] == p->abbrev[0]) { + MaybeAppend(state, p->real_name); + ++state->mangled_cur; + return true; + } + } + + State copy = *state; + if (ParseChar(state, 'u') && ParseSourceName(state)) { + return true; + } + *state = copy; + return false; +} + +// <function-type> ::= F [Y] <bare-function-type> E +static bool ParseFunctionType(State *state) { + State copy = *state; + if (ParseChar(state, 'F') && Optional(ParseChar(state, 'Y')) && + ParseBareFunctionType(state) && ParseChar(state, 'E')) { + return true; + } + *state = copy; + return false; +} + +// <bare-function-type> ::= <(signature) type>+ +static bool ParseBareFunctionType(State *state) { + State copy = *state; + DisableAppend(state); + if (OneOrMore(ParseType, state)) { + RestoreAppend(state, copy.append); + MaybeAppend(state, "()"); + return true; + } + *state = copy; + return false; +} + +// <class-enum-type> ::= <name> +static bool ParseClassEnumType(State *state) { + return ParseName(state); +} + +// <array-type> ::= A <(positive dimension) number> _ <(element) type> +// ::= A [<(dimension) expression>] _ <(element) type> +static bool ParseArrayType(State *state) { + State copy = *state; + if (ParseChar(state, 'A') && ParseNumber(state) && + ParseChar(state, '_') && ParseType(state)) { + return true; + } + *state = copy; + + if (ParseChar(state, 'A') && Optional(ParseExpression(state)) && + ParseChar(state, '_') && ParseType(state)) { + return true; + } + *state = copy; + return false; +} + +// <pointer-to-member-type> ::= M <(class) type> <(member) type> +static bool ParsePointerToMemberType(State *state) { + State copy = *state; + if (ParseChar(state, 'M') && ParseType(state) && + ParseType(state)) { + return true; + } + *state = copy; + return false; +} + +// <template-param> ::= T_ +// ::= T <parameter-2 non-negative number> _ +static bool ParseTemplateParam(State *state) { + if (ParseTwoChar(state, "T_")) { + MaybeAppend(state, "?"); // We don't support template substitutions. + return true; + } + + State copy = *state; + if (ParseChar(state, 'T') && ParseNumber(state) && + ParseChar(state, '_')) { + MaybeAppend(state, "?"); // We don't support template substitutions. + return true; + } + *state = copy; + return false; +} + + +// <template-template-param> ::= <template-param> +// ::= <substitution> +static bool ParseTemplateTemplateParam(State *state) { + return (ParseTemplateParam(state) || + ParseSubstitution(state)); +} + +// <template-args> ::= I <template-arg>+ E +static bool ParseTemplateArgs(State *state) { + State copy = *state; + DisableAppend(state); + if (ParseChar(state, 'I') && + OneOrMore(ParseTemplateArg, state) && + ParseChar(state, 'E')) { + RestoreAppend(state, copy.append); + MaybeAppend(state, "<>"); + return true; + } + *state = copy; + return false; +} + +// <template-arg> ::= <type> +// ::= <expr-primary> +// ::= X <expression> E +static bool ParseTemplateArg(State *state) { + if (ParseType(state) || + ParseExprPrimary(state)) { + return true; + } + + State copy = *state; + if (ParseChar(state, 'X') && ParseExpression(state) && + ParseChar(state, 'E')) { + return true; + } + *state = copy; + return false; +} + +// <expression> ::= <template-param> +// ::= <expr-primary> +// ::= <unary operator-name> <expression> +// ::= <binary operator-name> <expression> <expression> +// ::= <trinary operator-name> <expression> <expression> +// <expression> +// ::= st <type> +// ::= sr <type> <unqualified-name> <template-args> +// ::= sr <type> <unqualified-name> +static bool ParseExpression(State *state) { + if (ParseTemplateParam(state) || ParseExprPrimary(state)) { + return true; + } + + State copy = *state; + if (ParseOperatorName(state) && + ParseExpression(state) && + ParseExpression(state) && + ParseExpression(state)) { + return true; + } + *state = copy; + + if (ParseOperatorName(state) && + ParseExpression(state) && + ParseExpression(state)) { + return true; + } + *state = copy; + + if (ParseOperatorName(state) && + ParseExpression(state)) { + return true; + } + *state = copy; + + if (ParseTwoChar(state, "st") && ParseType(state)) { + return true; + } + *state = copy; + + if (ParseTwoChar(state, "sr") && ParseType(state) && + ParseUnqualifiedName(state) && + ParseTemplateArgs(state)) { + return true; + } + *state = copy; + + if (ParseTwoChar(state, "sr") && ParseType(state) && + ParseUnqualifiedName(state)) { + return true; + } + *state = copy; + return false; +} + +// <expr-primary> ::= L <type> <(value) number> E +// ::= L <type> <(value) float> E +// ::= L <mangled-name> E +// // A bug in g++'s C++ ABI version 2 (-fabi-version=2). +// ::= LZ <encoding> E +static bool ParseExprPrimary(State *state) { + State copy = *state; + if (ParseChar(state, 'L') && ParseType(state) && + ParseNumber(state) && + ParseChar(state, 'E')) { + return true; + } + *state = copy; + + if (ParseChar(state, 'L') && ParseType(state) && + ParseFloatNumber(state) && + ParseChar(state, 'E')) { + return true; + } + *state = copy; + + if (ParseChar(state, 'L') && ParseMangledName(state) && + ParseChar(state, 'E')) { + return true; + } + *state = copy; + + if (ParseTwoChar(state, "LZ") && ParseEncoding(state) && + ParseChar(state, 'E')) { + return true; + } + *state = copy; + + return false; +} + +// <local-name> := Z <(function) encoding> E <(entity) name> +// [<discriminator>] +// := Z <(function) encoding> E s [<discriminator>] +static bool ParseLocalName(State *state) { + State copy = *state; + if (ParseChar(state, 'Z') && ParseEncoding(state) && + ParseChar(state, 'E') && MaybeAppend(state, "::") && + ParseName(state) && Optional(ParseDiscriminator(state))) { + return true; + } + *state = copy; + + if (ParseChar(state, 'Z') && ParseEncoding(state) && + ParseTwoChar(state, "Es") && Optional(ParseDiscriminator(state))) { + return true; + } + *state = copy; + return false; +} + +// <discriminator> := _ <(non-negative) number> +static bool ParseDiscriminator(State *state) { + State copy = *state; + if (ParseChar(state, '_') && ParseNumber(state)) { + return true; + } + *state = copy; + return false; +} + +// <substitution> ::= S_ +// ::= S <seq-id> _ +// ::= St, etc. +static bool ParseSubstitution(State *state) { + if (ParseTwoChar(state, "S_")) { + MaybeAppend(state, "?"); // We don't support substitutions. + return true; + } + + State copy = *state; + if (ParseChar(state, 'S') && ParseSeqId(state) && + ParseChar(state, '_')) { + MaybeAppend(state, "?"); // We don't support substitutions. + return true; + } + *state = copy; + + // Expand abbreviations like "St" => "std". + if (ParseChar(state, 'S')) { + const AbbrevPair *p; + for (p = kSubstitutionList; p->abbrev != NULL; ++p) { + if (state->mangled_cur[0] == p->abbrev[1]) { + MaybeAppend(state, "std"); + if (p->real_name[0] != '\0') { + MaybeAppend(state, "::"); + MaybeAppend(state, p->real_name); + } + state->mangled_cur += 1; + return true; + } + } + } + *state = copy; + return false; +} + +// The demangler entry point. +bool Demangle(const char *mangled, char *out, int out_size) { + State state; + InitState(&state, mangled, out, out_size); + return (ParseMangledName(&state) && + state.overflowed == false && + RemainingLength(&state) == 0); +} + +_END_GOOGLE_NAMESPACE_
diff --git a/src/base/third_party/symbolize/demangle.h b/src/base/third_party/symbolize/demangle.h new file mode 100644 index 0000000..9c75915 --- /dev/null +++ b/src/base/third_party/symbolize/demangle.h
@@ -0,0 +1,84 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Satoru Takabayashi +// +// An async-signal-safe and thread-safe demangler for Itanium C++ ABI +// (aka G++ V3 ABI). + +// The demangler is implemented to be used in async signal handlers to +// symbolize stack traces. We cannot use libstdc++'s +// abi::__cxa_demangle() in such signal handlers since it's not async +// signal safe (it uses malloc() internally). +// +// Note that this demangler doesn't support full demangling. More +// specifically, it doesn't print types of function parameters and +// types of template arguments. It just skips them. However, it's +// still very useful to extract basic information such as class, +// function, constructor, destructor, and operator names. +// +// See the implementation note in demangle.cc if you are interested. +// +// Example: +// +// | Mangled Name | The Demangler | abi::__cxa_demangle() +// |---------------|---------------|----------------------- +// | _Z1fv | f() | f() +// | _Z1fi | f() | f(int) +// | _Z3foo3bar | foo() | foo(bar) +// | _Z1fIiEvi | f<>() | void f<int>(int) +// | _ZN1N1fE | N::f | N::f +// | _ZN3Foo3BarEv | Foo::Bar() | Foo::Bar() +// | _Zrm1XS_" | operator%() | operator%(X, X) +// | _ZN3FooC1Ev | Foo::Foo() | Foo::Foo() +// | _Z1fSs | f() | f(std::basic_string<char, +// | | | std::char_traits<char>, +// | | | std::allocator<char> >) +// +// See the unit test for more examples. +// +// Note: we might want to write demanglers for ABIs other than Itanium +// C++ ABI in the future. +// + +#ifndef BASE_DEMANGLE_H_ +#define BASE_DEMANGLE_H_ + +#include "config.h" + +_START_GOOGLE_NAMESPACE_ + +// Demangle "mangled". On success, return true and write the +// demangled symbol name to "out". Otherwise, return false. +// "out" is modified even if demangling is unsuccessful. +bool Demangle(const char *mangled, char *out, int out_size); + +_END_GOOGLE_NAMESPACE_ + +#endif // BASE_DEMANGLE_H_
diff --git a/src/base/third_party/symbolize/glog/logging.h b/src/base/third_party/symbolize/glog/logging.h new file mode 100644 index 0000000..a42c306 --- /dev/null +++ b/src/base/third_party/symbolize/glog/logging.h
@@ -0,0 +1,5 @@ +// Copyright (c) 2010 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. + +// Empty.
diff --git a/src/base/third_party/symbolize/glog/raw_logging.h b/src/base/third_party/symbolize/glog/raw_logging.h new file mode 100644 index 0000000..f5515c4 --- /dev/null +++ b/src/base/third_party/symbolize/glog/raw_logging.h
@@ -0,0 +1,6 @@ +// Copyright (c) 2010 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. + +#define WARNING 1; +#define RAW_LOG(severity, ...); // Do nothing.
diff --git a/src/base/third_party/symbolize/symbolize.cc b/src/base/third_party/symbolize/symbolize.cc new file mode 100644 index 0000000..f765fae --- /dev/null +++ b/src/base/third_party/symbolize/symbolize.cc
@@ -0,0 +1,685 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Satoru Takabayashi +// Stack-footprint reduction work done by Raksit Ashok +// +// Implementation note: +// +// We don't use heaps but only use stacks. We want to reduce the +// stack consumption so that the symbolizer can run on small stacks. +// +// Here are some numbers collected with GCC 4.1.0 on x86: +// - sizeof(Elf32_Sym) = 16 +// - sizeof(Elf32_Shdr) = 40 +// - sizeof(Elf64_Sym) = 24 +// - sizeof(Elf64_Shdr) = 64 +// +// This implementation is intended to be async-signal-safe but uses +// some functions which are not guaranteed to be so, such as memchr() +// and memmove(). We assume they are async-signal-safe. +// + +#include "build/build_config.h" +#include "utilities.h" + +#if defined(HAVE_SYMBOLIZE) + +#include <limits> + +#include "symbolize.h" +#include "demangle.h" + +_START_GOOGLE_NAMESPACE_ + +// We don't use assert() since it's not guaranteed to be +// async-signal-safe. Instead we define a minimal assertion +// macro. So far, we don't need pretty printing for __FILE__, etc. + +// A wrapper for abort() to make it callable in ? :. +static int AssertFail() { + abort(); + return 0; // Should not reach. +} + +#define SAFE_ASSERT(expr) ((expr) ? 0 : AssertFail()) + +static SymbolizeCallback g_symbolize_callback = NULL; +void InstallSymbolizeCallback(SymbolizeCallback callback) { + g_symbolize_callback = callback; +} + +// This function wraps the Demangle function to provide an interface +// where the input symbol is demangled in-place. +// To keep stack consumption low, we would like this function to not +// get inlined. +static ATTRIBUTE_NOINLINE void DemangleInplace(char *out, int out_size) { + char demangled[256]; // Big enough for sane demangled symbols. + if (Demangle(out, demangled, sizeof(demangled))) { + // Demangling succeeded. Copy to out if the space allows. + int len = strlen(demangled); + if (len + 1 <= out_size) { // +1 for '\0'. + SAFE_ASSERT(len < sizeof(demangled)); + memmove(out, demangled, len + 1); + } + } +} + +_END_GOOGLE_NAMESPACE_ + +#if defined(__ELF__) + +#include <dlfcn.h> +#if defined(OS_OPENBSD) +#include <sys/exec_elf.h> +#else +#include <elf.h> +#endif +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "symbolize.h" +#include "config.h" +#include "glog/raw_logging.h" + +// Re-runs fn until it doesn't cause EINTR. +#define NO_INTR(fn) do {} while ((fn) < 0 && errno == EINTR) + +_START_GOOGLE_NAMESPACE_ + +// Read up to "count" bytes from file descriptor "fd" into the buffer +// starting at "buf" while handling short reads and EINTR. On +// success, return the number of bytes read. Otherwise, return -1. +static ssize_t ReadPersistent(const int fd, void *buf, const size_t count) { + SAFE_ASSERT(fd >= 0); + SAFE_ASSERT(count <= std::numeric_limits<ssize_t>::max()); + char *buf0 = reinterpret_cast<char *>(buf); + ssize_t num_bytes = 0; + while (num_bytes < count) { + ssize_t len; + NO_INTR(len = read(fd, buf0 + num_bytes, count - num_bytes)); + if (len < 0) { // There was an error other than EINTR. + return -1; + } + if (len == 0) { // Reached EOF. + break; + } + num_bytes += len; + } + SAFE_ASSERT(num_bytes <= count); + return num_bytes; +} + +// Read up to "count" bytes from "offset" in the file pointed by file +// descriptor "fd" into the buffer starting at "buf". On success, +// return the number of bytes read. Otherwise, return -1. +static ssize_t ReadFromOffset(const int fd, void *buf, + const size_t count, const off_t offset) { + off_t off = lseek(fd, offset, SEEK_SET); + if (off == (off_t)-1) { + return -1; + } + return ReadPersistent(fd, buf, count); +} + +// Try reading exactly "count" bytes from "offset" bytes in a file +// pointed by "fd" into the buffer starting at "buf" while handling +// short reads and EINTR. On success, return true. Otherwise, return +// false. +static bool ReadFromOffsetExact(const int fd, void *buf, + const size_t count, const off_t offset) { + ssize_t len = ReadFromOffset(fd, buf, count, offset); + return len == count; +} + +// Returns elf_header.e_type if the file pointed by fd is an ELF binary. +static int FileGetElfType(const int fd) { + ElfW(Ehdr) elf_header; + if (!ReadFromOffsetExact(fd, &elf_header, sizeof(elf_header), 0)) { + return -1; + } + if (memcmp(elf_header.e_ident, ELFMAG, SELFMAG) != 0) { + return -1; + } + return elf_header.e_type; +} + +// Read the section headers in the given ELF binary, and if a section +// of the specified type is found, set the output to this section header +// and return true. Otherwise, return false. +// To keep stack consumption low, we would like this function to not get +// inlined. +static ATTRIBUTE_NOINLINE bool +GetSectionHeaderByType(const int fd, ElfW(Half) sh_num, const off_t sh_offset, + ElfW(Word) type, ElfW(Shdr) *out) { + // Read at most 16 section headers at a time to save read calls. + ElfW(Shdr) buf[16]; + for (int i = 0; i < sh_num;) { + const ssize_t num_bytes_left = (sh_num - i) * sizeof(buf[0]); + const ssize_t num_bytes_to_read = + (sizeof(buf) > num_bytes_left) ? num_bytes_left : sizeof(buf); + const ssize_t len = ReadFromOffset(fd, buf, num_bytes_to_read, + sh_offset + i * sizeof(buf[0])); + SAFE_ASSERT(len % sizeof(buf[0]) == 0); + const ssize_t num_headers_in_buf = len / sizeof(buf[0]); + SAFE_ASSERT(num_headers_in_buf <= sizeof(buf) / sizeof(buf[0])); + for (int j = 0; j < num_headers_in_buf; ++j) { + if (buf[j].sh_type == type) { + *out = buf[j]; + return true; + } + } + i += num_headers_in_buf; + } + return false; +} + +// There is no particular reason to limit section name to 63 characters, +// but there has (as yet) been no need for anything longer either. +const int kMaxSectionNameLen = 64; + +// name_len should include terminating '\0'. +bool GetSectionHeaderByName(int fd, const char *name, size_t name_len, + ElfW(Shdr) *out) { + ElfW(Ehdr) elf_header; + if (!ReadFromOffsetExact(fd, &elf_header, sizeof(elf_header), 0)) { + return false; + } + + ElfW(Shdr) shstrtab; + off_t shstrtab_offset = (elf_header.e_shoff + + elf_header.e_shentsize * elf_header.e_shstrndx); + if (!ReadFromOffsetExact(fd, &shstrtab, sizeof(shstrtab), shstrtab_offset)) { + return false; + } + + for (int i = 0; i < elf_header.e_shnum; ++i) { + off_t section_header_offset = (elf_header.e_shoff + + elf_header.e_shentsize * i); + if (!ReadFromOffsetExact(fd, out, sizeof(*out), section_header_offset)) { + return false; + } + char header_name[kMaxSectionNameLen]; + if (sizeof(header_name) < name_len) { + RAW_LOG(WARNING, "Section name '%s' is too long (%"PRIuS"); " + "section will not be found (even if present).", name, name_len); + // No point in even trying. + return false; + } + off_t name_offset = shstrtab.sh_offset + out->sh_name; + ssize_t n_read = ReadFromOffset(fd, &header_name, name_len, name_offset); + if (n_read == -1) { + return false; + } else if (n_read != name_len) { + // Short read -- name could be at end of file. + continue; + } + if (memcmp(header_name, name, name_len) == 0) { + return true; + } + } + return false; +} + +// Read a symbol table and look for the symbol containing the +// pc. Iterate over symbols in a symbol table and look for the symbol +// containing "pc". On success, return true and write the symbol name +// to out. Otherwise, return false. +// To keep stack consumption low, we would like this function to not get +// inlined. +static ATTRIBUTE_NOINLINE bool +FindSymbol(uint64_t pc, const int fd, char *out, int out_size, + uint64_t symbol_offset, const ElfW(Shdr) *strtab, + const ElfW(Shdr) *symtab) { + if (symtab == NULL) { + return false; + } + const int num_symbols = symtab->sh_size / symtab->sh_entsize; + for (int i = 0; i < num_symbols;) { + off_t offset = symtab->sh_offset + i * symtab->sh_entsize; + + // If we are reading Elf64_Sym's, we want to limit this array to + // 32 elements (to keep stack consumption low), otherwise we can + // have a 64 element Elf32_Sym array. +#if __WORDSIZE == 64 +#define NUM_SYMBOLS 32 +#else +#define NUM_SYMBOLS 64 +#endif + + // Read at most NUM_SYMBOLS symbols at once to save read() calls. + ElfW(Sym) buf[NUM_SYMBOLS]; + const ssize_t len = ReadFromOffset(fd, &buf, sizeof(buf), offset); + SAFE_ASSERT(len % sizeof(buf[0]) == 0); + const ssize_t num_symbols_in_buf = len / sizeof(buf[0]); + SAFE_ASSERT(num_symbols_in_buf <= sizeof(buf)/sizeof(buf[0])); + for (int j = 0; j < num_symbols_in_buf; ++j) { + const ElfW(Sym)& symbol = buf[j]; + uint64_t start_address = symbol.st_value; + start_address += symbol_offset; + uint64_t end_address = start_address + symbol.st_size; + if (symbol.st_value != 0 && // Skip null value symbols. + symbol.st_shndx != 0 && // Skip undefined symbols. + start_address <= pc && pc < end_address) { + ssize_t len1 = ReadFromOffset(fd, out, out_size, + strtab->sh_offset + symbol.st_name); + if (len1 <= 0 || memchr(out, '\0', out_size) == NULL) { + return false; + } + return true; // Obtained the symbol name. + } + } + i += num_symbols_in_buf; + } + return false; +} + +// Get the symbol name of "pc" from the file pointed by "fd". Process +// both regular and dynamic symbol tables if necessary. On success, +// write the symbol name to "out" and return true. Otherwise, return +// false. +static bool GetSymbolFromObjectFile(const int fd, uint64_t pc, + char *out, int out_size, + uint64_t map_start_address) { + // Read the ELF header. + ElfW(Ehdr) elf_header; + if (!ReadFromOffsetExact(fd, &elf_header, sizeof(elf_header), 0)) { + return false; + } + + uint64_t symbol_offset = 0; + if (elf_header.e_type == ET_DYN) { // DSO needs offset adjustment. + symbol_offset = map_start_address; + } + + ElfW(Shdr) symtab, strtab; + + // Consult a regular symbol table first. + if (!GetSectionHeaderByType(fd, elf_header.e_shnum, elf_header.e_shoff, + SHT_SYMTAB, &symtab)) { + return false; + } + if (!ReadFromOffsetExact(fd, &strtab, sizeof(strtab), elf_header.e_shoff + + symtab.sh_link * sizeof(symtab))) { + return false; + } + if (FindSymbol(pc, fd, out, out_size, symbol_offset, + &strtab, &symtab)) { + return true; // Found the symbol in a regular symbol table. + } + + // If the symbol is not found, then consult a dynamic symbol table. + if (!GetSectionHeaderByType(fd, elf_header.e_shnum, elf_header.e_shoff, + SHT_DYNSYM, &symtab)) { + return false; + } + if (!ReadFromOffsetExact(fd, &strtab, sizeof(strtab), elf_header.e_shoff + + symtab.sh_link * sizeof(symtab))) { + return false; + } + if (FindSymbol(pc, fd, out, out_size, symbol_offset, + &strtab, &symtab)) { + return true; // Found the symbol in a dynamic symbol table. + } + + return false; +} + +namespace { +// Thin wrapper around a file descriptor so that the file descriptor +// gets closed for sure. +struct FileDescriptor { + const int fd_; + explicit FileDescriptor(int fd) : fd_(fd) {} + ~FileDescriptor() { + if (fd_ >= 0) { + NO_INTR(close(fd_)); + } + } + int get() { return fd_; } + + private: + explicit FileDescriptor(const FileDescriptor&); + void operator=(const FileDescriptor&); +}; + +// Helper class for reading lines from file. +// +// Note: we don't use ProcMapsIterator since the object is big (it has +// a 5k array member) and uses async-unsafe functions such as sscanf() +// and snprintf(). +class LineReader { + public: + explicit LineReader(int fd, char *buf, int buf_len) : fd_(fd), + buf_(buf), buf_len_(buf_len), bol_(buf), eol_(buf), eod_(buf) { + } + + // Read '\n'-terminated line from file. On success, modify "bol" + // and "eol", then return true. Otherwise, return false. + // + // Note: if the last line doesn't end with '\n', the line will be + // dropped. It's an intentional behavior to make the code simple. + bool ReadLine(const char **bol, const char **eol) { + if (BufferIsEmpty()) { // First time. + const ssize_t num_bytes = ReadPersistent(fd_, buf_, buf_len_); + if (num_bytes <= 0) { // EOF or error. + return false; + } + eod_ = buf_ + num_bytes; + bol_ = buf_; + } else { + bol_ = eol_ + 1; // Advance to the next line in the buffer. + SAFE_ASSERT(bol_ <= eod_); // "bol_" can point to "eod_". + if (!HasCompleteLine()) { + const int incomplete_line_length = eod_ - bol_; + // Move the trailing incomplete line to the beginning. + memmove(buf_, bol_, incomplete_line_length); + // Read text from file and append it. + char * const append_pos = buf_ + incomplete_line_length; + const int capacity_left = buf_len_ - incomplete_line_length; + const ssize_t num_bytes = ReadPersistent(fd_, append_pos, + capacity_left); + if (num_bytes <= 0) { // EOF or error. + return false; + } + eod_ = append_pos + num_bytes; + bol_ = buf_; + } + } + eol_ = FindLineFeed(); + if (eol_ == NULL) { // '\n' not found. Malformed line. + return false; + } + *eol_ = '\0'; // Replace '\n' with '\0'. + + *bol = bol_; + *eol = eol_; + return true; + } + + // Beginning of line. + const char *bol() { + return bol_; + } + + // End of line. + const char *eol() { + return eol_; + } + + private: + explicit LineReader(const LineReader&); + void operator=(const LineReader&); + + char *FindLineFeed() { + return reinterpret_cast<char *>(memchr(bol_, '\n', eod_ - bol_)); + } + + bool BufferIsEmpty() { + return buf_ == eod_; + } + + bool HasCompleteLine() { + return !BufferIsEmpty() && FindLineFeed() != NULL; + } + + const int fd_; + char * const buf_; + const int buf_len_; + char *bol_; + char *eol_; + const char *eod_; // End of data in "buf_". +}; +} // namespace + +// Place the hex number read from "start" into "*hex". The pointer to +// the first non-hex character or "end" is returned. +static char *GetHex(const char *start, const char *end, uint64_t *hex) { + *hex = 0; + const char *p; + for (p = start; p < end; ++p) { + int ch = *p; + if ((ch >= '0' && ch <= '9') || + (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f')) { + *hex = (*hex << 4) | (ch < 'A' ? ch - '0' : (ch & 0xF) + 9); + } else { // Encountered the first non-hex character. + break; + } + } + SAFE_ASSERT(p <= end); + return const_cast<char *>(p); +} + +// Search for the object file (from /proc/self/maps) that contains +// the specified pc. If found, open this file and return the file handle, +// and also set start_address to the start address of where this object +// file is mapped to in memory. Otherwise, return -1. +static ATTRIBUTE_NOINLINE int +OpenObjectFileContainingPcAndGetStartAddress(uint64_t pc, + uint64_t &start_address) { + int object_fd; + + // Open /proc/self/maps. + int maps_fd; + NO_INTR(maps_fd = open("/proc/self/maps", O_RDONLY)); + FileDescriptor wrapped_maps_fd(maps_fd); + if (wrapped_maps_fd.get() < 0) { + return -1; + } + + // Iterate over maps and look for the map containing the pc. Then + // look into the symbol tables inside. + char buf[1024]; // Big enough for line of sane /proc/self/maps + LineReader reader(wrapped_maps_fd.get(), buf, sizeof(buf)); + while (true) { + const char *cursor; + const char *eol; + if (!reader.ReadLine(&cursor, &eol)) { // EOF or malformed line. + return -1; + } + + // Start parsing line in /proc/self/maps. Here is an example: + // + // 08048000-0804c000 r-xp 00000000 08:01 2142121 /bin/cat + // + // We want start address (08048000), end address (0804c000), flags + // (r-xp) and file name (/bin/cat). + + // Read start address. + cursor = GetHex(cursor, eol, &start_address); + if (cursor == eol || *cursor != '-') { + return -1; // Malformed line. + } + ++cursor; // Skip '-'. + + // Read end address. + uint64_t end_address; + cursor = GetHex(cursor, eol, &end_address); + if (cursor == eol || *cursor != ' ') { + return -1; // Malformed line. + } + ++cursor; // Skip ' '. + + // Check start and end addresses. + if (!(start_address <= pc && pc < end_address)) { + continue; // We skip this map. PC isn't in this map. + } + + // Read flags. Skip flags until we encounter a space or eol. + const char * const flags_start = cursor; + while (cursor < eol && *cursor != ' ') { + ++cursor; + } + // We expect at least four letters for flags (ex. "r-xp"). + if (cursor == eol || cursor < flags_start + 4) { + return -1; // Malformed line. + } + + // Check flags. We are only interested in "r-x" maps. + if (memcmp(flags_start, "r-x", 3) != 0) { // Not a "r-x" map. + continue; // We skip this map. + } + ++cursor; // Skip ' '. + + // Skip to file name. "cursor" now points to file offset. We need to + // skip at least three spaces for file offset, dev, and inode. + int num_spaces = 0; + while (cursor < eol) { + if (*cursor == ' ') { + ++num_spaces; + } else if (num_spaces >= 3) { + // The first non-space character after skipping three spaces + // is the beginning of the file name. + break; + } + ++cursor; + } + if (cursor == eol) { + return -1; // Malformed line. + } + + // Finally, "cursor" now points to file name of our interest. + NO_INTR(object_fd = open(cursor, O_RDONLY)); + if (object_fd < 0) { + return -1; + } + return object_fd; + } +} + +// The implementation of our symbolization routine. If it +// successfully finds the symbol containing "pc" and obtains the +// symbol name, returns true and write the symbol name to "out". +// Otherwise, returns false. If Callback function is installed via +// InstallSymbolizeCallback(), the function is also called in this function, +// and "out" is used as its output. +// To keep stack consumption low, we would like this function to not +// get inlined. +static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out, + int out_size) { + uint64_t pc0 = reinterpret_cast<uintptr_t>(pc); + uint64_t start_address = 0; + + int object_fd = OpenObjectFileContainingPcAndGetStartAddress(pc0, + start_address); + if (object_fd == -1) { + return false; + } + FileDescriptor wrapped_object_fd(object_fd); + int elf_type = FileGetElfType(wrapped_object_fd.get()); + if (elf_type == -1) { + return false; + } + if (g_symbolize_callback) { + // Run the call back if it's installed. + // Note: relocation (and much of the rest of this code) will be + // wrong for prelinked shared libraries and PIE executables. + uint64 relocation = (elf_type == ET_DYN) ? start_address : 0; + int num_bytes_written = g_symbolize_callback(wrapped_object_fd.get(), + pc, out, out_size, + relocation); + if (num_bytes_written > 0) { + out += num_bytes_written; + out_size -= num_bytes_written; + } + } + if (!GetSymbolFromObjectFile(wrapped_object_fd.get(), pc0, + out, out_size, start_address)) { + return false; + } + + // Symbolization succeeded. Now we try to demangle the symbol. + DemangleInplace(out, out_size); + return true; +} + +_END_GOOGLE_NAMESPACE_ + +#elif defined(OS_MACOSX) && defined(HAVE_DLADDR) + +#include <dlfcn.h> +#include <string.h> + +_START_GOOGLE_NAMESPACE_ + +static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out, + int out_size) { + Dl_info info; + if (dladdr(pc, &info)) { + if (strlen(info.dli_sname) < out_size) { + strcpy(out, info.dli_sname); + // Symbolization succeeded. Now we try to demangle the symbol. + DemangleInplace(out, out_size); + return true; + } + } + return false; +} + +_END_GOOGLE_NAMESPACE_ + +#else +# error BUG: HAVE_SYMBOLIZE was wrongly set +#endif + +_START_GOOGLE_NAMESPACE_ + +bool Symbolize(void *pc, char *out, int out_size) { + SAFE_ASSERT(out_size >= 0); + return SymbolizeAndDemangle(pc, out, out_size); +} + +_END_GOOGLE_NAMESPACE_ + +#else /* HAVE_SYMBOLIZE */ + +#include <assert.h> + +#include "config.h" + +_START_GOOGLE_NAMESPACE_ + +// TODO: Support other environments. +bool Symbolize(void *pc, char *out, int out_size) { + assert(0); + return false; +} + +_END_GOOGLE_NAMESPACE_ + +#endif
diff --git a/src/base/third_party/symbolize/symbolize.h b/src/base/third_party/symbolize/symbolize.h new file mode 100644 index 0000000..441e543 --- /dev/null +++ b/src/base/third_party/symbolize/symbolize.h
@@ -0,0 +1,133 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Satoru Takabayashi +// +// This library provides Symbolize() function that symbolizes program +// counters to their corresponding symbol names on linux platforms. +// This library has a minimal implementation of an ELF symbol table +// reader (i.e. it doesn't depend on libelf, etc.). +// +// The algorithm used in Symbolize() is as follows. +// +// 1. Go through a list of maps in /proc/self/maps and find the map +// containing the program counter. +// +// 2. Open the mapped file and find a regular symbol table inside. +// Iterate over symbols in the symbol table and look for the symbol +// containing the program counter. If such a symbol is found, +// obtain the symbol name, and demangle the symbol if possible. +// If the symbol isn't found in the regular symbol table (binary is +// stripped), try the same thing with a dynamic symbol table. +// +// Note that Symbolize() is originally implemented to be used in +// FailureSignalHandler() in base/google.cc. Hence it doesn't use +// malloc() and other unsafe operations. It should be both +// thread-safe and async-signal-safe. + +#ifndef BASE_SYMBOLIZE_H_ +#define BASE_SYMBOLIZE_H_ + +#include "utilities.h" +#include "config.h" +#include "glog/logging.h" + +#ifdef HAVE_SYMBOLIZE + +#if defined(__ELF__) // defined by gcc +#if defined(__OpenBSD__) +#include <sys/exec_elf.h> +#else +#include <elf.h> +#endif + +#if !defined(ANDROID) +#include <link.h> // For ElfW() macro. +#endif + +// For systems where SIZEOF_VOID_P is not defined, determine it +// based on __LP64__ (defined by gcc on 64-bit systems) +#if !defined(SIZEOF_VOID_P) +# if defined(__LP64__) +# define SIZEOF_VOID_P 8 +# else +# define SIZEOF_VOID_P 4 +# endif +#endif + +// If there is no ElfW macro, let's define it by ourself. +#ifndef ElfW +# if SIZEOF_VOID_P == 4 +# define ElfW(type) Elf32_##type +# elif SIZEOF_VOID_P == 8 +# define ElfW(type) Elf64_##type +# else +# error "Unknown sizeof(void *)" +# endif +#endif + +_START_GOOGLE_NAMESPACE_ + +// Gets the section header for the given name, if it exists. Returns true on +// success. Otherwise, returns false. +bool GetSectionHeaderByName(int fd, const char *name, size_t name_len, + ElfW(Shdr) *out); + +_END_GOOGLE_NAMESPACE_ + +#endif /* __ELF__ */ + +_START_GOOGLE_NAMESPACE_ + +// Installs a callback function, which will be called right before a symbol name +// is printed. The callback is intended to be used for showing a file name and a +// line number preceding a symbol name. +// "fd" is a file descriptor of the object file containing the program +// counter "pc". The callback function should write output to "out" +// and return the size of the output written. On error, the callback +// function should return -1. +typedef int (*SymbolizeCallback)(int fd, void *pc, char *out, size_t out_size, + uint64 relocation); +void InstallSymbolizeCallback(SymbolizeCallback callback); + +_END_GOOGLE_NAMESPACE_ + +#endif + +_START_GOOGLE_NAMESPACE_ + +// Symbolizes a program counter. On success, returns true and write the +// symbol name to "out". The symbol name is demangled if possible +// (supports symbols generated by GCC 3.x or newer). Otherwise, +// returns false. +bool Symbolize(void *pc, char *out, int out_size); + +_END_GOOGLE_NAMESPACE_ + +#endif // BASE_SYMBOLIZE_H_
diff --git a/src/base/third_party/symbolize/utilities.h b/src/base/third_party/symbolize/utilities.h new file mode 100644 index 0000000..0bed526 --- /dev/null +++ b/src/base/third_party/symbolize/utilities.h
@@ -0,0 +1,11 @@ +// Copyright (c) 2010 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 <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +typedef uint64_t uint64; +#define HAVE_SYMBOLIZE 1 +#define ATTRIBUTE_NOINLINE __attribute__ ((noinline))
diff --git a/src/base/third_party/valgrind/LICENSE b/src/base/third_party/valgrind/LICENSE new file mode 100644 index 0000000..41f677b --- /dev/null +++ b/src/base/third_party/valgrind/LICENSE
@@ -0,0 +1,39 @@ + Notice that the following BSD-style license applies to the Valgrind header + files used by Chromium (valgrind.h and memcheck.h). However, the rest of + Valgrind is licensed under the terms of the GNU General Public License, + version 2, unless otherwise indicated. + + ---------------------------------------------------------------- + + Copyright (C) 2000-2008 Julian Seward. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 3. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + + 4. The name of the author may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/base/third_party/valgrind/README.chromium b/src/base/third_party/valgrind/README.chromium new file mode 100644 index 0000000..56a1cbb --- /dev/null +++ b/src/base/third_party/valgrind/README.chromium
@@ -0,0 +1,11 @@ +Name: valgrind +URL: http://valgrind.org +License: BSD + +Header files in this directory define runtime macros that determine whether the +current process is running under Valgrind and tell Memcheck tool about custom +memory allocators. + +These header files were taken from Valgrind source code +(svn://svn.valgrind.org/valgrind/trunk@11504, dated 21 Jan 2011). The files are +covered under BSD license as described within.
diff --git a/src/base/third_party/valgrind/memcheck.h b/src/base/third_party/valgrind/memcheck.h new file mode 100644 index 0000000..f59c212 --- /dev/null +++ b/src/base/third_party/valgrind/memcheck.h
@@ -0,0 +1,279 @@ + +/* + ---------------------------------------------------------------- + + Notice that the following BSD-style license applies to this one + file (memcheck.h) only. The rest of Valgrind is licensed under the + terms of the GNU General Public License, version 2, unless + otherwise indicated. See the COPYING file in the source + distribution for details. + + ---------------------------------------------------------------- + + This file is part of MemCheck, a heavyweight Valgrind tool for + detecting memory errors. + + Copyright (C) 2000-2010 Julian Seward. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 3. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + + 4. The name of the author may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ---------------------------------------------------------------- + + Notice that the above BSD-style license applies to this one file + (memcheck.h) only. The entire rest of Valgrind is licensed under + the terms of the GNU General Public License, version 2. See the + COPYING file in the source distribution for details. + + ---------------------------------------------------------------- +*/ + + +#ifndef __MEMCHECK_H +#define __MEMCHECK_H + + +/* This file is for inclusion into client (your!) code. + + You can use these macros to manipulate and query memory permissions + inside your own programs. + + See comment near the top of valgrind.h on how to use them. +*/ + +#include "valgrind.h" + +/* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !! + This enum comprises an ABI exported by Valgrind to programs + which use client requests. DO NOT CHANGE THE ORDER OF THESE + ENTRIES, NOR DELETE ANY -- add new ones at the end. */ +typedef + enum { + VG_USERREQ__MAKE_MEM_NOACCESS = VG_USERREQ_TOOL_BASE('M','C'), + VG_USERREQ__MAKE_MEM_UNDEFINED, + VG_USERREQ__MAKE_MEM_DEFINED, + VG_USERREQ__DISCARD, + VG_USERREQ__CHECK_MEM_IS_ADDRESSABLE, + VG_USERREQ__CHECK_MEM_IS_DEFINED, + VG_USERREQ__DO_LEAK_CHECK, + VG_USERREQ__COUNT_LEAKS, + + VG_USERREQ__GET_VBITS, + VG_USERREQ__SET_VBITS, + + VG_USERREQ__CREATE_BLOCK, + + VG_USERREQ__MAKE_MEM_DEFINED_IF_ADDRESSABLE, + + /* Not next to VG_USERREQ__COUNT_LEAKS because it was added later. */ + VG_USERREQ__COUNT_LEAK_BLOCKS, + + /* This is just for memcheck's internal use - don't use it */ + _VG_USERREQ__MEMCHECK_RECORD_OVERLAP_ERROR + = VG_USERREQ_TOOL_BASE('M','C') + 256 + } Vg_MemCheckClientRequest; + + + +/* Client-code macros to manipulate the state of memory. */ + +/* Mark memory at _qzz_addr as unaddressable for _qzz_len bytes. */ +#define VALGRIND_MAKE_MEM_NOACCESS(_qzz_addr,_qzz_len) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ + VG_USERREQ__MAKE_MEM_NOACCESS, \ + (_qzz_addr), (_qzz_len), 0, 0, 0) + +/* Similarly, mark memory at _qzz_addr as addressable but undefined + for _qzz_len bytes. */ +#define VALGRIND_MAKE_MEM_UNDEFINED(_qzz_addr,_qzz_len) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ + VG_USERREQ__MAKE_MEM_UNDEFINED, \ + (_qzz_addr), (_qzz_len), 0, 0, 0) + +/* Similarly, mark memory at _qzz_addr as addressable and defined + for _qzz_len bytes. */ +#define VALGRIND_MAKE_MEM_DEFINED(_qzz_addr,_qzz_len) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ + VG_USERREQ__MAKE_MEM_DEFINED, \ + (_qzz_addr), (_qzz_len), 0, 0, 0) + +/* Similar to VALGRIND_MAKE_MEM_DEFINED except that addressability is + not altered: bytes which are addressable are marked as defined, + but those which are not addressable are left unchanged. */ +#define VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE(_qzz_addr,_qzz_len) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ + VG_USERREQ__MAKE_MEM_DEFINED_IF_ADDRESSABLE, \ + (_qzz_addr), (_qzz_len), 0, 0, 0) + +/* Create a block-description handle. The description is an ascii + string which is included in any messages pertaining to addresses + within the specified memory range. Has no other effect on the + properties of the memory range. */ +#define VALGRIND_CREATE_BLOCK(_qzz_addr,_qzz_len, _qzz_desc) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ + VG_USERREQ__CREATE_BLOCK, \ + (_qzz_addr), (_qzz_len), (_qzz_desc), \ + 0, 0) + +/* Discard a block-description-handle. Returns 1 for an + invalid handle, 0 for a valid handle. */ +#define VALGRIND_DISCARD(_qzz_blkindex) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ + VG_USERREQ__DISCARD, \ + 0, (_qzz_blkindex), 0, 0, 0) + + +/* Client-code macros to check the state of memory. */ + +/* Check that memory at _qzz_addr is addressable for _qzz_len bytes. + If suitable addressibility is not established, Valgrind prints an + error message and returns the address of the first offending byte. + Otherwise it returns zero. */ +#define VALGRIND_CHECK_MEM_IS_ADDRESSABLE(_qzz_addr,_qzz_len) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ + VG_USERREQ__CHECK_MEM_IS_ADDRESSABLE, \ + (_qzz_addr), (_qzz_len), 0, 0, 0) + +/* Check that memory at _qzz_addr is addressable and defined for + _qzz_len bytes. If suitable addressibility and definedness are not + established, Valgrind prints an error message and returns the + address of the first offending byte. Otherwise it returns zero. */ +#define VALGRIND_CHECK_MEM_IS_DEFINED(_qzz_addr,_qzz_len) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ + VG_USERREQ__CHECK_MEM_IS_DEFINED, \ + (_qzz_addr), (_qzz_len), 0, 0, 0) + +/* Use this macro to force the definedness and addressibility of an + lvalue to be checked. If suitable addressibility and definedness + are not established, Valgrind prints an error message and returns + the address of the first offending byte. Otherwise it returns + zero. */ +#define VALGRIND_CHECK_VALUE_IS_DEFINED(__lvalue) \ + VALGRIND_CHECK_MEM_IS_DEFINED( \ + (volatile unsigned char *)&(__lvalue), \ + (unsigned long)(sizeof (__lvalue))) + + +/* Do a full memory leak check (like --leak-check=full) mid-execution. */ +#define VALGRIND_DO_LEAK_CHECK \ + {unsigned long _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__DO_LEAK_CHECK, \ + 0, 0, 0, 0, 0); \ + } + +/* Do a summary memory leak check (like --leak-check=summary) mid-execution. */ +#define VALGRIND_DO_QUICK_LEAK_CHECK \ + {unsigned long _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__DO_LEAK_CHECK, \ + 1, 0, 0, 0, 0); \ + } + +/* Return number of leaked, dubious, reachable and suppressed bytes found by + all previous leak checks. They must be lvalues. */ +#define VALGRIND_COUNT_LEAKS(leaked, dubious, reachable, suppressed) \ + /* For safety on 64-bit platforms we assign the results to private + unsigned long variables, then assign these to the lvalues the user + specified, which works no matter what type 'leaked', 'dubious', etc + are. We also initialise '_qzz_leaked', etc because + VG_USERREQ__COUNT_LEAKS doesn't mark the values returned as + defined. */ \ + {unsigned long _qzz_res; \ + unsigned long _qzz_leaked = 0, _qzz_dubious = 0; \ + unsigned long _qzz_reachable = 0, _qzz_suppressed = 0; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__COUNT_LEAKS, \ + &_qzz_leaked, &_qzz_dubious, \ + &_qzz_reachable, &_qzz_suppressed, 0); \ + leaked = _qzz_leaked; \ + dubious = _qzz_dubious; \ + reachable = _qzz_reachable; \ + suppressed = _qzz_suppressed; \ + } + +/* Return number of leaked, dubious, reachable and suppressed bytes found by + all previous leak checks. They must be lvalues. */ +#define VALGRIND_COUNT_LEAK_BLOCKS(leaked, dubious, reachable, suppressed) \ + /* For safety on 64-bit platforms we assign the results to private + unsigned long variables, then assign these to the lvalues the user + specified, which works no matter what type 'leaked', 'dubious', etc + are. We also initialise '_qzz_leaked', etc because + VG_USERREQ__COUNT_LEAKS doesn't mark the values returned as + defined. */ \ + {unsigned long _qzz_res; \ + unsigned long _qzz_leaked = 0, _qzz_dubious = 0; \ + unsigned long _qzz_reachable = 0, _qzz_suppressed = 0; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__COUNT_LEAK_BLOCKS, \ + &_qzz_leaked, &_qzz_dubious, \ + &_qzz_reachable, &_qzz_suppressed, 0); \ + leaked = _qzz_leaked; \ + dubious = _qzz_dubious; \ + reachable = _qzz_reachable; \ + suppressed = _qzz_suppressed; \ + } + + +/* Get the validity data for addresses [zza..zza+zznbytes-1] and copy it + into the provided zzvbits array. Return values: + 0 if not running on valgrind + 1 success + 2 [previously indicated unaligned arrays; these are now allowed] + 3 if any parts of zzsrc/zzvbits are not addressable. + The metadata is not copied in cases 0, 2 or 3 so it should be + impossible to segfault your system by using this call. +*/ +#define VALGRIND_GET_VBITS(zza,zzvbits,zznbytes) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ + VG_USERREQ__GET_VBITS, \ + (const char*)(zza), \ + (char*)(zzvbits), \ + (zznbytes), 0, 0) + +/* Set the validity data for addresses [zza..zza+zznbytes-1], copying it + from the provided zzvbits array. Return values: + 0 if not running on valgrind + 1 success + 2 [previously indicated unaligned arrays; these are now allowed] + 3 if any parts of zza/zzvbits are not addressable. + The metadata is not copied in cases 0, 2 or 3 so it should be + impossible to segfault your system by using this call. +*/ +#define VALGRIND_SET_VBITS(zza,zzvbits,zznbytes) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ + VG_USERREQ__SET_VBITS, \ + (const char*)(zza), \ + (const char*)(zzvbits), \ + (zznbytes), 0, 0 ) + +#endif +
diff --git a/src/base/third_party/valgrind/valgrind.h b/src/base/third_party/valgrind/valgrind.h new file mode 100644 index 0000000..0bae0aa --- /dev/null +++ b/src/base/third_party/valgrind/valgrind.h
@@ -0,0 +1,4792 @@ +/* -*- c -*- + ---------------------------------------------------------------- + + Notice that the following BSD-style license applies to this one + file (valgrind.h) only. The rest of Valgrind is licensed under the + terms of the GNU General Public License, version 2, unless + otherwise indicated. See the COPYING file in the source + distribution for details. + + ---------------------------------------------------------------- + + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2000-2010 Julian Seward. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 3. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + + 4. The name of the author may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ---------------------------------------------------------------- + + Notice that the above BSD-style license applies to this one file + (valgrind.h) only. The entire rest of Valgrind is licensed under + the terms of the GNU General Public License, version 2. See the + COPYING file in the source distribution for details. + + ---------------------------------------------------------------- +*/ + + +/* This file is for inclusion into client (your!) code. + + You can use these macros to manipulate and query Valgrind's + execution inside your own programs. + + The resulting executables will still run without Valgrind, just a + little bit more slowly than they otherwise would, but otherwise + unchanged. When not running on valgrind, each client request + consumes very few (eg. 7) instructions, so the resulting performance + loss is negligible unless you plan to execute client requests + millions of times per second. Nevertheless, if that is still a + problem, you can compile with the NVALGRIND symbol defined (gcc + -DNVALGRIND) so that client requests are not even compiled in. */ + +#ifndef __VALGRIND_H +#define __VALGRIND_H + + +/* ------------------------------------------------------------------ */ +/* VERSION NUMBER OF VALGRIND */ +/* ------------------------------------------------------------------ */ + +/* Specify Valgrind's version number, so that user code can + conditionally compile based on our version number. Note that these + were introduced at version 3.6 and so do not exist in version 3.5 + or earlier. The recommended way to use them to check for "version + X.Y or later" is (eg) + +#if defined(__VALGRIND_MAJOR__) && defined(__VALGRIND_MINOR__) \ + && (__VALGRIND_MAJOR__ > 3 \ + || (__VALGRIND_MAJOR__ == 3 && __VALGRIND_MINOR__ >= 6)) +*/ +#define __VALGRIND_MAJOR__ 3 +#define __VALGRIND_MINOR__ 6 + + +#include <stdarg.h> + +/* Nb: this file might be included in a file compiled with -ansi. So + we can't use C++ style "//" comments nor the "asm" keyword (instead + use "__asm__"). */ + +/* Derive some tags indicating what the target platform is. Note + that in this file we're using the compiler's CPP symbols for + identifying architectures, which are different to the ones we use + within the rest of Valgrind. Note, __powerpc__ is active for both + 32 and 64-bit PPC, whereas __powerpc64__ is only active for the + latter (on Linux, that is). + + Misc note: how to find out what's predefined in gcc by default: + gcc -Wp,-dM somefile.c +*/ +#undef PLAT_ppc64_aix5 +#undef PLAT_ppc32_aix5 +#undef PLAT_x86_darwin +#undef PLAT_amd64_darwin +#undef PLAT_x86_win32 +#undef PLAT_x86_linux +#undef PLAT_amd64_linux +#undef PLAT_ppc32_linux +#undef PLAT_ppc64_linux +#undef PLAT_arm_linux + +#if defined(_AIX) && defined(__64BIT__) +# define PLAT_ppc64_aix5 1 +#elif defined(_AIX) && !defined(__64BIT__) +# define PLAT_ppc32_aix5 1 +#elif defined(__APPLE__) && defined(__i386__) +# define PLAT_x86_darwin 1 +#elif defined(__APPLE__) && defined(__x86_64__) +# define PLAT_amd64_darwin 1 +#elif defined(__MINGW32__) || defined(__CYGWIN32__) || defined(_WIN32) && defined(_M_IX86) +# define PLAT_x86_win32 1 +#elif defined(__linux__) && defined(__i386__) +# define PLAT_x86_linux 1 +#elif defined(__linux__) && defined(__x86_64__) +# define PLAT_amd64_linux 1 +#elif defined(__linux__) && defined(__powerpc__) && !defined(__powerpc64__) +# define PLAT_ppc32_linux 1 +#elif defined(__linux__) && defined(__powerpc__) && defined(__powerpc64__) +# define PLAT_ppc64_linux 1 +#elif defined(__linux__) && defined(__arm__) +# define PLAT_arm_linux 1 +#else +/* If we're not compiling for our target platform, don't generate + any inline asms. */ +# if !defined(NVALGRIND) +# define NVALGRIND 1 +# endif +#endif + + +/* ------------------------------------------------------------------ */ +/* ARCHITECTURE SPECIFICS for SPECIAL INSTRUCTIONS. There is nothing */ +/* in here of use to end-users -- skip to the next section. */ +/* ------------------------------------------------------------------ */ + +#if defined(NVALGRIND) + +/* Define NVALGRIND to completely remove the Valgrind magic sequence + from the compiled code (analogous to NDEBUG's effects on + assert()) */ +#define VALGRIND_DO_CLIENT_REQUEST( \ + _zzq_rlval, _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + { \ + (_zzq_rlval) = (_zzq_default); \ + } + +#else /* ! NVALGRIND */ + +/* The following defines the magic code sequences which the JITter + spots and handles magically. Don't look too closely at them as + they will rot your brain. + + The assembly code sequences for all architectures is in this one + file. This is because this file must be stand-alone, and we don't + want to have multiple files. + + For VALGRIND_DO_CLIENT_REQUEST, we must ensure that the default + value gets put in the return slot, so that everything works when + this is executed not under Valgrind. Args are passed in a memory + block, and so there's no intrinsic limit to the number that could + be passed, but it's currently five. + + The macro args are: + _zzq_rlval result lvalue + _zzq_default default value (result returned when running on real CPU) + _zzq_request request code + _zzq_arg1..5 request params + + The other two macros are used to support function wrapping, and are + a lot simpler. VALGRIND_GET_NR_CONTEXT returns the value of the + guest's NRADDR pseudo-register and whatever other information is + needed to safely run the call original from the wrapper: on + ppc64-linux, the R2 value at the divert point is also needed. This + information is abstracted into a user-visible type, OrigFn. + + VALGRIND_CALL_NOREDIR_* behaves the same as the following on the + guest, but guarantees that the branch instruction will not be + redirected: x86: call *%eax, amd64: call *%rax, ppc32/ppc64: + branch-and-link-to-r11. VALGRIND_CALL_NOREDIR is just text, not a + complete inline asm, since it needs to be combined with more magic + inline asm stuff to be useful. +*/ + +/* ------------------------- x86-{linux,darwin} ---------------- */ + +#if defined(PLAT_x86_linux) || defined(PLAT_x86_darwin) \ + || (defined(PLAT_x86_win32) && defined(__GNUC__)) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "roll $3, %%edi ; roll $13, %%edi\n\t" \ + "roll $29, %%edi ; roll $19, %%edi\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST( \ + _zzq_rlval, _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + { volatile unsigned int _zzq_args[6]; \ + volatile unsigned int _zzq_result; \ + _zzq_args[0] = (unsigned int)(_zzq_request); \ + _zzq_args[1] = (unsigned int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned int)(_zzq_arg5); \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %EDX = client_request ( %EAX ) */ \ + "xchgl %%ebx,%%ebx" \ + : "=d" (_zzq_result) \ + : "a" (&_zzq_args[0]), "0" (_zzq_default) \ + : "cc", "memory" \ + ); \ + _zzq_rlval = _zzq_result; \ + } + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + volatile unsigned int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %EAX = guest_NRADDR */ \ + "xchgl %%ecx,%%ecx" \ + : "=a" (__addr) \ + : \ + : "cc", "memory" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_CALL_NOREDIR_EAX \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* call-noredir *%EAX */ \ + "xchgl %%edx,%%edx\n\t" +#endif /* PLAT_x86_linux || PLAT_x86_darwin || (PLAT_x86_win32 && __GNUC__) */ + +/* ------------------------- x86-Win32 ------------------------- */ + +#if defined(PLAT_x86_win32) && !defined(__GNUC__) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + } + OrigFn; + +#if defined(_MSC_VER) + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + __asm rol edi, 3 __asm rol edi, 13 \ + __asm rol edi, 29 __asm rol edi, 19 + +#define VALGRIND_DO_CLIENT_REQUEST( \ + _zzq_rlval, _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + { volatile uintptr_t _zzq_args[6]; \ + volatile unsigned int _zzq_result; \ + _zzq_args[0] = (uintptr_t)(_zzq_request); \ + _zzq_args[1] = (uintptr_t)(_zzq_arg1); \ + _zzq_args[2] = (uintptr_t)(_zzq_arg2); \ + _zzq_args[3] = (uintptr_t)(_zzq_arg3); \ + _zzq_args[4] = (uintptr_t)(_zzq_arg4); \ + _zzq_args[5] = (uintptr_t)(_zzq_arg5); \ + __asm { __asm lea eax, _zzq_args __asm mov edx, _zzq_default \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* %EDX = client_request ( %EAX ) */ \ + __asm xchg ebx,ebx \ + __asm mov _zzq_result, edx \ + } \ + _zzq_rlval = _zzq_result; \ + } + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + volatile unsigned int __addr; \ + __asm { __SPECIAL_INSTRUCTION_PREAMBLE \ + /* %EAX = guest_NRADDR */ \ + __asm xchg ecx,ecx \ + __asm mov __addr, eax \ + } \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_CALL_NOREDIR_EAX ERROR + +#else +#error Unsupported compiler. +#endif + +#endif /* PLAT_x86_win32 */ + +/* ------------------------ amd64-{linux,darwin} --------------- */ + +#if defined(PLAT_amd64_linux) || defined(PLAT_amd64_darwin) + +typedef + struct { + unsigned long long int nraddr; /* where's the code? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "rolq $3, %%rdi ; rolq $13, %%rdi\n\t" \ + "rolq $61, %%rdi ; rolq $51, %%rdi\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST( \ + _zzq_rlval, _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + { volatile unsigned long long int _zzq_args[6]; \ + volatile unsigned long long int _zzq_result; \ + _zzq_args[0] = (unsigned long long int)(_zzq_request); \ + _zzq_args[1] = (unsigned long long int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned long long int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned long long int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned long long int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned long long int)(_zzq_arg5); \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %RDX = client_request ( %RAX ) */ \ + "xchgq %%rbx,%%rbx" \ + : "=d" (_zzq_result) \ + : "a" (&_zzq_args[0]), "0" (_zzq_default) \ + : "cc", "memory" \ + ); \ + _zzq_rlval = _zzq_result; \ + } + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + volatile unsigned long long int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %RAX = guest_NRADDR */ \ + "xchgq %%rcx,%%rcx" \ + : "=a" (__addr) \ + : \ + : "cc", "memory" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_CALL_NOREDIR_RAX \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* call-noredir *%RAX */ \ + "xchgq %%rdx,%%rdx\n\t" +#endif /* PLAT_amd64_linux || PLAT_amd64_darwin */ + +/* ------------------------ ppc32-linux ------------------------ */ + +#if defined(PLAT_ppc32_linux) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "rlwinm 0,0,3,0,0 ; rlwinm 0,0,13,0,0\n\t" \ + "rlwinm 0,0,29,0,0 ; rlwinm 0,0,19,0,0\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST( \ + _zzq_rlval, _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + \ + { unsigned int _zzq_args[6]; \ + unsigned int _zzq_result; \ + unsigned int* _zzq_ptr; \ + _zzq_args[0] = (unsigned int)(_zzq_request); \ + _zzq_args[1] = (unsigned int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned int)(_zzq_arg5); \ + _zzq_ptr = _zzq_args; \ + __asm__ volatile("mr 3,%1\n\t" /*default*/ \ + "mr 4,%2\n\t" /*ptr*/ \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = client_request ( %R4 ) */ \ + "or 1,1,1\n\t" \ + "mr %0,3" /*result*/ \ + : "=b" (_zzq_result) \ + : "b" (_zzq_default), "b" (_zzq_ptr) \ + : "cc", "memory", "r3", "r4"); \ + _zzq_rlval = _zzq_result; \ + } + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + unsigned int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = guest_NRADDR */ \ + "or 2,2,2\n\t" \ + "mr %0,3" \ + : "=b" (__addr) \ + : \ + : "cc", "memory", "r3" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* branch-and-link-to-noredir *%R11 */ \ + "or 3,3,3\n\t" +#endif /* PLAT_ppc32_linux */ + +/* ------------------------ ppc64-linux ------------------------ */ + +#if defined(PLAT_ppc64_linux) + +typedef + struct { + unsigned long long int nraddr; /* where's the code? */ + unsigned long long int r2; /* what tocptr do we need? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "rotldi 0,0,3 ; rotldi 0,0,13\n\t" \ + "rotldi 0,0,61 ; rotldi 0,0,51\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST( \ + _zzq_rlval, _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + \ + { unsigned long long int _zzq_args[6]; \ + register unsigned long long int _zzq_result __asm__("r3"); \ + register unsigned long long int* _zzq_ptr __asm__("r4"); \ + _zzq_args[0] = (unsigned long long int)(_zzq_request); \ + _zzq_args[1] = (unsigned long long int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned long long int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned long long int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned long long int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned long long int)(_zzq_arg5); \ + _zzq_ptr = _zzq_args; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = client_request ( %R4 ) */ \ + "or 1,1,1" \ + : "=r" (_zzq_result) \ + : "0" (_zzq_default), "r" (_zzq_ptr) \ + : "cc", "memory"); \ + _zzq_rlval = _zzq_result; \ + } + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + register unsigned long long int __addr __asm__("r3"); \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = guest_NRADDR */ \ + "or 2,2,2" \ + : "=r" (__addr) \ + : \ + : "cc", "memory" \ + ); \ + _zzq_orig->nraddr = __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = guest_NRADDR_GPR2 */ \ + "or 4,4,4" \ + : "=r" (__addr) \ + : \ + : "cc", "memory" \ + ); \ + _zzq_orig->r2 = __addr; \ + } + +#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* branch-and-link-to-noredir *%R11 */ \ + "or 3,3,3\n\t" + +#endif /* PLAT_ppc64_linux */ + +/* ------------------------- arm-linux ------------------------- */ + +#if defined(PLAT_arm_linux) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "mov r12, r12, ror #3 ; mov r12, r12, ror #13 \n\t" \ + "mov r12, r12, ror #29 ; mov r12, r12, ror #19 \n\t" + +#define VALGRIND_DO_CLIENT_REQUEST( \ + _zzq_rlval, _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + \ + { volatile unsigned int _zzq_args[6]; \ + volatile unsigned int _zzq_result; \ + _zzq_args[0] = (unsigned int)(_zzq_request); \ + _zzq_args[1] = (unsigned int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned int)(_zzq_arg5); \ + __asm__ volatile("mov r3, %1\n\t" /*default*/ \ + "mov r4, %2\n\t" /*ptr*/ \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* R3 = client_request ( R4 ) */ \ + "orr r10, r10, r10\n\t" \ + "mov %0, r3" /*result*/ \ + : "=r" (_zzq_result) \ + : "r" (_zzq_default), "r" (&_zzq_args[0]) \ + : "cc","memory", "r3", "r4"); \ + _zzq_rlval = _zzq_result; \ + } + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + unsigned int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* R3 = guest_NRADDR */ \ + "orr r11, r11, r11\n\t" \ + "mov %0, r3" \ + : "=r" (__addr) \ + : \ + : "cc", "memory", "r3" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* branch-and-link-to-noredir *%R4 */ \ + "orr r12, r12, r12\n\t" + +#endif /* PLAT_arm_linux */ + +/* ------------------------ ppc32-aix5 ------------------------- */ + +#if defined(PLAT_ppc32_aix5) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + unsigned int r2; /* what tocptr do we need? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "rlwinm 0,0,3,0,0 ; rlwinm 0,0,13,0,0\n\t" \ + "rlwinm 0,0,29,0,0 ; rlwinm 0,0,19,0,0\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST( \ + _zzq_rlval, _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + \ + { unsigned int _zzq_args[7]; \ + register unsigned int _zzq_result; \ + register unsigned int* _zzq_ptr; \ + _zzq_args[0] = (unsigned int)(_zzq_request); \ + _zzq_args[1] = (unsigned int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned int)(_zzq_arg5); \ + _zzq_args[6] = (unsigned int)(_zzq_default); \ + _zzq_ptr = _zzq_args; \ + __asm__ volatile("mr 4,%1\n\t" \ + "lwz 3, 24(4)\n\t" \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = client_request ( %R4 ) */ \ + "or 1,1,1\n\t" \ + "mr %0,3" \ + : "=b" (_zzq_result) \ + : "b" (_zzq_ptr) \ + : "r3", "r4", "cc", "memory"); \ + _zzq_rlval = _zzq_result; \ + } + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + register unsigned int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = guest_NRADDR */ \ + "or 2,2,2\n\t" \ + "mr %0,3" \ + : "=b" (__addr) \ + : \ + : "r3", "cc", "memory" \ + ); \ + _zzq_orig->nraddr = __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = guest_NRADDR_GPR2 */ \ + "or 4,4,4\n\t" \ + "mr %0,3" \ + : "=b" (__addr) \ + : \ + : "r3", "cc", "memory" \ + ); \ + _zzq_orig->r2 = __addr; \ + } + +#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* branch-and-link-to-noredir *%R11 */ \ + "or 3,3,3\n\t" + +#endif /* PLAT_ppc32_aix5 */ + +/* ------------------------ ppc64-aix5 ------------------------- */ + +#if defined(PLAT_ppc64_aix5) + +typedef + struct { + unsigned long long int nraddr; /* where's the code? */ + unsigned long long int r2; /* what tocptr do we need? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "rotldi 0,0,3 ; rotldi 0,0,13\n\t" \ + "rotldi 0,0,61 ; rotldi 0,0,51\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST( \ + _zzq_rlval, _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + \ + { unsigned long long int _zzq_args[7]; \ + register unsigned long long int _zzq_result; \ + register unsigned long long int* _zzq_ptr; \ + _zzq_args[0] = (unsigned int long long)(_zzq_request); \ + _zzq_args[1] = (unsigned int long long)(_zzq_arg1); \ + _zzq_args[2] = (unsigned int long long)(_zzq_arg2); \ + _zzq_args[3] = (unsigned int long long)(_zzq_arg3); \ + _zzq_args[4] = (unsigned int long long)(_zzq_arg4); \ + _zzq_args[5] = (unsigned int long long)(_zzq_arg5); \ + _zzq_args[6] = (unsigned int long long)(_zzq_default); \ + _zzq_ptr = _zzq_args; \ + __asm__ volatile("mr 4,%1\n\t" \ + "ld 3, 48(4)\n\t" \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = client_request ( %R4 ) */ \ + "or 1,1,1\n\t" \ + "mr %0,3" \ + : "=b" (_zzq_result) \ + : "b" (_zzq_ptr) \ + : "r3", "r4", "cc", "memory"); \ + _zzq_rlval = _zzq_result; \ + } + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + register unsigned long long int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = guest_NRADDR */ \ + "or 2,2,2\n\t" \ + "mr %0,3" \ + : "=b" (__addr) \ + : \ + : "r3", "cc", "memory" \ + ); \ + _zzq_orig->nraddr = __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = guest_NRADDR_GPR2 */ \ + "or 4,4,4\n\t" \ + "mr %0,3" \ + : "=b" (__addr) \ + : \ + : "r3", "cc", "memory" \ + ); \ + _zzq_orig->r2 = __addr; \ + } + +#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* branch-and-link-to-noredir *%R11 */ \ + "or 3,3,3\n\t" + +#endif /* PLAT_ppc64_aix5 */ + +/* Insert assembly code for other platforms here... */ + +#endif /* NVALGRIND */ + + +/* ------------------------------------------------------------------ */ +/* PLATFORM SPECIFICS for FUNCTION WRAPPING. This is all very */ +/* ugly. It's the least-worst tradeoff I can think of. */ +/* ------------------------------------------------------------------ */ + +/* This section defines magic (a.k.a appalling-hack) macros for doing + guaranteed-no-redirection macros, so as to get from function + wrappers to the functions they are wrapping. The whole point is to + construct standard call sequences, but to do the call itself with a + special no-redirect call pseudo-instruction that the JIT + understands and handles specially. This section is long and + repetitious, and I can't see a way to make it shorter. + + The naming scheme is as follows: + + CALL_FN_{W,v}_{v,W,WW,WWW,WWWW,5W,6W,7W,etc} + + 'W' stands for "word" and 'v' for "void". Hence there are + different macros for calling arity 0, 1, 2, 3, 4, etc, functions, + and for each, the possibility of returning a word-typed result, or + no result. +*/ + +/* Use these to write the name of your wrapper. NOTE: duplicates + VG_WRAP_FUNCTION_Z{U,Z} in pub_tool_redir.h. */ + +/* Use an extra level of macroisation so as to ensure the soname/fnname + args are fully macro-expanded before pasting them together. */ +#define VG_CONCAT4(_aa,_bb,_cc,_dd) _aa##_bb##_cc##_dd + +#define I_WRAP_SONAME_FNNAME_ZU(soname,fnname) \ + VG_CONCAT4(_vgwZU_,soname,_,fnname) + +#define I_WRAP_SONAME_FNNAME_ZZ(soname,fnname) \ + VG_CONCAT4(_vgwZZ_,soname,_,fnname) + +/* Use this macro from within a wrapper function to collect the + context (address and possibly other info) of the original function. + Once you have that you can then use it in one of the CALL_FN_ + macros. The type of the argument _lval is OrigFn. */ +#define VALGRIND_GET_ORIG_FN(_lval) VALGRIND_GET_NR_CONTEXT(_lval) + +/* Derivatives of the main macros below, for calling functions + returning void. */ + +#define CALL_FN_v_v(fnptr) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_v(_junk,fnptr); } while (0) + +#define CALL_FN_v_W(fnptr, arg1) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_W(_junk,fnptr,arg1); } while (0) + +#define CALL_FN_v_WW(fnptr, arg1,arg2) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_WW(_junk,fnptr,arg1,arg2); } while (0) + +#define CALL_FN_v_WWW(fnptr, arg1,arg2,arg3) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_WWW(_junk,fnptr,arg1,arg2,arg3); } while (0) + +#define CALL_FN_v_WWWW(fnptr, arg1,arg2,arg3,arg4) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_WWWW(_junk,fnptr,arg1,arg2,arg3,arg4); } while (0) + +#define CALL_FN_v_5W(fnptr, arg1,arg2,arg3,arg4,arg5) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_5W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5); } while (0) + +#define CALL_FN_v_6W(fnptr, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_6W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5,arg6); } while (0) + +#define CALL_FN_v_7W(fnptr, arg1,arg2,arg3,arg4,arg5,arg6,arg7) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_7W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5,arg6,arg7); } while (0) + +/* ------------------------- x86-{linux,darwin} ---------------- */ + +#if defined(PLAT_x86_linux) || defined(PLAT_x86_darwin) + +/* These regs are trashed by the hidden call. No need to mention eax + as gcc can already see that, plus causes gcc to bomb. */ +#define __CALLER_SAVED_REGS /*"eax"*/ "ecx", "edx" + +/* These CALL_FN_ macros assume that on x86-linux, sizeof(unsigned + long) == 4. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + __asm__ volatile( \ + "subl $12, %%esp\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + "addl $16, %%esp\n" \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + __asm__ volatile( \ + "subl $8, %%esp\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + "addl $16, %%esp\n" \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + __asm__ volatile( \ + "subl $4, %%esp\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + "addl $16, %%esp\n" \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + __asm__ volatile( \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + "addl $16, %%esp\n" \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + __asm__ volatile( \ + "subl $12, %%esp\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + "addl $32, %%esp\n" \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + __asm__ volatile( \ + "subl $8, %%esp\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + "addl $32, %%esp\n" \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + __asm__ volatile( \ + "subl $4, %%esp\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + "addl $32, %%esp\n" \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + __asm__ volatile( \ + "pushl 32(%%eax)\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + "addl $32, %%esp\n" \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + __asm__ volatile( \ + "subl $12, %%esp\n\t" \ + "pushl 36(%%eax)\n\t" \ + "pushl 32(%%eax)\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + "addl $48, %%esp\n" \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + __asm__ volatile( \ + "subl $8, %%esp\n\t" \ + "pushl 40(%%eax)\n\t" \ + "pushl 36(%%eax)\n\t" \ + "pushl 32(%%eax)\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + "addl $48, %%esp\n" \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + __asm__ volatile( \ + "subl $4, %%esp\n\t" \ + "pushl 44(%%eax)\n\t" \ + "pushl 40(%%eax)\n\t" \ + "pushl 36(%%eax)\n\t" \ + "pushl 32(%%eax)\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + "addl $48, %%esp\n" \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + _argvec[12] = (unsigned long)(arg12); \ + __asm__ volatile( \ + "pushl 48(%%eax)\n\t" \ + "pushl 44(%%eax)\n\t" \ + "pushl 40(%%eax)\n\t" \ + "pushl 36(%%eax)\n\t" \ + "pushl 32(%%eax)\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + "addl $48, %%esp\n" \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_x86_linux || PLAT_x86_darwin */ + +/* ------------------------ amd64-{linux,darwin} --------------- */ + +#if defined(PLAT_amd64_linux) || defined(PLAT_amd64_darwin) + +/* ARGREGS: rdi rsi rdx rcx r8 r9 (the rest on stack in R-to-L order) */ + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS /*"rax",*/ "rcx", "rdx", "rsi", \ + "rdi", "r8", "r9", "r10", "r11" + +/* This is all pretty complex. It's so as to make stack unwinding + work reliably. See bug 243270. The basic problem is the sub and + add of 128 of %rsp in all of the following macros. If gcc believes + the CFA is in %rsp, then unwinding may fail, because what's at the + CFA is not what gcc "expected" when it constructs the CFIs for the + places where the macros are instantiated. + + But we can't just add a CFI annotation to increase the CFA offset + by 128, to match the sub of 128 from %rsp, because we don't know + whether gcc has chosen %rsp as the CFA at that point, or whether it + has chosen some other register (eg, %rbp). In the latter case, + adding a CFI annotation to change the CFA offset is simply wrong. + + So the solution is to get hold of the CFA using + __builtin_dwarf_cfa(), put it in a known register, and add a + CFI annotation to say what the register is. We choose %rbp for + this (perhaps perversely), because: + + (1) %rbp is already subject to unwinding. If a new register was + chosen then the unwinder would have to unwind it in all stack + traces, which is expensive, and + + (2) %rbp is already subject to precise exception updates in the + JIT. If a new register was chosen, we'd have to have precise + exceptions for it too, which reduces performance of the + generated code. + + However .. one extra complication. We can't just whack the result + of __builtin_dwarf_cfa() into %rbp and then add %rbp to the + list of trashed registers at the end of the inline assembly + fragments; gcc won't allow %rbp to appear in that list. Hence + instead we need to stash %rbp in %r15 for the duration of the asm, + and say that %r15 is trashed instead. gcc seems happy to go with + that. + + Oh .. and this all needs to be conditionalised so that it is + unchanged from before this commit, when compiled with older gccs + that don't support __builtin_dwarf_cfa. Furthermore, since + this header file is freestanding, it has to be independent of + config.h, and so the following conditionalisation cannot depend on + configure time checks. + + Although it's not clear from + 'defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM)', + this expression excludes Darwin. + .cfi directives in Darwin assembly appear to be completely + different and I haven't investigated how they work. + + For even more entertainment value, note we have to use the + completely undocumented __builtin_dwarf_cfa(), which appears to + really compute the CFA, whereas __builtin_frame_address(0) claims + to but actually doesn't. See + https://bugs.kde.org/show_bug.cgi?id=243270#c47 +*/ +#if defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM) +# define __FRAME_POINTER \ + ,"r"(__builtin_dwarf_cfa()) +# define VALGRIND_CFI_PROLOGUE \ + "movq %%rbp, %%r15\n\t" \ + "movq %2, %%rbp\n\t" \ + ".cfi_remember_state\n\t" \ + ".cfi_def_cfa rbp, 0\n\t" +# define VALGRIND_CFI_EPILOGUE \ + "movq %%r15, %%rbp\n\t" \ + ".cfi_restore_state\n\t" +#else +# define __FRAME_POINTER +# define VALGRIND_CFI_PROLOGUE +# define VALGRIND_CFI_EPILOGUE +#endif + + +/* These CALL_FN_ macros assume that on amd64-linux, sizeof(unsigned + long) == 8. */ + +/* NB 9 Sept 07. There is a nasty kludge here in all these CALL_FN_ + macros. In order not to trash the stack redzone, we need to drop + %rsp by 128 before the hidden call, and restore afterwards. The + nastyness is that it is only by luck that the stack still appears + to be unwindable during the hidden call - since then the behaviour + of any routine using this macro does not match what the CFI data + says. Sigh. + + Why is this important? Imagine that a wrapper has a stack + allocated local, and passes to the hidden call, a pointer to it. + Because gcc does not know about the hidden call, it may allocate + that local in the redzone. Unfortunately the hidden call may then + trash it before it comes to use it. So we must step clear of the + redzone, for the duration of the hidden call, to make it safe. + + Probably the same problem afflicts the other redzone-style ABIs too + (ppc64-linux, ppc32-aix5, ppc64-aix5); but for those, the stack is + self describing (none of this CFI nonsense) so at least messing + with the stack pointer doesn't give a danger of non-unwindable + stack. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "subq $128,%%rsp\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + "addq $128,%%rsp\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "subq $128,%%rsp\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + "addq $128,%%rsp\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "subq $128,%%rsp\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + "addq $128,%%rsp\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "subq $128,%%rsp\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + "addq $128,%%rsp\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "subq $128,%%rsp\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + "addq $128,%%rsp\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "subq $128,%%rsp\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + "addq $128,%%rsp\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "subq $128,%%rsp\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + "addq $128,%%rsp\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "subq $136,%%rsp\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + "addq $8, %%rsp\n" \ + "addq $136,%%rsp\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "subq $128,%%rsp\n\t" \ + "pushq 64(%%rax)\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + "addq $16, %%rsp\n" \ + "addq $128,%%rsp\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "subq $136,%%rsp\n\t" \ + "pushq 72(%%rax)\n\t" \ + "pushq 64(%%rax)\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + "addq $24, %%rsp\n" \ + "addq $136,%%rsp\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "subq $128,%%rsp\n\t" \ + "pushq 80(%%rax)\n\t" \ + "pushq 72(%%rax)\n\t" \ + "pushq 64(%%rax)\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + "addq $32, %%rsp\n" \ + "addq $128,%%rsp\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "subq $136,%%rsp\n\t" \ + "pushq 88(%%rax)\n\t" \ + "pushq 80(%%rax)\n\t" \ + "pushq 72(%%rax)\n\t" \ + "pushq 64(%%rax)\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + "addq $40, %%rsp\n" \ + "addq $136,%%rsp\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + _argvec[12] = (unsigned long)(arg12); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "subq $128,%%rsp\n\t" \ + "pushq 96(%%rax)\n\t" \ + "pushq 88(%%rax)\n\t" \ + "pushq 80(%%rax)\n\t" \ + "pushq 72(%%rax)\n\t" \ + "pushq 64(%%rax)\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + "addq $48, %%rsp\n" \ + "addq $128,%%rsp\n\t" \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_amd64_linux || PLAT_amd64_darwin */ + +/* ------------------------ ppc32-linux ------------------------ */ + +#if defined(PLAT_ppc32_linux) + +/* This is useful for finding out about the on-stack stuff: + + extern int f9 ( int,int,int,int,int,int,int,int,int ); + extern int f10 ( int,int,int,int,int,int,int,int,int,int ); + extern int f11 ( int,int,int,int,int,int,int,int,int,int,int ); + extern int f12 ( int,int,int,int,int,int,int,int,int,int,int,int ); + + int g9 ( void ) { + return f9(11,22,33,44,55,66,77,88,99); + } + int g10 ( void ) { + return f10(11,22,33,44,55,66,77,88,99,110); + } + int g11 ( void ) { + return f11(11,22,33,44,55,66,77,88,99,110,121); + } + int g12 ( void ) { + return f12(11,22,33,44,55,66,77,88,99,110,121,132); + } +*/ + +/* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */ + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS \ + "lr", "ctr", "xer", \ + "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \ + "r0", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \ + "r11", "r12", "r13" + +/* These CALL_FN_ macros assume that on ppc32-linux, + sizeof(unsigned long) == 4. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 10,32(11)\n\t" /* arg8->r10 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "addi 1,1,-16\n\t" \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,8(1)\n\t" \ + /* args1-8 */ \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 10,32(11)\n\t" /* arg8->r10 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "addi 1,1,16\n\t" \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + _argvec[10] = (unsigned long)arg10; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "addi 1,1,-16\n\t" \ + /* arg10 */ \ + "lwz 3,40(11)\n\t" \ + "stw 3,12(1)\n\t" \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,8(1)\n\t" \ + /* args1-8 */ \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 10,32(11)\n\t" /* arg8->r10 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "addi 1,1,16\n\t" \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + _argvec[10] = (unsigned long)arg10; \ + _argvec[11] = (unsigned long)arg11; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "addi 1,1,-32\n\t" \ + /* arg11 */ \ + "lwz 3,44(11)\n\t" \ + "stw 3,16(1)\n\t" \ + /* arg10 */ \ + "lwz 3,40(11)\n\t" \ + "stw 3,12(1)\n\t" \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,8(1)\n\t" \ + /* args1-8 */ \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 10,32(11)\n\t" /* arg8->r10 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "addi 1,1,32\n\t" \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + _argvec[10] = (unsigned long)arg10; \ + _argvec[11] = (unsigned long)arg11; \ + _argvec[12] = (unsigned long)arg12; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "addi 1,1,-32\n\t" \ + /* arg12 */ \ + "lwz 3,48(11)\n\t" \ + "stw 3,20(1)\n\t" \ + /* arg11 */ \ + "lwz 3,44(11)\n\t" \ + "stw 3,16(1)\n\t" \ + /* arg10 */ \ + "lwz 3,40(11)\n\t" \ + "stw 3,12(1)\n\t" \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,8(1)\n\t" \ + /* args1-8 */ \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 10,32(11)\n\t" /* arg8->r10 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "addi 1,1,32\n\t" \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_ppc32_linux */ + +/* ------------------------ ppc64-linux ------------------------ */ + +#if defined(PLAT_ppc64_linux) + +/* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */ + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS \ + "lr", "ctr", "xer", \ + "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \ + "r0", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \ + "r11", "r12", "r13" + +/* These CALL_FN_ macros assume that on ppc64-linux, sizeof(unsigned + long) == 8. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+0]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)" /* restore tocptr */ \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+1]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)" /* restore tocptr */ \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+2]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)" /* restore tocptr */ \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+3]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)" /* restore tocptr */ \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+4]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)" /* restore tocptr */ \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+5]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)" /* restore tocptr */ \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+6]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)" /* restore tocptr */ \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+7]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)" /* restore tocptr */ \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+8]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)" /* restore tocptr */ \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+9]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-128\n\t" /* expand stack frame */ \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + "addi 1,1,128" /* restore frame */ \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+10]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-128\n\t" /* expand stack frame */ \ + /* arg10 */ \ + "ld 3,80(11)\n\t" \ + "std 3,120(1)\n\t" \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + "addi 1,1,128" /* restore frame */ \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+11]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + _argvec[2+11] = (unsigned long)arg11; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-144\n\t" /* expand stack frame */ \ + /* arg11 */ \ + "ld 3,88(11)\n\t" \ + "std 3,128(1)\n\t" \ + /* arg10 */ \ + "ld 3,80(11)\n\t" \ + "std 3,120(1)\n\t" \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + "addi 1,1,144" /* restore frame */ \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+12]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + _argvec[2+11] = (unsigned long)arg11; \ + _argvec[2+12] = (unsigned long)arg12; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-144\n\t" /* expand stack frame */ \ + /* arg12 */ \ + "ld 3,96(11)\n\t" \ + "std 3,136(1)\n\t" \ + /* arg11 */ \ + "ld 3,88(11)\n\t" \ + "std 3,128(1)\n\t" \ + /* arg10 */ \ + "ld 3,80(11)\n\t" \ + "std 3,120(1)\n\t" \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + "addi 1,1,144" /* restore frame */ \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_ppc64_linux */ + +/* ------------------------- arm-linux ------------------------- */ + +#if defined(PLAT_arm_linux) + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS "r0", "r1", "r2", "r3","r4","r14" + +/* These CALL_FN_ macros assume that on arm-linux, sizeof(unsigned + long) == 4. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + "mov %0, r0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + __asm__ volatile( \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + "mov %0, r0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + __asm__ volatile( \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + "mov %0, r0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + __asm__ volatile( \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + "mov %0, r0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + __asm__ volatile( \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + __asm__ volatile( \ + "ldr r0, [%1, #20] \n\t" \ + "push {r0} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + "add sp, sp, #4 \n\t" \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + __asm__ volatile( \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "push {r0, r1} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + "add sp, sp, #8 \n\t" \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + __asm__ volatile( \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "push {r0, r1, r2} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + "add sp, sp, #12 \n\t" \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + __asm__ volatile( \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "ldr r3, [%1, #32] \n\t" \ + "push {r0, r1, r2, r3} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + "add sp, sp, #16 \n\t" \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + __asm__ volatile( \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "ldr r3, [%1, #32] \n\t" \ + "ldr r4, [%1, #36] \n\t" \ + "push {r0, r1, r2, r3, r4} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + "add sp, sp, #20 \n\t" \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + __asm__ volatile( \ + "ldr r0, [%1, #40] \n\t" \ + "push {r0} \n\t" \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "ldr r3, [%1, #32] \n\t" \ + "ldr r4, [%1, #36] \n\t" \ + "push {r0, r1, r2, r3, r4} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + "add sp, sp, #24 \n\t" \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + __asm__ volatile( \ + "ldr r0, [%1, #40] \n\t" \ + "ldr r1, [%1, #44] \n\t" \ + "push {r0, r1} \n\t" \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "ldr r3, [%1, #32] \n\t" \ + "ldr r4, [%1, #36] \n\t" \ + "push {r0, r1, r2, r3, r4} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + "add sp, sp, #28 \n\t" \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory",__CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + _argvec[12] = (unsigned long)(arg12); \ + __asm__ volatile( \ + "ldr r0, [%1, #40] \n\t" \ + "ldr r1, [%1, #44] \n\t" \ + "ldr r2, [%1, #48] \n\t" \ + "push {r0, r1, r2} \n\t" \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "ldr r3, [%1, #32] \n\t" \ + "ldr r4, [%1, #36] \n\t" \ + "push {r0, r1, r2, r3, r4} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + "add sp, sp, #32 \n\t" \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_arm_linux */ + +/* ------------------------ ppc32-aix5 ------------------------- */ + +#if defined(PLAT_ppc32_aix5) + +/* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */ + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS \ + "lr", "ctr", "xer", \ + "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \ + "r0", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \ + "r11", "r12", "r13" + +/* Expand the stack frame, copying enough info that unwinding + still works. Trashes r3. */ + +#define VG_EXPAND_FRAME_BY_trashes_r3(_n_fr) \ + "addi 1,1,-" #_n_fr "\n\t" \ + "lwz 3," #_n_fr "(1)\n\t" \ + "stw 3,0(1)\n\t" + +#define VG_CONTRACT_FRAME_BY(_n_fr) \ + "addi 1,1," #_n_fr "\n\t" + +/* These CALL_FN_ macros assume that on ppc32-aix5, sizeof(unsigned + long) == 4. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+0]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "stw 2,-8(11)\n\t" /* save tocptr */ \ + "lwz 2,-4(11)\n\t" /* use nraddr's tocptr */ \ + "lwz 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "lwz 2,-8(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+1]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "stw 2,-8(11)\n\t" /* save tocptr */ \ + "lwz 2,-4(11)\n\t" /* use nraddr's tocptr */ \ + "lwz 3, 4(11)\n\t" /* arg1->r3 */ \ + "lwz 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "lwz 2,-8(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+2]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "stw 2,-8(11)\n\t" /* save tocptr */ \ + "lwz 2,-4(11)\n\t" /* use nraddr's tocptr */ \ + "lwz 3, 4(11)\n\t" /* arg1->r3 */ \ + "lwz 4, 8(11)\n\t" /* arg2->r4 */ \ + "lwz 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "lwz 2,-8(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+3]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "stw 2,-8(11)\n\t" /* save tocptr */ \ + "lwz 2,-4(11)\n\t" /* use nraddr's tocptr */ \ + "lwz 3, 4(11)\n\t" /* arg1->r3 */ \ + "lwz 4, 8(11)\n\t" /* arg2->r4 */ \ + "lwz 5, 12(11)\n\t" /* arg3->r5 */ \ + "lwz 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "lwz 2,-8(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+4]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "stw 2,-8(11)\n\t" /* save tocptr */ \ + "lwz 2,-4(11)\n\t" /* use nraddr's tocptr */ \ + "lwz 3, 4(11)\n\t" /* arg1->r3 */ \ + "lwz 4, 8(11)\n\t" /* arg2->r4 */ \ + "lwz 5, 12(11)\n\t" /* arg3->r5 */ \ + "lwz 6, 16(11)\n\t" /* arg4->r6 */ \ + "lwz 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "lwz 2,-8(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+5]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "stw 2,-8(11)\n\t" /* save tocptr */ \ + "lwz 2,-4(11)\n\t" /* use nraddr's tocptr */ \ + "lwz 3, 4(11)\n\t" /* arg1->r3 */ \ + "lwz 4, 8(11)\n\t" /* arg2->r4 */ \ + "lwz 5, 12(11)\n\t" /* arg3->r5 */ \ + "lwz 6, 16(11)\n\t" /* arg4->r6 */ \ + "lwz 7, 20(11)\n\t" /* arg5->r7 */ \ + "lwz 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "lwz 2,-8(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+6]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "stw 2,-8(11)\n\t" /* save tocptr */ \ + "lwz 2,-4(11)\n\t" /* use nraddr's tocptr */ \ + "lwz 3, 4(11)\n\t" /* arg1->r3 */ \ + "lwz 4, 8(11)\n\t" /* arg2->r4 */ \ + "lwz 5, 12(11)\n\t" /* arg3->r5 */ \ + "lwz 6, 16(11)\n\t" /* arg4->r6 */ \ + "lwz 7, 20(11)\n\t" /* arg5->r7 */ \ + "lwz 8, 24(11)\n\t" /* arg6->r8 */ \ + "lwz 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "lwz 2,-8(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+7]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "stw 2,-8(11)\n\t" /* save tocptr */ \ + "lwz 2,-4(11)\n\t" /* use nraddr's tocptr */ \ + "lwz 3, 4(11)\n\t" /* arg1->r3 */ \ + "lwz 4, 8(11)\n\t" /* arg2->r4 */ \ + "lwz 5, 12(11)\n\t" /* arg3->r5 */ \ + "lwz 6, 16(11)\n\t" /* arg4->r6 */ \ + "lwz 7, 20(11)\n\t" /* arg5->r7 */ \ + "lwz 8, 24(11)\n\t" /* arg6->r8 */ \ + "lwz 9, 28(11)\n\t" /* arg7->r9 */ \ + "lwz 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "lwz 2,-8(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+8]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "stw 2,-8(11)\n\t" /* save tocptr */ \ + "lwz 2,-4(11)\n\t" /* use nraddr's tocptr */ \ + "lwz 3, 4(11)\n\t" /* arg1->r3 */ \ + "lwz 4, 8(11)\n\t" /* arg2->r4 */ \ + "lwz 5, 12(11)\n\t" /* arg3->r5 */ \ + "lwz 6, 16(11)\n\t" /* arg4->r6 */ \ + "lwz 7, 20(11)\n\t" /* arg5->r7 */ \ + "lwz 8, 24(11)\n\t" /* arg6->r8 */ \ + "lwz 9, 28(11)\n\t" /* arg7->r9 */ \ + "lwz 10, 32(11)\n\t" /* arg8->r10 */ \ + "lwz 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "lwz 2,-8(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+9]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "stw 2,-8(11)\n\t" /* save tocptr */ \ + "lwz 2,-4(11)\n\t" /* use nraddr's tocptr */ \ + VG_EXPAND_FRAME_BY_trashes_r3(64) \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,56(1)\n\t" \ + /* args1-8 */ \ + "lwz 3, 4(11)\n\t" /* arg1->r3 */ \ + "lwz 4, 8(11)\n\t" /* arg2->r4 */ \ + "lwz 5, 12(11)\n\t" /* arg3->r5 */ \ + "lwz 6, 16(11)\n\t" /* arg4->r6 */ \ + "lwz 7, 20(11)\n\t" /* arg5->r7 */ \ + "lwz 8, 24(11)\n\t" /* arg6->r8 */ \ + "lwz 9, 28(11)\n\t" /* arg7->r9 */ \ + "lwz 10, 32(11)\n\t" /* arg8->r10 */ \ + "lwz 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "lwz 2,-8(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(64) \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+10]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "stw 2,-8(11)\n\t" /* save tocptr */ \ + "lwz 2,-4(11)\n\t" /* use nraddr's tocptr */ \ + VG_EXPAND_FRAME_BY_trashes_r3(64) \ + /* arg10 */ \ + "lwz 3,40(11)\n\t" \ + "stw 3,60(1)\n\t" \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,56(1)\n\t" \ + /* args1-8 */ \ + "lwz 3, 4(11)\n\t" /* arg1->r3 */ \ + "lwz 4, 8(11)\n\t" /* arg2->r4 */ \ + "lwz 5, 12(11)\n\t" /* arg3->r5 */ \ + "lwz 6, 16(11)\n\t" /* arg4->r6 */ \ + "lwz 7, 20(11)\n\t" /* arg5->r7 */ \ + "lwz 8, 24(11)\n\t" /* arg6->r8 */ \ + "lwz 9, 28(11)\n\t" /* arg7->r9 */ \ + "lwz 10, 32(11)\n\t" /* arg8->r10 */ \ + "lwz 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "lwz 2,-8(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(64) \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+11]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + _argvec[2+11] = (unsigned long)arg11; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "stw 2,-8(11)\n\t" /* save tocptr */ \ + "lwz 2,-4(11)\n\t" /* use nraddr's tocptr */ \ + VG_EXPAND_FRAME_BY_trashes_r3(72) \ + /* arg11 */ \ + "lwz 3,44(11)\n\t" \ + "stw 3,64(1)\n\t" \ + /* arg10 */ \ + "lwz 3,40(11)\n\t" \ + "stw 3,60(1)\n\t" \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,56(1)\n\t" \ + /* args1-8 */ \ + "lwz 3, 4(11)\n\t" /* arg1->r3 */ \ + "lwz 4, 8(11)\n\t" /* arg2->r4 */ \ + "lwz 5, 12(11)\n\t" /* arg3->r5 */ \ + "lwz 6, 16(11)\n\t" /* arg4->r6 */ \ + "lwz 7, 20(11)\n\t" /* arg5->r7 */ \ + "lwz 8, 24(11)\n\t" /* arg6->r8 */ \ + "lwz 9, 28(11)\n\t" /* arg7->r9 */ \ + "lwz 10, 32(11)\n\t" /* arg8->r10 */ \ + "lwz 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "lwz 2,-8(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(72) \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+12]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + _argvec[2+11] = (unsigned long)arg11; \ + _argvec[2+12] = (unsigned long)arg12; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "stw 2,-8(11)\n\t" /* save tocptr */ \ + "lwz 2,-4(11)\n\t" /* use nraddr's tocptr */ \ + VG_EXPAND_FRAME_BY_trashes_r3(72) \ + /* arg12 */ \ + "lwz 3,48(11)\n\t" \ + "stw 3,68(1)\n\t" \ + /* arg11 */ \ + "lwz 3,44(11)\n\t" \ + "stw 3,64(1)\n\t" \ + /* arg10 */ \ + "lwz 3,40(11)\n\t" \ + "stw 3,60(1)\n\t" \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,56(1)\n\t" \ + /* args1-8 */ \ + "lwz 3, 4(11)\n\t" /* arg1->r3 */ \ + "lwz 4, 8(11)\n\t" /* arg2->r4 */ \ + "lwz 5, 12(11)\n\t" /* arg3->r5 */ \ + "lwz 6, 16(11)\n\t" /* arg4->r6 */ \ + "lwz 7, 20(11)\n\t" /* arg5->r7 */ \ + "lwz 8, 24(11)\n\t" /* arg6->r8 */ \ + "lwz 9, 28(11)\n\t" /* arg7->r9 */ \ + "lwz 10, 32(11)\n\t" /* arg8->r10 */ \ + "lwz 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "lwz 2,-8(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(72) \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_ppc32_aix5 */ + +/* ------------------------ ppc64-aix5 ------------------------- */ + +#if defined(PLAT_ppc64_aix5) + +/* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */ + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS \ + "lr", "ctr", "xer", \ + "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \ + "r0", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \ + "r11", "r12", "r13" + +/* Expand the stack frame, copying enough info that unwinding + still works. Trashes r3. */ + +#define VG_EXPAND_FRAME_BY_trashes_r3(_n_fr) \ + "addi 1,1,-" #_n_fr "\n\t" \ + "ld 3," #_n_fr "(1)\n\t" \ + "std 3,0(1)\n\t" + +#define VG_CONTRACT_FRAME_BY(_n_fr) \ + "addi 1,1," #_n_fr "\n\t" + +/* These CALL_FN_ macros assume that on ppc64-aix5, sizeof(unsigned + long) == 8. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+0]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+1]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+2]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+3]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+4]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+5]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+6]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+7]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+8]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+9]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + VG_EXPAND_FRAME_BY_trashes_r3(128) \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(128) \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+10]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + VG_EXPAND_FRAME_BY_trashes_r3(128) \ + /* arg10 */ \ + "ld 3,80(11)\n\t" \ + "std 3,120(1)\n\t" \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(128) \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+11]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + _argvec[2+11] = (unsigned long)arg11; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + VG_EXPAND_FRAME_BY_trashes_r3(144) \ + /* arg11 */ \ + "ld 3,88(11)\n\t" \ + "std 3,128(1)\n\t" \ + /* arg10 */ \ + "ld 3,80(11)\n\t" \ + "std 3,120(1)\n\t" \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(144) \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+12]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + _argvec[2+11] = (unsigned long)arg11; \ + _argvec[2+12] = (unsigned long)arg12; \ + __asm__ volatile( \ + "mr 11,%1\n\t" \ + VG_EXPAND_FRAME_BY_trashes_r3(512) \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + VG_EXPAND_FRAME_BY_trashes_r3(144) \ + /* arg12 */ \ + "ld 3,96(11)\n\t" \ + "std 3,136(1)\n\t" \ + /* arg11 */ \ + "ld 3,88(11)\n\t" \ + "std 3,128(1)\n\t" \ + /* arg10 */ \ + "ld 3,80(11)\n\t" \ + "std 3,120(1)\n\t" \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VG_CONTRACT_FRAME_BY(144) \ + VG_CONTRACT_FRAME_BY(512) \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_ppc64_aix5 */ + + +/* ------------------------------------------------------------------ */ +/* ARCHITECTURE INDEPENDENT MACROS for CLIENT REQUESTS. */ +/* */ +/* ------------------------------------------------------------------ */ + +/* Some request codes. There are many more of these, but most are not + exposed to end-user view. These are the public ones, all of the + form 0x1000 + small_number. + + Core ones are in the range 0x00000000--0x0000ffff. The non-public + ones start at 0x2000. +*/ + +/* These macros are used by tools -- they must be public, but don't + embed them into other programs. */ +#define VG_USERREQ_TOOL_BASE(a,b) \ + ((unsigned int)(((a)&0xff) << 24 | ((b)&0xff) << 16)) +#define VG_IS_TOOL_USERREQ(a, b, v) \ + (VG_USERREQ_TOOL_BASE(a,b) == ((v) & 0xffff0000)) + +/* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !! + This enum comprises an ABI exported by Valgrind to programs + which use client requests. DO NOT CHANGE THE ORDER OF THESE + ENTRIES, NOR DELETE ANY -- add new ones at the end. */ +typedef + enum { VG_USERREQ__RUNNING_ON_VALGRIND = 0x1001, + VG_USERREQ__DISCARD_TRANSLATIONS = 0x1002, + + /* These allow any function to be called from the simulated + CPU but run on the real CPU. Nb: the first arg passed to + the function is always the ThreadId of the running + thread! So CLIENT_CALL0 actually requires a 1 arg + function, etc. */ + VG_USERREQ__CLIENT_CALL0 = 0x1101, + VG_USERREQ__CLIENT_CALL1 = 0x1102, + VG_USERREQ__CLIENT_CALL2 = 0x1103, + VG_USERREQ__CLIENT_CALL3 = 0x1104, + + /* Can be useful in regression testing suites -- eg. can + send Valgrind's output to /dev/null and still count + errors. */ + VG_USERREQ__COUNT_ERRORS = 0x1201, + + /* These are useful and can be interpreted by any tool that + tracks malloc() et al, by using vg_replace_malloc.c. */ + VG_USERREQ__MALLOCLIKE_BLOCK = 0x1301, + VG_USERREQ__FREELIKE_BLOCK = 0x1302, + /* Memory pool support. */ + VG_USERREQ__CREATE_MEMPOOL = 0x1303, + VG_USERREQ__DESTROY_MEMPOOL = 0x1304, + VG_USERREQ__MEMPOOL_ALLOC = 0x1305, + VG_USERREQ__MEMPOOL_FREE = 0x1306, + VG_USERREQ__MEMPOOL_TRIM = 0x1307, + VG_USERREQ__MOVE_MEMPOOL = 0x1308, + VG_USERREQ__MEMPOOL_CHANGE = 0x1309, + VG_USERREQ__MEMPOOL_EXISTS = 0x130a, + + /* Allow printfs to valgrind log. */ + /* The first two pass the va_list argument by value, which + assumes it is the same size as or smaller than a UWord, + which generally isn't the case. Hence are deprecated. + The second two pass the vargs by reference and so are + immune to this problem. */ + /* both :: char* fmt, va_list vargs (DEPRECATED) */ + VG_USERREQ__PRINTF = 0x1401, + VG_USERREQ__PRINTF_BACKTRACE = 0x1402, + /* both :: char* fmt, va_list* vargs */ + VG_USERREQ__PRINTF_VALIST_BY_REF = 0x1403, + VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF = 0x1404, + + /* Stack support. */ + VG_USERREQ__STACK_REGISTER = 0x1501, + VG_USERREQ__STACK_DEREGISTER = 0x1502, + VG_USERREQ__STACK_CHANGE = 0x1503, + + /* Wine support */ + VG_USERREQ__LOAD_PDB_DEBUGINFO = 0x1601, + + /* Querying of debug info. */ + VG_USERREQ__MAP_IP_TO_SRCLOC = 0x1701 + } Vg_ClientRequest; + +#if !defined(__GNUC__) +# define __extension__ /* */ +#endif + + +/* + * VALGRIND_DO_CLIENT_REQUEST_EXPR(): a C expression that invokes a Valgrind + * client request and whose value equals the client request result. + */ + +#if defined(NVALGRIND) + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + (_zzq_default) + +#else /*defined(NVALGRIND)*/ + +#if defined(_MSC_VER) + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + (vg_VALGRIND_DO_CLIENT_REQUEST_EXPR((uintptr_t)(_zzq_default), \ + (_zzq_request), (uintptr_t)(_zzq_arg1), (uintptr_t)(_zzq_arg2), \ + (uintptr_t)(_zzq_arg3), (uintptr_t)(_zzq_arg4), \ + (uintptr_t)(_zzq_arg5))) + +static __inline unsigned +vg_VALGRIND_DO_CLIENT_REQUEST_EXPR(uintptr_t _zzq_default, + unsigned _zzq_request, uintptr_t _zzq_arg1, + uintptr_t _zzq_arg2, uintptr_t _zzq_arg3, + uintptr_t _zzq_arg4, uintptr_t _zzq_arg5) +{ + unsigned _zzq_rlval; + VALGRIND_DO_CLIENT_REQUEST(_zzq_rlval, _zzq_default, _zzq_request, + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5); + return _zzq_rlval; +} + +#else /*defined(_MSC_VER)*/ + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + (__extension__({unsigned int _zzq_rlval; \ + VALGRIND_DO_CLIENT_REQUEST(_zzq_rlval, _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + _zzq_rlval; \ + })) + +#endif /*defined(_MSC_VER)*/ + +#endif /*defined(NVALGRIND)*/ + + +/* Returns the number of Valgrinds this code is running under. That + is, 0 if running natively, 1 if running under Valgrind, 2 if + running under Valgrind which is running under another Valgrind, + etc. */ +#define RUNNING_ON_VALGRIND \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* if not */, \ + VG_USERREQ__RUNNING_ON_VALGRIND, \ + 0, 0, 0, 0, 0) \ + + +/* Discard translation of code in the range [_qzz_addr .. _qzz_addr + + _qzz_len - 1]. Useful if you are debugging a JITter or some such, + since it provides a way to make sure valgrind will retranslate the + invalidated area. Returns no value. */ +#define VALGRIND_DISCARD_TRANSLATIONS(_qzz_addr,_qzz_len) \ + {unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__DISCARD_TRANSLATIONS, \ + _qzz_addr, _qzz_len, 0, 0, 0); \ + } + + +/* These requests are for getting Valgrind itself to print something. + Possibly with a backtrace. This is a really ugly hack. The return value + is the number of characters printed, excluding the "**<pid>** " part at the + start and the backtrace (if present). */ + +#if defined(NVALGRIND) + +# define VALGRIND_PRINTF(...) +# define VALGRIND_PRINTF_BACKTRACE(...) + +#else /* NVALGRIND */ + +#if !defined(_MSC_VER) +/* Modern GCC will optimize the static routine out if unused, + and unused attribute will shut down warnings about it. */ +static int VALGRIND_PRINTF(const char *format, ...) + __attribute__((format(__printf__, 1, 2), __unused__)); +#endif +static int +#if defined(_MSC_VER) +__inline +#endif +VALGRIND_PRINTF(const char *format, ...) +{ + unsigned long _qzz_res; + va_list vargs; + va_start(vargs, format); +#if defined(_MSC_VER) + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, + VG_USERREQ__PRINTF_VALIST_BY_REF, + (uintptr_t)format, + (uintptr_t)&vargs, + 0, 0, 0); +#else + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, + VG_USERREQ__PRINTF_VALIST_BY_REF, + (unsigned long)format, + (unsigned long)&vargs, + 0, 0, 0); +#endif + va_end(vargs); + return (int)_qzz_res; +} + +#if !defined(_MSC_VER) +static int VALGRIND_PRINTF_BACKTRACE(const char *format, ...) + __attribute__((format(__printf__, 1, 2), __unused__)); +#endif +static int +#if defined(_MSC_VER) +__inline +#endif +VALGRIND_PRINTF_BACKTRACE(const char *format, ...) +{ + unsigned long _qzz_res; + va_list vargs; + va_start(vargs, format); +#if defined(_MSC_VER) + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, + VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF, + (uintptr_t)format, + (uintptr_t)&vargs, + 0, 0, 0); +#else + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, + VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF, + (unsigned long)format, + (unsigned long)&vargs, + 0, 0, 0); +#endif + va_end(vargs); + return (int)_qzz_res; +} + +#endif /* NVALGRIND */ + + +/* These requests allow control to move from the simulated CPU to the + real CPU, calling an arbitary function. + + Note that the current ThreadId is inserted as the first argument. + So this call: + + VALGRIND_NON_SIMD_CALL2(f, arg1, arg2) + + requires f to have this signature: + + Word f(Word tid, Word arg1, Word arg2) + + where "Word" is a word-sized type. + + Note that these client requests are not entirely reliable. For example, + if you call a function with them that subsequently calls printf(), + there's a high chance Valgrind will crash. Generally, your prospects of + these working are made higher if the called function does not refer to + any global variables, and does not refer to any libc or other functions + (printf et al). Any kind of entanglement with libc or dynamic linking is + likely to have a bad outcome, for tricky reasons which we've grappled + with a lot in the past. +*/ +#define VALGRIND_NON_SIMD_CALL0(_qyy_fn) \ + __extension__ \ + ({unsigned long _qyy_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qyy_res, 0 /* default return */, \ + VG_USERREQ__CLIENT_CALL0, \ + _qyy_fn, \ + 0, 0, 0, 0); \ + _qyy_res; \ + }) + +#define VALGRIND_NON_SIMD_CALL1(_qyy_fn, _qyy_arg1) \ + __extension__ \ + ({unsigned long _qyy_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qyy_res, 0 /* default return */, \ + VG_USERREQ__CLIENT_CALL1, \ + _qyy_fn, \ + _qyy_arg1, 0, 0, 0); \ + _qyy_res; \ + }) + +#define VALGRIND_NON_SIMD_CALL2(_qyy_fn, _qyy_arg1, _qyy_arg2) \ + __extension__ \ + ({unsigned long _qyy_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qyy_res, 0 /* default return */, \ + VG_USERREQ__CLIENT_CALL2, \ + _qyy_fn, \ + _qyy_arg1, _qyy_arg2, 0, 0); \ + _qyy_res; \ + }) + +#define VALGRIND_NON_SIMD_CALL3(_qyy_fn, _qyy_arg1, _qyy_arg2, _qyy_arg3) \ + __extension__ \ + ({unsigned long _qyy_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qyy_res, 0 /* default return */, \ + VG_USERREQ__CLIENT_CALL3, \ + _qyy_fn, \ + _qyy_arg1, _qyy_arg2, \ + _qyy_arg3, 0); \ + _qyy_res; \ + }) + + +/* Counts the number of errors that have been recorded by a tool. Nb: + the tool must record the errors with VG_(maybe_record_error)() or + VG_(unique_error)() for them to be counted. */ +#define VALGRIND_COUNT_ERRORS \ + __extension__ \ + ({unsigned int _qyy_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qyy_res, 0 /* default return */, \ + VG_USERREQ__COUNT_ERRORS, \ + 0, 0, 0, 0, 0); \ + _qyy_res; \ + }) + +/* Several Valgrind tools (Memcheck, Massif, Helgrind, DRD) rely on knowing + when heap blocks are allocated in order to give accurate results. This + happens automatically for the standard allocator functions such as + malloc(), calloc(), realloc(), memalign(), new, new[], free(), delete, + delete[], etc. + + But if your program uses a custom allocator, this doesn't automatically + happen, and Valgrind will not do as well. For example, if you allocate + superblocks with mmap() and then allocates chunks of the superblocks, all + Valgrind's observations will be at the mmap() level and it won't know that + the chunks should be considered separate entities. In Memcheck's case, + that means you probably won't get heap block overrun detection (because + there won't be redzones marked as unaddressable) and you definitely won't + get any leak detection. + + The following client requests allow a custom allocator to be annotated so + that it can be handled accurately by Valgrind. + + VALGRIND_MALLOCLIKE_BLOCK marks a region of memory as having been allocated + by a malloc()-like function. For Memcheck (an illustrative case), this + does two things: + + - It records that the block has been allocated. This means any addresses + within the block mentioned in error messages will be + identified as belonging to the block. It also means that if the block + isn't freed it will be detected by the leak checker. + + - It marks the block as being addressable and undefined (if 'is_zeroed' is + not set), or addressable and defined (if 'is_zeroed' is set). This + controls how accesses to the block by the program are handled. + + 'addr' is the start of the usable block (ie. after any + redzone), 'sizeB' is its size. 'rzB' is the redzone size if the allocator + can apply redzones -- these are blocks of padding at the start and end of + each block. Adding redzones is recommended as it makes it much more likely + Valgrind will spot block overruns. `is_zeroed' indicates if the memory is + zeroed (or filled with another predictable value), as is the case for + calloc(). + + VALGRIND_MALLOCLIKE_BLOCK should be put immediately after the point where a + heap block -- that will be used by the client program -- is allocated. + It's best to put it at the outermost level of the allocator if possible; + for example, if you have a function my_alloc() which calls + internal_alloc(), and the client request is put inside internal_alloc(), + stack traces relating to the heap block will contain entries for both + my_alloc() and internal_alloc(), which is probably not what you want. + + For Memcheck users: if you use VALGRIND_MALLOCLIKE_BLOCK to carve out + custom blocks from within a heap block, B, that has been allocated with + malloc/calloc/new/etc, then block B will be *ignored* during leak-checking + -- the custom blocks will take precedence. + + VALGRIND_FREELIKE_BLOCK is the partner to VALGRIND_MALLOCLIKE_BLOCK. For + Memcheck, it does two things: + + - It records that the block has been deallocated. This assumes that the + block was annotated as having been allocated via + VALGRIND_MALLOCLIKE_BLOCK. Otherwise, an error will be issued. + + - It marks the block as being unaddressable. + + VALGRIND_FREELIKE_BLOCK should be put immediately after the point where a + heap block is deallocated. + + In many cases, these two client requests will not be enough to get your + allocator working well with Memcheck. More specifically, if your allocator + writes to freed blocks in any way then a VALGRIND_MAKE_MEM_UNDEFINED call + will be necessary to mark the memory as addressable just before the zeroing + occurs, otherwise you'll get a lot of invalid write errors. For example, + you'll need to do this if your allocator recycles freed blocks, but it + zeroes them before handing them back out (via VALGRIND_MALLOCLIKE_BLOCK). + Alternatively, if your allocator reuses freed blocks for allocator-internal + data structures, VALGRIND_MAKE_MEM_UNDEFINED calls will also be necessary. + + Really, what's happening is a blurring of the lines between the client + program and the allocator... after VALGRIND_FREELIKE_BLOCK is called, the + memory should be considered unaddressable to the client program, but the + allocator knows more than the rest of the client program and so may be able + to safely access it. Extra client requests are necessary for Valgrind to + understand the distinction between the allocator and the rest of the + program. + + Note: there is currently no VALGRIND_REALLOCLIKE_BLOCK client request; it + has to be emulated with MALLOCLIKE/FREELIKE and memory copying. + + Ignored if addr == 0. +*/ +#define VALGRIND_MALLOCLIKE_BLOCK(addr, sizeB, rzB, is_zeroed) \ + {unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__MALLOCLIKE_BLOCK, \ + addr, sizeB, rzB, is_zeroed, 0); \ + } + +/* See the comment for VALGRIND_MALLOCLIKE_BLOCK for details. + Ignored if addr == 0. +*/ +#define VALGRIND_FREELIKE_BLOCK(addr, rzB) \ + {unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__FREELIKE_BLOCK, \ + addr, rzB, 0, 0, 0); \ + } + +/* Create a memory pool. */ +#define VALGRIND_CREATE_MEMPOOL(pool, rzB, is_zeroed) \ + {unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__CREATE_MEMPOOL, \ + pool, rzB, is_zeroed, 0, 0); \ + } + +/* Destroy a memory pool. */ +#define VALGRIND_DESTROY_MEMPOOL(pool) \ + {unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__DESTROY_MEMPOOL, \ + pool, 0, 0, 0, 0); \ + } + +/* Associate a piece of memory with a memory pool. */ +#define VALGRIND_MEMPOOL_ALLOC(pool, addr, size) \ + {unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__MEMPOOL_ALLOC, \ + pool, addr, size, 0, 0); \ + } + +/* Disassociate a piece of memory from a memory pool. */ +#define VALGRIND_MEMPOOL_FREE(pool, addr) \ + {unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__MEMPOOL_FREE, \ + pool, addr, 0, 0, 0); \ + } + +/* Disassociate any pieces outside a particular range. */ +#define VALGRIND_MEMPOOL_TRIM(pool, addr, size) \ + {unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__MEMPOOL_TRIM, \ + pool, addr, size, 0, 0); \ + } + +/* Resize and/or move a piece associated with a memory pool. */ +#define VALGRIND_MOVE_MEMPOOL(poolA, poolB) \ + {unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__MOVE_MEMPOOL, \ + poolA, poolB, 0, 0, 0); \ + } + +/* Resize and/or move a piece associated with a memory pool. */ +#define VALGRIND_MEMPOOL_CHANGE(pool, addrA, addrB, size) \ + {unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__MEMPOOL_CHANGE, \ + pool, addrA, addrB, size, 0); \ + } + +/* Return 1 if a mempool exists, else 0. */ +#define VALGRIND_MEMPOOL_EXISTS(pool) \ + __extension__ \ + ({unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__MEMPOOL_EXISTS, \ + pool, 0, 0, 0, 0); \ + _qzz_res; \ + }) + +/* Mark a piece of memory as being a stack. Returns a stack id. */ +#define VALGRIND_STACK_REGISTER(start, end) \ + __extension__ \ + ({unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__STACK_REGISTER, \ + start, end, 0, 0, 0); \ + _qzz_res; \ + }) + +/* Unmark the piece of memory associated with a stack id as being a + stack. */ +#define VALGRIND_STACK_DEREGISTER(id) \ + {unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__STACK_DEREGISTER, \ + id, 0, 0, 0, 0); \ + } + +/* Change the start and end address of the stack id. */ +#define VALGRIND_STACK_CHANGE(id, start, end) \ + {unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__STACK_CHANGE, \ + id, start, end, 0, 0); \ + } + +/* Load PDB debug info for Wine PE image_map. */ +#define VALGRIND_LOAD_PDB_DEBUGINFO(fd, ptr, total_size, delta) \ + {unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__LOAD_PDB_DEBUGINFO, \ + fd, ptr, total_size, delta, 0); \ + } + +/* Map a code address to a source file name and line number. buf64 + must point to a 64-byte buffer in the caller's address space. The + result will be dumped in there and is guaranteed to be zero + terminated. If no info is found, the first byte is set to zero. */ +#define VALGRIND_MAP_IP_TO_SRCLOC(addr, buf64) \ + {unsigned int _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + VG_USERREQ__MAP_IP_TO_SRCLOC, \ + addr, buf64, 0, 0, 0); \ + } + + +#undef PLAT_x86_linux +#undef PLAT_amd64_linux +#undef PLAT_ppc32_linux +#undef PLAT_ppc64_linux +#undef PLAT_arm_linux +#undef PLAT_ppc32_aix5 +#undef PLAT_ppc64_aix5 + +#endif /* __VALGRIND_H */
diff --git a/src/base/third_party/xdg_mime/LICENSE b/src/base/third_party/xdg_mime/LICENSE new file mode 100644 index 0000000..55fedcf --- /dev/null +++ b/src/base/third_party/xdg_mime/LICENSE
@@ -0,0 +1,168 @@ +Licensed under the Academic Free License version 2.0 (below) +Or under the following terms: + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the +Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. + + +-------------------------------------------------------------------------------- +Academic Free License v. 2.0 +-------------------------------------------------------------------------------- + +This Academic Free License (the "License") applies to any original work of +authorship (the "Original Work") whose owner (the "Licensor") has placed the +following notice immediately following the copyright notice for the Original +Work: + +Licensed under the Academic Free License version 2.0 +1) Grant of Copyright License. Licensor hereby grants You a world-wide, +royalty-free, non-exclusive, perpetual, sublicenseable license to do the +following: + +a) to reproduce the Original Work in copies; +b) to prepare derivative works ("Derivative Works") based upon the Original + Work; +c) to distribute copies of the Original Work and Derivative Works to the + public; +d) to perform the Original Work publicly; and +e) to display the Original Work publicly. + +2) Grant of Patent License. Licensor hereby grants You a world-wide, +royalty-free, non-exclusive, perpetual, sublicenseable license, under patent +claims owned or controlled by the Licensor that are embodied in the Original +Work as furnished by the Licensor, to make, use, sell and offer for sale the +Original Work and Derivative Works. + +3) Grant of Source Code License. The term "Source Code" means the preferred +form of the Original Work for making modifications to it and all available +documentation describing how to modify the Original Work. Licensor hereby +agrees to provide a machine-readable copy of the Source Code of the Original +Work along with each copy of the Original Work that Licensor distributes. +Licensor reserves the right to satisfy this obligation by placing a +machine-readable copy of the Source Code in an information repository +reasonably calculated to permit inexpensive and convenient access by You for as +long as Licensor continues to distribute the Original Work, and by publishing +the address of that information repository in a notice immediately following +the copyright notice that applies to the Original Work. + +4) Exclusions From License Grant. Neither the names of Licensor, nor the names +of any contributors to the Original Work, nor any of their trademarks or +service marks, may be used to endorse or promote products derived from this +Original Work without express prior written permission of the Licensor. Nothing +in this License shall be deemed to grant any rights to trademarks, copyrights, +patents, trade secrets or any other intellectual property of Licensor except as +expressly stated herein. No patent license is granted to make, use, sell or +offer to sell embodiments of any patent claims other than the licensed claims +defined in Section 2. No right is granted to the trademarks of Licensor even if +such marks are included in the Original Work. Nothing in this License shall be +interpreted to prohibit Licensor from licensing under different terms from this +License any Original Work that Licensor otherwise would have a right to +license. + +5) This section intentionally omitted. + +6) Attribution Rights. You must retain, in the Source Code of any Derivative +Works that You create, all copyright, patent or trademark notices from the +Source Code of the Original Work, as well as any notices of licensing and any +descriptive text identified therein as an "Attribution Notice." You must cause +the Source Code for any Derivative Works that You create to carry a prominent +Attribution Notice reasonably calculated to inform recipients that You have +modified the Original Work. + +7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that +the copyright in and to the Original Work and the patent rights granted herein +by Licensor are owned by the Licensor or are sublicensed to You under the terms +of this License with the permission of the contributor(s) of those copyrights +and patent rights. Except as expressly stated in the immediately proceeding +sentence, the Original Work is provided under this License on an "AS IS" BASIS +and WITHOUT WARRANTY, either express or implied, including, without limitation, +the warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. +This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No +license to Original Work is granted hereunder except under this disclaimer. + +8) Limitation of Liability. Under no circumstances and under no legal theory, +whether in tort (including negligence), contract, or otherwise, shall the +Licensor be liable to any person for any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License +or the use of the Original Work including, without limitation, damages for loss +of goodwill, work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses. This limitation of liability shall not +apply to liability for death or personal injury resulting from Licensor's +negligence to the extent applicable law prohibits such limitation. Some +jurisdictions do not allow the exclusion or limitation of incidental or +consequential damages, so this exclusion and limitation may not apply to You. + +9) Acceptance and Termination. If You distribute copies of the Original Work or +a Derivative Work, You must make a reasonable effort under the circumstances to +obtain the express assent of recipients to the terms of this License. Nothing +else but this License (or another written agreement between Licensor and You) +grants You permission to create Derivative Works based upon the Original Work +or to exercise any of the rights granted in Section 1 herein, and any attempt +to do so except under the terms of this License (or another written agreement +between Licensor and You) is expressly prohibited by U.S. copyright law, the +equivalent laws of other countries, and by international treaty. Therefore, by +exercising any of the rights granted to You in Section 1 herein, You indicate +Your acceptance of this License and all of its terms and conditions. + +10) Termination for Patent Action. This License shall terminate automatically +and You may no longer exercise any of the rights granted to You by this License +as of the date You commence an action, including a cross-claim or counterclaim, +for patent infringement (i) against Licensor with respect to a patent +applicable to software or (ii) against any entity with respect to a patent +applicable to the Original Work (but excluding combinations of the Original +Work with other software or hardware). + +11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this +License may be brought only in the courts of a jurisdiction wherein the +Licensor resides or in which Licensor conducts its primary business, and under +the laws of that jurisdiction excluding its conflict-of-law provisions. The +application of the United Nations Convention on Contracts for the International +Sale of Goods is expressly excluded. Any use of the Original Work outside the +scope of this License or after its termination shall be subject to the +requirements and penalties of the U.S. Copyright Act, 17 U.S.C. 101 et seq., +the equivalent laws of other countries, and international treaty. This section +shall survive the termination of this License. + +12) Attorneys Fees. In any action to enforce the terms of this License or +seeking damages relating thereto, the prevailing party shall be entitled to +recover its costs and expenses, including, without limitation, reasonable +attorneys' fees and costs incurred in connection with such action, including +any appeal of such action. This section shall survive the termination of this +License. + +13) Miscellaneous. This License represents the complete agreement concerning +the subject matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent necessary to +make it enforceable. + +14) Definition of "You" in This License. "You" throughout this License, whether +in upper or lower case, means an individual or a legal entity exercising rights +under, and complying with all of the terms of, this License. For legal +entities, "You" includes any entity that controls, is controlled by, or is +under common control with you. For purposes of this definition, "control" means +(i) the power, direct or indirect, to cause the direction or management of such +entity, whether by contract or otherwise, or (ii) ownership of fifty percent +(50%) or more of the outstanding shares, or (iii) beneficial ownership of such +entity. + +15) Right to Use. You may use the Original Work in all ways not otherwise +restricted or conditioned by this License or by law, and Licensor promises not +to interfere with or be responsible for such uses by You. + +This license is Copyright (C) 2003 Lawrence E. Rosen. All rights reserved. +Permission is hereby granted to copy and distribute this license without +modification. This license may not be modified without the express written +permission of its copyright owner.
diff --git a/src/base/third_party/xdg_mime/README b/src/base/third_party/xdg_mime/README new file mode 100644 index 0000000..e7f3f68 --- /dev/null +++ b/src/base/third_party/xdg_mime/README
@@ -0,0 +1,8 @@ +This module is a simple module that parses the proposed MIME spec listed +at http://freedesktop.org/. It is currently targetted at version 0.12. +There are no formal releases planned for this module, and it is not +intended to be installed at this time. Rather, it is meant to be used +by other libraries or applications to add support for the MIME system. + +It is dual-licensed under the terms of the GNU Lesser General Public +License, and the Academic Free License, version 2.0.
diff --git a/src/base/third_party/xdg_mime/README.chromium b/src/base/third_party/xdg_mime/README.chromium new file mode 100644 index 0000000..29d239a --- /dev/null +++ b/src/base/third_party/xdg_mime/README.chromium
@@ -0,0 +1,11 @@ +Name: xdg-mime +URL: http://freedesktop.org +License: Academic Free License version 2.0 or LGPL v2 + +The code in this directory is synced from: +git://anongit.freedesktop.org/xdg/xdgmime +@ 2cdd8d36d7930d5a594587286cb1949ff62f7027 on 2012/08/06. + +In addition, we have the following patch(es): +- compile.patch: small tweaks to make the code compile. +- Added a LICENSE file.
diff --git a/src/base/third_party/xdg_mime/compile.patch b/src/base/third_party/xdg_mime/compile.patch new file mode 100644 index 0000000..cd055ed --- /dev/null +++ b/src/base/third_party/xdg_mime/compile.patch
@@ -0,0 +1,17 @@ +--- a/xdgmimecache.c ++++ b/xdgmimecache.c +@@ -40,6 +40,8 @@ + + #include <netinet/in.h> /* for ntohl/ntohs */ + ++#define HAVE_MMAP 1 ++ + #ifdef HAVE_MMAP + #include <sys/mman.h> + #else +@@ -1000,5 +1002,3 @@ + dump_glob_node (cache, offset + 20 * j, 0); + } + } +- +-
diff --git a/src/base/third_party/xdg_mime/xdgmime.c b/src/base/third_party/xdg_mime/xdgmime.c new file mode 100644 index 0000000..c7b16bb --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmime.c
@@ -0,0 +1,934 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmime.c: XDG Mime Spec mime resolver. Based on version 0.11 of the spec. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003,2004 Red Hat, Inc. + * Copyright (C) 2003,2004 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "xdgmime.h" +#include "xdgmimeint.h" +#include "xdgmimeglob.h" +#include "xdgmimemagic.h" +#include "xdgmimealias.h" +#include "xdgmimeicon.h" +#include "xdgmimeparent.h" +#include "xdgmimecache.h" +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/time.h> +#include <unistd.h> +#include <assert.h> + +typedef struct XdgDirTimeList XdgDirTimeList; +typedef struct XdgCallbackList XdgCallbackList; + +static int need_reread = TRUE; +static time_t last_stat_time = 0; + +static XdgGlobHash *global_hash = NULL; +static XdgMimeMagic *global_magic = NULL; +static XdgAliasList *alias_list = NULL; +static XdgParentList *parent_list = NULL; +static XdgDirTimeList *dir_time_list = NULL; +static XdgCallbackList *callback_list = NULL; +static XdgIconList *icon_list = NULL; +static XdgIconList *generic_icon_list = NULL; + +XdgMimeCache **_caches = NULL; +static int n_caches = 0; + +const char xdg_mime_type_unknown[] = "application/octet-stream"; +const char xdg_mime_type_empty[] = "application/x-zerosize"; +const char xdg_mime_type_textplain[] = "text/plain"; + + +enum +{ + XDG_CHECKED_UNCHECKED, + XDG_CHECKED_VALID, + XDG_CHECKED_INVALID +}; + +struct XdgDirTimeList +{ + time_t mtime; + char *directory_name; + int checked; + XdgDirTimeList *next; +}; + +struct XdgCallbackList +{ + XdgCallbackList *next; + XdgCallbackList *prev; + int callback_id; + XdgMimeCallback callback; + void *data; + XdgMimeDestroy destroy; +}; + +/* Function called by xdg_run_command_on_dirs. If it returns TRUE, further + * directories aren't looked at */ +typedef int (*XdgDirectoryFunc) (const char *directory, + void *user_data); + +static void +xdg_dir_time_list_add (char *file_name, + time_t mtime) +{ + XdgDirTimeList *list; + + for (list = dir_time_list; list; list = list->next) + { + if (strcmp (list->directory_name, file_name) == 0) + { + free (file_name); + return; + } + } + + list = calloc (1, sizeof (XdgDirTimeList)); + list->checked = XDG_CHECKED_UNCHECKED; + list->directory_name = file_name; + list->mtime = mtime; + list->next = dir_time_list; + dir_time_list = list; +} + +static void +xdg_dir_time_list_free (XdgDirTimeList *list) +{ + XdgDirTimeList *next; + + while (list) + { + next = list->next; + free (list->directory_name); + free (list); + list = next; + } +} + +static int +xdg_mime_init_from_directory (const char *directory) +{ + char *file_name; + struct stat st; + + assert (directory != NULL); + + file_name = malloc (strlen (directory) + strlen ("/mime/mime.cache") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/mime.cache"); + if (stat (file_name, &st) == 0) + { + XdgMimeCache *cache = _xdg_mime_cache_new_from_file (file_name); + + if (cache != NULL) + { + xdg_dir_time_list_add (file_name, st.st_mtime); + + _caches = realloc (_caches, sizeof (XdgMimeCache *) * (n_caches + 2)); + _caches[n_caches] = cache; + _caches[n_caches + 1] = NULL; + n_caches++; + + return FALSE; + } + } + free (file_name); + + file_name = malloc (strlen (directory) + strlen ("/mime/globs2") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/globs2"); + if (stat (file_name, &st) == 0) + { + _xdg_mime_glob_read_from_file (global_hash, file_name, TRUE); + xdg_dir_time_list_add (file_name, st.st_mtime); + } + else + { + free (file_name); + file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/globs"); + if (stat (file_name, &st) == 0) + { + _xdg_mime_glob_read_from_file (global_hash, file_name, FALSE); + xdg_dir_time_list_add (file_name, st.st_mtime); + } + else + { + free (file_name); + } + } + + file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/magic"); + if (stat (file_name, &st) == 0) + { + _xdg_mime_magic_read_from_file (global_magic, file_name); + xdg_dir_time_list_add (file_name, st.st_mtime); + } + else + { + free (file_name); + } + + file_name = malloc (strlen (directory) + strlen ("/mime/aliases") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/aliases"); + _xdg_mime_alias_read_from_file (alias_list, file_name); + free (file_name); + + file_name = malloc (strlen (directory) + strlen ("/mime/subclasses") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/subclasses"); + _xdg_mime_parent_read_from_file (parent_list, file_name); + free (file_name); + + file_name = malloc (strlen (directory) + strlen ("/mime/icons") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/icons"); + _xdg_mime_icon_read_from_file (icon_list, file_name); + free (file_name); + + file_name = malloc (strlen (directory) + strlen ("/mime/generic-icons") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/generic-icons"); + _xdg_mime_icon_read_from_file (generic_icon_list, file_name); + free (file_name); + + return FALSE; /* Keep processing */ +} + +/* Runs a command on all the directories in the search path */ +static void +xdg_run_command_on_dirs (XdgDirectoryFunc func, + void *user_data) +{ + const char *xdg_data_home; + const char *xdg_data_dirs; + const char *ptr; + + xdg_data_home = getenv ("XDG_DATA_HOME"); + if (xdg_data_home) + { + if ((func) (xdg_data_home, user_data)) + return; + } + else + { + const char *home; + + home = getenv ("HOME"); + if (home != NULL) + { + char *guessed_xdg_home; + int stop_processing; + + guessed_xdg_home = malloc (strlen (home) + strlen ("/.local/share/") + 1); + strcpy (guessed_xdg_home, home); + strcat (guessed_xdg_home, "/.local/share/"); + stop_processing = (func) (guessed_xdg_home, user_data); + free (guessed_xdg_home); + + if (stop_processing) + return; + } + } + + xdg_data_dirs = getenv ("XDG_DATA_DIRS"); + if (xdg_data_dirs == NULL) + xdg_data_dirs = "/usr/local/share/:/usr/share/"; + + ptr = xdg_data_dirs; + + while (*ptr != '\000') + { + const char *end_ptr; + char *dir; + int len; + int stop_processing; + + end_ptr = ptr; + while (*end_ptr != ':' && *end_ptr != '\000') + end_ptr ++; + + if (end_ptr == ptr) + { + ptr++; + continue; + } + + if (*end_ptr == ':') + len = end_ptr - ptr; + else + len = end_ptr - ptr + 1; + dir = malloc (len + 1); + strncpy (dir, ptr, len); + dir[len] = '\0'; + stop_processing = (func) (dir, user_data); + free (dir); + + if (stop_processing) + return; + + ptr = end_ptr; + } +} + +/* Checks file_path to make sure it has the same mtime as last time it was + * checked. If it has a different mtime, or if the file doesn't exist, it + * returns FALSE. + * + * FIXME: This doesn't protect against permission changes. + */ +static int +xdg_check_file (const char *file_path, + int *exists) +{ + struct stat st; + + /* If the file exists */ + if (stat (file_path, &st) == 0) + { + XdgDirTimeList *list; + + if (exists) + *exists = TRUE; + + for (list = dir_time_list; list; list = list->next) + { + if (! strcmp (list->directory_name, file_path)) + { + if (st.st_mtime == list->mtime) + list->checked = XDG_CHECKED_VALID; + else + list->checked = XDG_CHECKED_INVALID; + + return (list->checked != XDG_CHECKED_VALID); + } + } + return TRUE; + } + + if (exists) + *exists = FALSE; + + return FALSE; +} + +static int +xdg_check_dir (const char *directory, + int *invalid_dir_list) +{ + int invalid, exists; + char *file_name; + + assert (directory != NULL); + + /* Check the mime.cache file */ + file_name = malloc (strlen (directory) + strlen ("/mime/mime.cache") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/mime.cache"); + invalid = xdg_check_file (file_name, &exists); + free (file_name); + if (invalid) + { + *invalid_dir_list = TRUE; + return TRUE; + } + else if (exists) + { + return FALSE; + } + + /* Check the globs file */ + file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/globs"); + invalid = xdg_check_file (file_name, NULL); + free (file_name); + if (invalid) + { + *invalid_dir_list = TRUE; + return TRUE; + } + + /* Check the magic file */ + file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/magic"); + invalid = xdg_check_file (file_name, NULL); + free (file_name); + if (invalid) + { + *invalid_dir_list = TRUE; + return TRUE; + } + + return FALSE; /* Keep processing */ +} + +/* Walks through all the mime files stat()ing them to see if they've changed. + * Returns TRUE if they have. */ +static int +xdg_check_dirs (void) +{ + XdgDirTimeList *list; + int invalid_dir_list = FALSE; + + for (list = dir_time_list; list; list = list->next) + list->checked = XDG_CHECKED_UNCHECKED; + + xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_check_dir, + &invalid_dir_list); + + if (invalid_dir_list) + return TRUE; + + for (list = dir_time_list; list; list = list->next) + { + if (list->checked != XDG_CHECKED_VALID) + return TRUE; + } + + return FALSE; +} + +/* We want to avoid stat()ing on every single mime call, so we only look for + * newer files every 5 seconds. This will return TRUE if we need to reread the + * mime data from disk. + */ +static int +xdg_check_time_and_dirs (void) +{ + struct timeval tv; + time_t current_time; + int retval = FALSE; + + gettimeofday (&tv, NULL); + current_time = tv.tv_sec; + + if (current_time >= last_stat_time + 5) + { + retval = xdg_check_dirs (); + last_stat_time = current_time; + } + + return retval; +} + +/* Called in every public function. It reloads the hash function if need be. + */ +static void +xdg_mime_init (void) +{ + if (xdg_check_time_and_dirs ()) + { + xdg_mime_shutdown (); + } + + if (need_reread) + { + global_hash = _xdg_glob_hash_new (); + global_magic = _xdg_mime_magic_new (); + alias_list = _xdg_mime_alias_list_new (); + parent_list = _xdg_mime_parent_list_new (); + icon_list = _xdg_mime_icon_list_new (); + generic_icon_list = _xdg_mime_icon_list_new (); + + xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_mime_init_from_directory, + NULL); + + need_reread = FALSE; + } +} + +const char * +xdg_mime_get_mime_type_for_data (const void *data, + size_t len, + int *result_prio) +{ + const char *mime_type; + + if (len == 0) + { + *result_prio = 100; + return XDG_MIME_TYPE_EMPTY; + } + + xdg_mime_init (); + + if (_caches) + mime_type = _xdg_mime_cache_get_mime_type_for_data (data, len, result_prio); + else + mime_type = _xdg_mime_magic_lookup_data (global_magic, data, len, result_prio, NULL, 0); + + if (mime_type) + return mime_type; + + return _xdg_binary_or_text_fallback(data, len); +} + +const char * +xdg_mime_get_mime_type_for_file (const char *file_name, + struct stat *statbuf) +{ + const char *mime_type; + /* currently, only a few globs occur twice, and none + * more often, so 5 seems plenty. + */ + const char *mime_types[5]; + FILE *file; + unsigned char *data; + int max_extent; + int bytes_read; + struct stat buf; + const char *base_name; + int n; + + if (file_name == NULL) + return NULL; + if (! _xdg_utf8_validate (file_name)) + return NULL; + + xdg_mime_init (); + + if (_caches) + return _xdg_mime_cache_get_mime_type_for_file (file_name, statbuf); + + base_name = _xdg_get_base_name (file_name); + n = _xdg_glob_hash_lookup_file_name (global_hash, base_name, mime_types, 5); + + if (n == 1) + return mime_types[0]; + + if (!statbuf) + { + if (stat (file_name, &buf) != 0) + return XDG_MIME_TYPE_UNKNOWN; + + statbuf = &buf; + } + + if (!S_ISREG (statbuf->st_mode)) + return XDG_MIME_TYPE_UNKNOWN; + + /* FIXME: Need to make sure that max_extent isn't totally broken. This could + * be large and need getting from a stream instead of just reading it all + * in. */ + max_extent = _xdg_mime_magic_get_buffer_extents (global_magic); + data = malloc (max_extent); + if (data == NULL) + return XDG_MIME_TYPE_UNKNOWN; + + file = fopen (file_name, "r"); + if (file == NULL) + { + free (data); + return XDG_MIME_TYPE_UNKNOWN; + } + + bytes_read = fread (data, 1, max_extent, file); + if (ferror (file)) + { + free (data); + fclose (file); + return XDG_MIME_TYPE_UNKNOWN; + } + + mime_type = _xdg_mime_magic_lookup_data (global_magic, data, bytes_read, NULL, + mime_types, n); + + free (data); + fclose (file); + + if (mime_type) + return mime_type; + + return _xdg_binary_or_text_fallback(data, bytes_read); +} + +const char * +xdg_mime_get_mime_type_from_file_name (const char *file_name) +{ + const char *mime_type; + + xdg_mime_init (); + + if (_caches) + return _xdg_mime_cache_get_mime_type_from_file_name (file_name); + + if (_xdg_glob_hash_lookup_file_name (global_hash, file_name, &mime_type, 1)) + return mime_type; + else + return XDG_MIME_TYPE_UNKNOWN; +} + +int +xdg_mime_get_mime_types_from_file_name (const char *file_name, + const char *mime_types[], + int n_mime_types) +{ + xdg_mime_init (); + + if (_caches) + return _xdg_mime_cache_get_mime_types_from_file_name (file_name, mime_types, n_mime_types); + + return _xdg_glob_hash_lookup_file_name (global_hash, file_name, mime_types, n_mime_types); +} + +int +xdg_mime_is_valid_mime_type (const char *mime_type) +{ + /* FIXME: We should make this a better test + */ + return _xdg_utf8_validate (mime_type); +} + +void +xdg_mime_shutdown (void) +{ + XdgCallbackList *list; + + /* FIXME: Need to make this (and the whole library) thread safe */ + if (dir_time_list) + { + xdg_dir_time_list_free (dir_time_list); + dir_time_list = NULL; + } + + if (global_hash) + { + _xdg_glob_hash_free (global_hash); + global_hash = NULL; + } + if (global_magic) + { + _xdg_mime_magic_free (global_magic); + global_magic = NULL; + } + + if (alias_list) + { + _xdg_mime_alias_list_free (alias_list); + alias_list = NULL; + } + + if (parent_list) + { + _xdg_mime_parent_list_free (parent_list); + parent_list = NULL; + } + + if (icon_list) + { + _xdg_mime_icon_list_free (icon_list); + icon_list = NULL; + } + + if (generic_icon_list) + { + _xdg_mime_icon_list_free (generic_icon_list); + generic_icon_list = NULL; + } + + if (_caches) + { + int i; + + for (i = 0; i < n_caches; i++) + _xdg_mime_cache_unref (_caches[i]); + free (_caches); + _caches = NULL; + n_caches = 0; + } + + for (list = callback_list; list; list = list->next) + (list->callback) (list->data); + + need_reread = TRUE; +} + +int +xdg_mime_get_max_buffer_extents (void) +{ + xdg_mime_init (); + + if (_caches) + return _xdg_mime_cache_get_max_buffer_extents (); + + return _xdg_mime_magic_get_buffer_extents (global_magic); +} + +const char * +_xdg_mime_unalias_mime_type (const char *mime_type) +{ + const char *lookup; + + if (_caches) + return _xdg_mime_cache_unalias_mime_type (mime_type); + + if ((lookup = _xdg_mime_alias_list_lookup (alias_list, mime_type)) != NULL) + return lookup; + + return mime_type; +} + +const char * +xdg_mime_unalias_mime_type (const char *mime_type) +{ + xdg_mime_init (); + + return _xdg_mime_unalias_mime_type (mime_type); +} + +int +_xdg_mime_mime_type_equal (const char *mime_a, + const char *mime_b) +{ + const char *unalias_a, *unalias_b; + + unalias_a = _xdg_mime_unalias_mime_type (mime_a); + unalias_b = _xdg_mime_unalias_mime_type (mime_b); + + if (strcmp (unalias_a, unalias_b) == 0) + return 1; + + return 0; +} + +int +xdg_mime_mime_type_equal (const char *mime_a, + const char *mime_b) +{ + xdg_mime_init (); + + return _xdg_mime_mime_type_equal (mime_a, mime_b); +} + +int +xdg_mime_media_type_equal (const char *mime_a, + const char *mime_b) +{ + char *sep; + + sep = strchr (mime_a, '/'); + + if (sep && strncmp (mime_a, mime_b, sep - mime_a + 1) == 0) + return 1; + + return 0; +} + +#if 1 +static int +xdg_mime_is_super_type (const char *mime) +{ + int length; + const char *type; + + length = strlen (mime); + type = &(mime[length - 2]); + + if (strcmp (type, "/*") == 0) + return 1; + + return 0; +} +#endif + +int +_xdg_mime_mime_type_subclass (const char *mime, + const char *base) +{ + const char *umime, *ubase; + const char **parents; + + if (_caches) + return _xdg_mime_cache_mime_type_subclass (mime, base); + + umime = _xdg_mime_unalias_mime_type (mime); + ubase = _xdg_mime_unalias_mime_type (base); + + if (strcmp (umime, ubase) == 0) + return 1; + +#if 1 + /* Handle supertypes */ + if (xdg_mime_is_super_type (ubase) && + xdg_mime_media_type_equal (umime, ubase)) + return 1; +#endif + + /* Handle special cases text/plain and application/octet-stream */ + if (strcmp (ubase, "text/plain") == 0 && + strncmp (umime, "text/", 5) == 0) + return 1; + + if (strcmp (ubase, "application/octet-stream") == 0) + return 1; + + parents = _xdg_mime_parent_list_lookup (parent_list, umime); + for (; parents && *parents; parents++) + { + if (_xdg_mime_mime_type_subclass (*parents, ubase)) + return 1; + } + + return 0; +} + +int +xdg_mime_mime_type_subclass (const char *mime, + const char *base) +{ + xdg_mime_init (); + + return _xdg_mime_mime_type_subclass (mime, base); +} + +char ** +xdg_mime_list_mime_parents (const char *mime) +{ + const char **parents; + char **result; + int i, n; + + if (_caches) + return _xdg_mime_cache_list_mime_parents (mime); + + parents = xdg_mime_get_mime_parents (mime); + + if (!parents) + return NULL; + + for (i = 0; parents[i]; i++) ; + + n = (i + 1) * sizeof (char *); + result = (char **) malloc (n); + memcpy (result, parents, n); + + return result; +} + +const char ** +xdg_mime_get_mime_parents (const char *mime) +{ + const char *umime; + + xdg_mime_init (); + + umime = _xdg_mime_unalias_mime_type (mime); + + return _xdg_mime_parent_list_lookup (parent_list, umime); +} + +void +xdg_mime_dump (void) +{ + xdg_mime_init(); + + printf ("*** ALIASES ***\n\n"); + _xdg_mime_alias_list_dump (alias_list); + printf ("\n*** PARENTS ***\n\n"); + _xdg_mime_parent_list_dump (parent_list); + printf ("\n*** CACHE ***\n\n"); + _xdg_glob_hash_dump (global_hash); + printf ("\n*** GLOBS ***\n\n"); + _xdg_glob_hash_dump (global_hash); + printf ("\n*** GLOBS REVERSE TREE ***\n\n"); + _xdg_mime_cache_glob_dump (); +} + + +/* Registers a function to be called every time the mime database reloads its files + */ +int +xdg_mime_register_reload_callback (XdgMimeCallback callback, + void *data, + XdgMimeDestroy destroy) +{ + XdgCallbackList *list_el; + static int callback_id = 1; + + /* Make a new list element */ + list_el = calloc (1, sizeof (XdgCallbackList)); + list_el->callback_id = callback_id; + list_el->callback = callback; + list_el->data = data; + list_el->destroy = destroy; + list_el->next = callback_list; + if (list_el->next) + list_el->next->prev = list_el; + + callback_list = list_el; + callback_id ++; + + return callback_id - 1; +} + +void +xdg_mime_remove_callback (int callback_id) +{ + XdgCallbackList *list; + + for (list = callback_list; list; list = list->next) + { + if (list->callback_id == callback_id) + { + if (list->next) + list->next = list->prev; + + if (list->prev) + list->prev->next = list->next; + else + callback_list = list->next; + + /* invoke the destroy handler */ + (list->destroy) (list->data); + free (list); + return; + } + } +} + +const char * +xdg_mime_get_icon (const char *mime) +{ + xdg_mime_init (); + + if (_caches) + return _xdg_mime_cache_get_icon (mime); + + return _xdg_mime_icon_list_lookup (icon_list, mime); +} + +const char * +xdg_mime_get_generic_icon (const char *mime) +{ + xdg_mime_init (); + + if (_caches) + return _xdg_mime_cache_get_generic_icon (mime); + + return _xdg_mime_icon_list_lookup (generic_icon_list, mime); +}
diff --git a/src/base/third_party/xdg_mime/xdgmime.h b/src/base/third_party/xdg_mime/xdgmime.h new file mode 100644 index 0000000..6a34edf --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmime.h
@@ -0,0 +1,133 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmime.h: XDG Mime Spec mime resolver. Based on version 0.11 of the spec. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __XDG_MIME_H__ +#define __XDG_MIME_H__ + +#include <stdlib.h> +#include <sys/stat.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef XDG_PREFIX +#define XDG_ENTRY(func) _XDG_ENTRY2(XDG_PREFIX,func) +#define _XDG_ENTRY2(prefix,func) _XDG_ENTRY3(prefix,func) +#define _XDG_ENTRY3(prefix,func) prefix##_##func + +#define XDG_RESERVED_ENTRY(func) _XDG_RESERVED_ENTRY2(XDG_PREFIX,func) +#define _XDG_RESERVED_ENTRY2(prefix,func) _XDG_RESERVED_ENTRY3(prefix,func) +#define _XDG_RESERVED_ENTRY3(prefix,func) _##prefix##_##func +#endif + +typedef void (*XdgMimeCallback) (void *user_data); +typedef void (*XdgMimeDestroy) (void *user_data); + + +#ifdef XDG_PREFIX +#define xdg_mime_get_mime_type_for_data XDG_ENTRY(get_mime_type_for_data) +#define xdg_mime_get_mime_type_for_file XDG_ENTRY(get_mime_type_for_file) +#define xdg_mime_get_mime_type_from_file_name XDG_ENTRY(get_mime_type_from_file_name) +#define xdg_mime_get_mime_types_from_file_name XDG_ENTRY(get_mime_types_from_file_name) +#define xdg_mime_is_valid_mime_type XDG_ENTRY(is_valid_mime_type) +#define xdg_mime_mime_type_equal XDG_ENTRY(mime_type_equal) +#define xdg_mime_media_type_equal XDG_ENTRY(media_type_equal) +#define xdg_mime_mime_type_subclass XDG_ENTRY(mime_type_subclass) +#define xdg_mime_get_mime_parents XDG_ENTRY(get_mime_parents) +#define xdg_mime_list_mime_parents XDG_ENTRY(list_mime_parents) +#define xdg_mime_unalias_mime_type XDG_ENTRY(unalias_mime_type) +#define xdg_mime_get_max_buffer_extents XDG_ENTRY(get_max_buffer_extents) +#define xdg_mime_shutdown XDG_ENTRY(shutdown) +#define xdg_mime_dump XDG_ENTRY(dump) +#define xdg_mime_register_reload_callback XDG_ENTRY(register_reload_callback) +#define xdg_mime_remove_callback XDG_ENTRY(remove_callback) +#define xdg_mime_type_unknown XDG_ENTRY(type_unknown) +#define xdg_mime_type_empty XDG_ENTRY(type_empty) +#define xdg_mime_type_textplain XDG_ENTRY(type_textplain) +#define xdg_mime_get_icon XDG_ENTRY(get_icon) +#define xdg_mime_get_generic_icon XDG_ENTRY(get_generic_icon) + +#define _xdg_mime_mime_type_equal XDG_RESERVED_ENTRY(mime_type_equal) +#define _xdg_mime_mime_type_subclass XDG_RESERVED_ENTRY(mime_type_subclass) +#define _xdg_mime_unalias_mime_type XDG_RESERVED_ENTRY(unalias_mime_type) +#endif + +extern const char xdg_mime_type_unknown[]; +extern const char xdg_mime_type_empty[]; +extern const char xdg_mime_type_textplain[]; +#define XDG_MIME_TYPE_UNKNOWN xdg_mime_type_unknown +#define XDG_MIME_TYPE_EMPTY xdg_mime_type_empty +#define XDG_MIME_TYPE_TEXTPLAIN xdg_mime_type_textplain + +const char *xdg_mime_get_mime_type_for_data (const void *data, + size_t len, + int *result_prio); +const char *xdg_mime_get_mime_type_for_file (const char *file_name, + struct stat *statbuf); +const char *xdg_mime_get_mime_type_from_file_name (const char *file_name); +int xdg_mime_get_mime_types_from_file_name(const char *file_name, + const char *mime_types[], + int n_mime_types); +int xdg_mime_is_valid_mime_type (const char *mime_type); +int xdg_mime_mime_type_equal (const char *mime_a, + const char *mime_b); +int xdg_mime_media_type_equal (const char *mime_a, + const char *mime_b); +int xdg_mime_mime_type_subclass (const char *mime_a, + const char *mime_b); + /* xdg_mime_get_mime_parents() is deprecated since it does + * not work correctly with caches. Use xdg_mime_list_parents() + * instead, but notice that that function expects you to free + * the array it returns. + */ +const char **xdg_mime_get_mime_parents (const char *mime); +char ** xdg_mime_list_mime_parents (const char *mime); +const char *xdg_mime_unalias_mime_type (const char *mime); +const char *xdg_mime_get_icon (const char *mime); +const char *xdg_mime_get_generic_icon (const char *mime); +int xdg_mime_get_max_buffer_extents (void); +void xdg_mime_shutdown (void); +void xdg_mime_dump (void); +int xdg_mime_register_reload_callback (XdgMimeCallback callback, + void *data, + XdgMimeDestroy destroy); +void xdg_mime_remove_callback (int callback_id); + + /* Private versions of functions that don't call xdg_mime_init () */ +int _xdg_mime_mime_type_equal (const char *mime_a, + const char *mime_b); +int _xdg_mime_mime_type_subclass (const char *mime, + const char *base); +const char *_xdg_mime_unalias_mime_type (const char *mime); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __XDG_MIME_H__ */
diff --git a/src/base/third_party/xdg_mime/xdgmimealias.c b/src/base/third_party/xdg_mime/xdgmimealias.c new file mode 100644 index 0000000..07d89eb --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmimealias.c
@@ -0,0 +1,184 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimealias.c: Private file. Datastructure for storing the aliases. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2004 Red Hat, Inc. + * Copyright (C) 2004 Matthias Clasen <mclasen@redhat.com> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "xdgmimealias.h" +#include "xdgmimeint.h" +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <fnmatch.h> + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +typedef struct XdgAlias XdgAlias; + +struct XdgAlias +{ + char *alias; + char *mime_type; +}; + +struct XdgAliasList +{ + struct XdgAlias *aliases; + int n_aliases; +}; + +XdgAliasList * +_xdg_mime_alias_list_new (void) +{ + XdgAliasList *list; + + list = malloc (sizeof (XdgAliasList)); + + list->aliases = NULL; + list->n_aliases = 0; + + return list; +} + +void +_xdg_mime_alias_list_free (XdgAliasList *list) +{ + int i; + + if (list->aliases) + { + for (i = 0; i < list->n_aliases; i++) + { + free (list->aliases[i].alias); + free (list->aliases[i].mime_type); + } + free (list->aliases); + } + free (list); +} + +static int +alias_entry_cmp (const void *v1, const void *v2) +{ + return strcmp (((XdgAlias *)v1)->alias, ((XdgAlias *)v2)->alias); +} + +const char * +_xdg_mime_alias_list_lookup (XdgAliasList *list, + const char *alias) +{ + XdgAlias *entry; + XdgAlias key; + + if (list->n_aliases > 0) + { + key.alias = (char *)alias; + key.mime_type = NULL; + + entry = bsearch (&key, list->aliases, list->n_aliases, + sizeof (XdgAlias), alias_entry_cmp); + if (entry) + return entry->mime_type; + } + + return NULL; +} + +void +_xdg_mime_alias_read_from_file (XdgAliasList *list, + const char *file_name) +{ + FILE *file; + char line[255]; + int alloc; + + file = fopen (file_name, "r"); + + if (file == NULL) + return; + + /* FIXME: Not UTF-8 safe. Doesn't work if lines are greater than 255 chars. + * Blah */ + alloc = list->n_aliases + 16; + list->aliases = realloc (list->aliases, alloc * sizeof (XdgAlias)); + while (fgets (line, 255, file) != NULL) + { + char *sep; + if (line[0] == '#') + continue; + + sep = strchr (line, ' '); + if (sep == NULL) + continue; + *(sep++) = '\000'; + sep[strlen (sep) -1] = '\000'; + if (list->n_aliases == alloc) + { + alloc <<= 1; + list->aliases = realloc (list->aliases, + alloc * sizeof (XdgAlias)); + } + list->aliases[list->n_aliases].alias = strdup (line); + list->aliases[list->n_aliases].mime_type = strdup (sep); + list->n_aliases++; + } + list->aliases = realloc (list->aliases, + list->n_aliases * sizeof (XdgAlias)); + + fclose (file); + + if (list->n_aliases > 1) + qsort (list->aliases, list->n_aliases, + sizeof (XdgAlias), alias_entry_cmp); +} + + +void +_xdg_mime_alias_list_dump (XdgAliasList *list) +{ + int i; + + if (list->aliases) + { + for (i = 0; i < list->n_aliases; i++) + { + printf ("%s %s\n", + list->aliases[i].alias, + list->aliases[i].mime_type); + } + } +} + +
diff --git a/src/base/third_party/xdg_mime/xdgmimealias.h b/src/base/third_party/xdg_mime/xdgmimealias.h new file mode 100644 index 0000000..3c28012 --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmimealias.h
@@ -0,0 +1,51 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimealias.h: Private file. Datastructure for storing the aliases. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2004 Red Hat, Inc. + * Copyright (C) 200 Matthias Clasen <mclasen@redhat.com> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __XDG_MIME_ALIAS_H__ +#define __XDG_MIME_ALIAS_H__ + +#include "xdgmime.h" + +typedef struct XdgAliasList XdgAliasList; + +#ifdef XDG_PREFIX +#define _xdg_mime_alias_read_from_file XDG_RESERVED_ENTRY(alias_read_from_file) +#define _xdg_mime_alias_list_new XDG_RESERVED_ENTRY(alias_list_new) +#define _xdg_mime_alias_list_free XDG_RESERVED_ENTRY(alias_list_free) +#define _xdg_mime_alias_list_lookup XDG_RESERVED_ENTRY(alias_list_lookup) +#define _xdg_mime_alias_list_dump XDG_RESERVED_ENTRY(alias_list_dump) +#endif + +void _xdg_mime_alias_read_from_file (XdgAliasList *list, + const char *file_name); +XdgAliasList *_xdg_mime_alias_list_new (void); +void _xdg_mime_alias_list_free (XdgAliasList *list); +const char *_xdg_mime_alias_list_lookup (XdgAliasList *list, + const char *alias); +void _xdg_mime_alias_list_dump (XdgAliasList *list); + +#endif /* __XDG_MIME_ALIAS_H__ */
diff --git a/src/base/third_party/xdg_mime/xdgmimecache.c b/src/base/third_party/xdg_mime/xdgmimecache.c new file mode 100644 index 0000000..ddb8754 --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmimecache.c
@@ -0,0 +1,1069 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimealias.c: Private file. mmappable caches for mime data + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2005 Matthias Clasen <mclasen@redhat.com> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <fcntl.h> +#include <unistd.h> +#include <fnmatch.h> +#include <assert.h> + +#include <netinet/in.h> /* for ntohl/ntohs */ + +#define HAVE_MMAP 1 + +#ifdef HAVE_MMAP +#include <sys/mman.h> +#else +#warning Building xdgmime without MMAP support. Binary "mime.cache" files will not be used. +#endif + +#include <sys/stat.h> +#include <sys/types.h> + +#include "xdgmimecache.h" +#include "xdgmimeint.h" + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +#ifndef _O_BINARY +#define _O_BINARY 0 +#endif + +#ifndef MAP_FAILED +#define MAP_FAILED ((void *) -1) +#endif + +#define MAJOR_VERSION 1 +#define MINOR_VERSION_MIN 1 +#define MINOR_VERSION_MAX 2 + +struct _XdgMimeCache +{ + int ref_count; + int minor; + + size_t size; + char *buffer; +}; + +#define GET_UINT16(cache,offset) (ntohs(*(xdg_uint16_t*)((cache) + (offset)))) +#define GET_UINT32(cache,offset) (ntohl(*(xdg_uint32_t*)((cache) + (offset)))) + +XdgMimeCache * +_xdg_mime_cache_ref (XdgMimeCache *cache) +{ + cache->ref_count++; + return cache; +} + +void +_xdg_mime_cache_unref (XdgMimeCache *cache) +{ + cache->ref_count--; + + if (cache->ref_count == 0) + { +#ifdef HAVE_MMAP + munmap (cache->buffer, cache->size); +#endif + free (cache); + } +} + +XdgMimeCache * +_xdg_mime_cache_new_from_file (const char *file_name) +{ + XdgMimeCache *cache = NULL; + +#ifdef HAVE_MMAP + int fd = -1; + struct stat st; + char *buffer = NULL; + int minor; + + /* Open the file and map it into memory */ + fd = open (file_name, O_RDONLY|_O_BINARY, 0); + + if (fd < 0) + return NULL; + + if (fstat (fd, &st) < 0 || st.st_size < 4) + goto done; + + buffer = (char *) mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + + if (buffer == MAP_FAILED) + goto done; + + minor = GET_UINT16 (buffer, 2); + /* Verify version */ + if (GET_UINT16 (buffer, 0) != MAJOR_VERSION || + (minor < MINOR_VERSION_MIN || + minor > MINOR_VERSION_MAX)) + { + munmap (buffer, st.st_size); + + goto done; + } + + cache = (XdgMimeCache *) malloc (sizeof (XdgMimeCache)); + cache->minor = minor; + cache->ref_count = 1; + cache->buffer = buffer; + cache->size = st.st_size; + + done: + if (fd != -1) + close (fd); + +#endif /* HAVE_MMAP */ + + return cache; +} + +static int +cache_magic_matchlet_compare_to_data (XdgMimeCache *cache, + xdg_uint32_t offset, + const void *data, + size_t len) +{ + xdg_uint32_t range_start = GET_UINT32 (cache->buffer, offset); + xdg_uint32_t range_length = GET_UINT32 (cache->buffer, offset + 4); + xdg_uint32_t data_length = GET_UINT32 (cache->buffer, offset + 12); + xdg_uint32_t data_offset = GET_UINT32 (cache->buffer, offset + 16); + xdg_uint32_t mask_offset = GET_UINT32 (cache->buffer, offset + 20); + + int i, j; + + for (i = range_start; i < range_start + range_length; i++) + { + int valid_matchlet = TRUE; + + if (i + data_length > len) + return FALSE; + + if (mask_offset) + { + for (j = 0; j < data_length; j++) + { + if ((((unsigned char *)cache->buffer)[data_offset + j] & ((unsigned char *)cache->buffer)[mask_offset + j]) != + ((((unsigned char *) data)[j + i]) & ((unsigned char *)cache->buffer)[mask_offset + j])) + { + valid_matchlet = FALSE; + break; + } + } + } + else + { + valid_matchlet = memcmp(cache->buffer + data_offset, data + i, data_length) == 0; + } + + if (valid_matchlet) + return TRUE; + } + + return FALSE; +} + +static int +cache_magic_matchlet_compare (XdgMimeCache *cache, + xdg_uint32_t offset, + const void *data, + size_t len) +{ + xdg_uint32_t n_children = GET_UINT32 (cache->buffer, offset + 24); + xdg_uint32_t child_offset = GET_UINT32 (cache->buffer, offset + 28); + + int i; + + if (cache_magic_matchlet_compare_to_data (cache, offset, data, len)) + { + if (n_children == 0) + return TRUE; + + for (i = 0; i < n_children; i++) + { + if (cache_magic_matchlet_compare (cache, child_offset + 32 * i, + data, len)) + return TRUE; + } + } + + return FALSE; +} + +static const char * +cache_magic_compare_to_data (XdgMimeCache *cache, + xdg_uint32_t offset, + const void *data, + size_t len, + int *prio) +{ + xdg_uint32_t priority = GET_UINT32 (cache->buffer, offset); + xdg_uint32_t mimetype_offset = GET_UINT32 (cache->buffer, offset + 4); + xdg_uint32_t n_matchlets = GET_UINT32 (cache->buffer, offset + 8); + xdg_uint32_t matchlet_offset = GET_UINT32 (cache->buffer, offset + 12); + + int i; + + for (i = 0; i < n_matchlets; i++) + { + if (cache_magic_matchlet_compare (cache, matchlet_offset + i * 32, + data, len)) + { + *prio = priority; + + return cache->buffer + mimetype_offset; + } + } + + return NULL; +} + +static const char * +cache_magic_lookup_data (XdgMimeCache *cache, + const void *data, + size_t len, + int *prio, + const char *mime_types[], + int n_mime_types) +{ + xdg_uint32_t list_offset; + xdg_uint32_t n_entries; + xdg_uint32_t offset; + + int j, n; + + *prio = 0; + + list_offset = GET_UINT32 (cache->buffer, 24); + n_entries = GET_UINT32 (cache->buffer, list_offset); + offset = GET_UINT32 (cache->buffer, list_offset + 8); + + for (j = 0; j < n_entries; j++) + { + const char *match; + + match = cache_magic_compare_to_data (cache, offset + 16 * j, + data, len, prio); + if (match) + return match; + else + { + xdg_uint32_t mimetype_offset; + const char *non_match; + + mimetype_offset = GET_UINT32 (cache->buffer, offset + 16 * j + 4); + non_match = cache->buffer + mimetype_offset; + + for (n = 0; n < n_mime_types; n++) + { + if (mime_types[n] && + _xdg_mime_mime_type_equal (mime_types[n], non_match)) + mime_types[n] = NULL; + } + } + } + + return NULL; +} + +static const char * +cache_alias_lookup (const char *alias) +{ + const char *ptr; + int i, min, max, mid, cmp; + + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 4); + xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset); + xdg_uint32_t offset; + + min = 0; + max = n_entries - 1; + while (max >= min) + { + mid = (min + max) / 2; + + offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid); + ptr = cache->buffer + offset; + cmp = strcmp (ptr, alias); + + if (cmp < 0) + min = mid + 1; + else if (cmp > 0) + max = mid - 1; + else + { + offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid + 4); + return cache->buffer + offset; + } + } + } + + return NULL; +} + +typedef struct { + const char *mime; + int weight; +} MimeWeight; + +static int +cache_glob_lookup_literal (const char *file_name, + const char *mime_types[], + int n_mime_types, + int case_sensitive_check) +{ + const char *ptr; + int i, min, max, mid, cmp; + + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 12); + xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset); + xdg_uint32_t offset; + + min = 0; + max = n_entries - 1; + while (max >= min) + { + mid = (min + max) / 2; + + offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * mid); + ptr = cache->buffer + offset; + cmp = strcmp (ptr, file_name); + + if (cmp < 0) + min = mid + 1; + else if (cmp > 0) + max = mid - 1; + else + { + int weight = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * mid + 8); + int case_sensitive = weight & 0x100; + weight = weight & 0xff; + + if (case_sensitive_check || !case_sensitive) + { + offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * mid + 4); + mime_types[0] = (const char *)(cache->buffer + offset); + + return 1; + } + return 0; + } + } + } + + return 0; +} + +static int +cache_glob_lookup_fnmatch (const char *file_name, + MimeWeight mime_types[], + int n_mime_types, + int case_sensitive_check) +{ + const char *mime_type; + const char *ptr; + + int i, j, n; + + n = 0; + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + + xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 20); + xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset); + + for (j = 0; j < n_entries && n < n_mime_types; j++) + { + xdg_uint32_t offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * j); + xdg_uint32_t mimetype_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * j + 4); + int weight = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * j + 8); + int case_sensitive = weight & 0x100; + weight = weight & 0xff; + ptr = cache->buffer + offset; + mime_type = cache->buffer + mimetype_offset; + if (case_sensitive_check || !case_sensitive) + { + /* FIXME: Not UTF-8 safe */ + if (fnmatch (ptr, file_name, 0) == 0) + { + mime_types[n].mime = mime_type; + mime_types[n].weight = weight; + n++; + } + } + } + + if (n > 0) + return n; + } + + return 0; +} + +static int +cache_glob_node_lookup_suffix (XdgMimeCache *cache, + xdg_uint32_t n_entries, + xdg_uint32_t offset, + const char *file_name, + int len, + int case_sensitive_check, + MimeWeight mime_types[], + int n_mime_types) +{ + xdg_unichar_t character; + xdg_unichar_t match_char; + xdg_uint32_t mimetype_offset; + xdg_uint32_t n_children; + xdg_uint32_t child_offset; + int weight; + int case_sensitive; + + int min, max, mid, n, i; + + character = file_name[len - 1]; + + assert (character != 0); + + min = 0; + max = n_entries - 1; + while (max >= min) + { + mid = (min + max) / 2; + match_char = GET_UINT32 (cache->buffer, offset + 12 * mid); + if (match_char < character) + min = mid + 1; + else if (match_char > character) + max = mid - 1; + else + { + len--; + n = 0; + n_children = GET_UINT32 (cache->buffer, offset + 12 * mid + 4); + child_offset = GET_UINT32 (cache->buffer, offset + 12 * mid + 8); + + if (len > 0) + { + n = cache_glob_node_lookup_suffix (cache, + n_children, child_offset, + file_name, len, + case_sensitive_check, + mime_types, + n_mime_types); + } + if (n == 0) + { + i = 0; + while (n < n_mime_types && i < n_children) + { + match_char = GET_UINT32 (cache->buffer, child_offset + 12 * i); + if (match_char != 0) + break; + + mimetype_offset = GET_UINT32 (cache->buffer, child_offset + 12 * i + 4); + weight = GET_UINT32 (cache->buffer, child_offset + 12 * i + 8); + case_sensitive = weight & 0x100; + weight = weight & 0xff; + + if (case_sensitive_check || !case_sensitive) + { + mime_types[n].mime = cache->buffer + mimetype_offset; + mime_types[n].weight = weight; + n++; + } + i++; + } + } + return n; + } + } + return 0; +} + +static int +cache_glob_lookup_suffix (const char *file_name, + int len, + int ignore_case, + MimeWeight mime_types[], + int n_mime_types) +{ + int i, n; + + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + + xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 16); + xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset); + xdg_uint32_t offset = GET_UINT32 (cache->buffer, list_offset + 4); + + n = cache_glob_node_lookup_suffix (cache, + n_entries, offset, + file_name, len, + ignore_case, + mime_types, + n_mime_types); + if (n > 0) + return n; + } + + return 0; +} + +static int compare_mime_weight (const void *a, const void *b) +{ + const MimeWeight *aa = (const MimeWeight *)a; + const MimeWeight *bb = (const MimeWeight *)b; + + return bb->weight - aa->weight; +} + +#define ISUPPER(c) ((c) >= 'A' && (c) <= 'Z') +static char * +ascii_tolower (const char *str) +{ + char *p, *lower; + + lower = strdup (str); + p = lower; + while (*p != 0) + { + char c = *p; + *p++ = ISUPPER (c) ? c - 'A' + 'a' : c; + } + return lower; +} + +static int +cache_glob_lookup_file_name (const char *file_name, + const char *mime_types[], + int n_mime_types) +{ + int n; + MimeWeight mimes[10]; + int n_mimes = 10; + int i; + int len; + char *lower_case; + + assert (file_name != NULL && n_mime_types > 0); + + /* First, check the literals */ + + lower_case = ascii_tolower (file_name); + + n = cache_glob_lookup_literal (lower_case, mime_types, n_mime_types, FALSE); + if (n > 0) + { + free (lower_case); + return n; + } + + n = cache_glob_lookup_literal (file_name, mime_types, n_mime_types, TRUE); + if (n > 0) + { + free (lower_case); + return n; + } + + len = strlen (file_name); + n = cache_glob_lookup_suffix (lower_case, len, FALSE, mimes, n_mimes); + if (n == 0) + n = cache_glob_lookup_suffix (file_name, len, TRUE, mimes, n_mimes); + + /* Last, try fnmatch */ + if (n == 0) + n = cache_glob_lookup_fnmatch (lower_case, mimes, n_mimes, FALSE); + if (n == 0) + n = cache_glob_lookup_fnmatch (file_name, mimes, n_mimes, TRUE); + + free (lower_case); + + qsort (mimes, n, sizeof (MimeWeight), compare_mime_weight); + + if (n_mime_types < n) + n = n_mime_types; + + for (i = 0; i < n; i++) + mime_types[i] = mimes[i].mime; + + return n; +} + +int +_xdg_mime_cache_get_max_buffer_extents (void) +{ + xdg_uint32_t offset; + xdg_uint32_t max_extent; + int i; + + max_extent = 0; + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + + offset = GET_UINT32 (cache->buffer, 24); + max_extent = MAX (max_extent, GET_UINT32 (cache->buffer, offset + 4)); + } + + return max_extent; +} + +static const char * +cache_get_mime_type_for_data (const void *data, + size_t len, + int *result_prio, + const char *mime_types[], + int n_mime_types) +{ + const char *mime_type; + int i, n, priority; + + priority = 0; + mime_type = NULL; + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + + int prio; + const char *match; + + match = cache_magic_lookup_data (cache, data, len, &prio, + mime_types, n_mime_types); + if (prio > priority) + { + priority = prio; + mime_type = match; + } + } + + if (result_prio) + *result_prio = priority; + + if (priority > 0) + { + /* Pick glob-result R where mime_type inherits from R */ + for (n = 0; n < n_mime_types; n++) + { + if (mime_types[n] && _xdg_mime_cache_mime_type_subclass(mime_types[n], mime_type)) + return mime_types[n]; + } + + /* Return magic match */ + return mime_type; + } + + /* Pick first glob result, as fallback */ + for (n = 0; n < n_mime_types; n++) + { + if (mime_types[n]) + return mime_types[n]; + } + + return NULL; +} + +const char * +_xdg_mime_cache_get_mime_type_for_data (const void *data, + size_t len, + int *result_prio) +{ + return cache_get_mime_type_for_data (data, len, result_prio, NULL, 0); +} + +const char * +_xdg_mime_cache_get_mime_type_for_file (const char *file_name, + struct stat *statbuf) +{ + const char *mime_type; + const char *mime_types[10]; + FILE *file; + unsigned char *data; + int max_extent; + int bytes_read; + struct stat buf; + const char *base_name; + int n; + + if (file_name == NULL) + return NULL; + + if (! _xdg_utf8_validate (file_name)) + return NULL; + + base_name = _xdg_get_base_name (file_name); + n = cache_glob_lookup_file_name (base_name, mime_types, 10); + + if (n == 1) + return mime_types[0]; + + if (!statbuf) + { + if (stat (file_name, &buf) != 0) + return XDG_MIME_TYPE_UNKNOWN; + + statbuf = &buf; + } + + if (statbuf->st_size == 0) + return XDG_MIME_TYPE_EMPTY; + + if (!S_ISREG (statbuf->st_mode)) + return XDG_MIME_TYPE_UNKNOWN; + + /* FIXME: Need to make sure that max_extent isn't totally broken. This could + * be large and need getting from a stream instead of just reading it all + * in. */ + max_extent = _xdg_mime_cache_get_max_buffer_extents (); + data = malloc (max_extent); + if (data == NULL) + return XDG_MIME_TYPE_UNKNOWN; + + file = fopen (file_name, "r"); + if (file == NULL) + { + free (data); + return XDG_MIME_TYPE_UNKNOWN; + } + + bytes_read = fread (data, 1, max_extent, file); + if (ferror (file)) + { + free (data); + fclose (file); + return XDG_MIME_TYPE_UNKNOWN; + } + + mime_type = cache_get_mime_type_for_data (data, bytes_read, NULL, + mime_types, n); + + if (!mime_type) + mime_type = _xdg_binary_or_text_fallback(data, bytes_read); + + free (data); + fclose (file); + + return mime_type; +} + +const char * +_xdg_mime_cache_get_mime_type_from_file_name (const char *file_name) +{ + const char *mime_type; + + if (cache_glob_lookup_file_name (file_name, &mime_type, 1)) + return mime_type; + else + return XDG_MIME_TYPE_UNKNOWN; +} + +int +_xdg_mime_cache_get_mime_types_from_file_name (const char *file_name, + const char *mime_types[], + int n_mime_types) +{ + return cache_glob_lookup_file_name (file_name, mime_types, n_mime_types); +} + +#if 1 +static int +is_super_type (const char *mime) +{ + int length; + const char *type; + + length = strlen (mime); + type = &(mime[length - 2]); + + if (strcmp (type, "/*") == 0) + return 1; + + return 0; +} +#endif + +int +_xdg_mime_cache_mime_type_subclass (const char *mime, + const char *base) +{ + const char *umime, *ubase; + + int i, j, min, max, med, cmp; + + umime = _xdg_mime_cache_unalias_mime_type (mime); + ubase = _xdg_mime_cache_unalias_mime_type (base); + + if (strcmp (umime, ubase) == 0) + return 1; + + /* We really want to handle text/ * in GtkFileFilter, so we just + * turn on the supertype matching + */ +#if 1 + /* Handle supertypes */ + if (is_super_type (ubase) && + xdg_mime_media_type_equal (umime, ubase)) + return 1; +#endif + + /* Handle special cases text/plain and application/octet-stream */ + if (strcmp (ubase, "text/plain") == 0 && + strncmp (umime, "text/", 5) == 0) + return 1; + + if (strcmp (ubase, "application/octet-stream") == 0) + return 1; + + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + + xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 8); + xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset); + xdg_uint32_t offset, n_parents, parent_offset; + + min = 0; + max = n_entries - 1; + while (max >= min) + { + med = (min + max)/2; + + offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * med); + cmp = strcmp (cache->buffer + offset, umime); + if (cmp < 0) + min = med + 1; + else if (cmp > 0) + max = med - 1; + else + { + offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * med + 4); + n_parents = GET_UINT32 (cache->buffer, offset); + + for (j = 0; j < n_parents; j++) + { + parent_offset = GET_UINT32 (cache->buffer, offset + 4 + 4 * j); + if (_xdg_mime_cache_mime_type_subclass (cache->buffer + parent_offset, ubase)) + return 1; + } + + break; + } + } + } + + return 0; +} + +const char * +_xdg_mime_cache_unalias_mime_type (const char *mime) +{ + const char *lookup; + + lookup = cache_alias_lookup (mime); + + if (lookup) + return lookup; + + return mime; +} + +char ** +_xdg_mime_cache_list_mime_parents (const char *mime) +{ + int i, j, k, l, p; + char *all_parents[128]; /* we'll stop at 128 */ + char **result; + + mime = xdg_mime_unalias_mime_type (mime); + + p = 0; + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + + xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 8); + xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset); + + for (j = 0; j < n_entries; j++) + { + xdg_uint32_t mimetype_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * j); + xdg_uint32_t parents_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * j + 4); + + if (strcmp (cache->buffer + mimetype_offset, mime) == 0) + { + xdg_uint32_t parent_mime_offset; + xdg_uint32_t n_parents = GET_UINT32 (cache->buffer, parents_offset); + + for (k = 0; k < n_parents && p < 127; k++) + { + parent_mime_offset = GET_UINT32 (cache->buffer, parents_offset + 4 + 4 * k); + + /* Don't add same parent multiple times. + * This can happen for instance if the same type is listed in multiple directories + */ + for (l = 0; l < p; l++) + { + if (strcmp (all_parents[l], cache->buffer + parent_mime_offset) == 0) + break; + } + + if (l == p) + all_parents[p++] = cache->buffer + parent_mime_offset; + } + + break; + } + } + } + all_parents[p++] = NULL; + + result = (char **) malloc (p * sizeof (char *)); + memcpy (result, all_parents, p * sizeof (char *)); + + return result; +} + +static const char * +cache_lookup_icon (const char *mime, int header) +{ + const char *ptr; + int i, min, max, mid, cmp; + + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, header); + xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset); + xdg_uint32_t offset; + + min = 0; + max = n_entries - 1; + while (max >= min) + { + mid = (min + max) / 2; + + offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid); + ptr = cache->buffer + offset; + cmp = strcmp (ptr, mime); + + if (cmp < 0) + min = mid + 1; + else if (cmp > 0) + max = mid - 1; + else + { + offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid + 4); + return cache->buffer + offset; + } + } + } + + return NULL; +} + +const char * +_xdg_mime_cache_get_generic_icon (const char *mime) +{ + return cache_lookup_icon (mime, 36); +} + +const char * +_xdg_mime_cache_get_icon (const char *mime) +{ + return cache_lookup_icon (mime, 32); +} + +static void +dump_glob_node (XdgMimeCache *cache, + xdg_uint32_t offset, + int depth) +{ + xdg_unichar_t character; + xdg_uint32_t mime_offset; + xdg_uint32_t n_children; + xdg_uint32_t child_offset; + int i; + + character = GET_UINT32 (cache->buffer, offset); + mime_offset = GET_UINT32 (cache->buffer, offset + 4); + n_children = GET_UINT32 (cache->buffer, offset + 8); + child_offset = GET_UINT32 (cache->buffer, offset + 12); + for (i = 0; i < depth; i++) + printf (" "); + printf ("%c", character); + if (mime_offset) + printf (" - %s", cache->buffer + mime_offset); + printf ("\n"); + if (child_offset) + { + for (i = 0; i < n_children; i++) + dump_glob_node (cache, child_offset + 20 * i, depth + 1); + } +} + +void +_xdg_mime_cache_glob_dump (void) +{ + int i, j; + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + xdg_uint32_t list_offset; + xdg_uint32_t n_entries; + xdg_uint32_t offset; + list_offset = GET_UINT32 (cache->buffer, 16); + n_entries = GET_UINT32 (cache->buffer, list_offset); + offset = GET_UINT32 (cache->buffer, list_offset + 4); + for (j = 0; j < n_entries; j++) + dump_glob_node (cache, offset + 20 * j, 0); + } +}
diff --git a/src/base/third_party/xdg_mime/xdgmimecache.h b/src/base/third_party/xdg_mime/xdgmimecache.h new file mode 100644 index 0000000..27f42d0 --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmimecache.h
@@ -0,0 +1,81 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimecache.h: Private file. Datastructure for mmapped caches. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2005 Matthias Clasen <mclasen@redhat.com> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __XDG_MIME_CACHE_H__ +#define __XDG_MIME_CACHE_H__ + +#include "xdgmime.h" + +typedef struct _XdgMimeCache XdgMimeCache; + +#ifdef XDG_PREFIX +#define _xdg_mime_cache_new_from_file XDG_RESERVED_ENTRY(cache_new_from_file) +#define _xdg_mime_cache_ref XDG_RESERVED_ENTRY(cache_ref) +#define _xdg_mime_cache_unref XDG_RESERVED_ENTRY(cache_unref) +#define _xdg_mime_cache_get_max_buffer_extents XDG_RESERVED_ENTRY(cache_get_max_buffer_extents) +#define _xdg_mime_cache_get_mime_type_for_data XDG_RESERVED_ENTRY(cache_get_mime_type_for_data) +#define _xdg_mime_cache_get_mime_type_for_file XDG_RESERVED_ENTRY(cache_get_mime_type_for_file) +#define _xdg_mime_cache_get_mime_type_from_file_name XDG_RESERVED_ENTRY(cache_get_mime_type_from_file_name) +#define _xdg_mime_cache_get_mime_types_from_file_name XDG_RESERVED_ENTRY(cache_get_mime_types_from_file_name) +#define _xdg_mime_cache_list_mime_parents XDG_RESERVED_ENTRY(cache_list_mime_parents) +#define _xdg_mime_cache_mime_type_subclass XDG_RESERVED_ENTRY(cache_mime_type_subclass) +#define _xdg_mime_cache_unalias_mime_type XDG_RESERVED_ENTRY(cache_unalias_mime_type) +#define _xdg_mime_cache_get_icon XDG_RESERVED_ENTRY(cache_get_icon) +#define _xdg_mime_cache_get_generic_icon XDG_RESERVED_ENTRY(cache_get_generic_icon) +#define _xdg_mime_cache_glob_dump XDG_RESERVED_ENTRY(cache_glob_dump) +#endif + +extern XdgMimeCache **_caches; + +XdgMimeCache *_xdg_mime_cache_new_from_file (const char *file_name); +XdgMimeCache *_xdg_mime_cache_ref (XdgMimeCache *cache); +void _xdg_mime_cache_unref (XdgMimeCache *cache); + + +const char *_xdg_mime_cache_get_mime_type_for_data (const void *data, + size_t len, + int *result_prio); +const char *_xdg_mime_cache_get_mime_type_for_file (const char *file_name, + struct stat *statbuf); +int _xdg_mime_cache_get_mime_types_from_file_name (const char *file_name, + const char *mime_types[], + int n_mime_types); +const char *_xdg_mime_cache_get_mime_type_from_file_name (const char *file_name); +int _xdg_mime_cache_is_valid_mime_type (const char *mime_type); +int _xdg_mime_cache_mime_type_equal (const char *mime_a, + const char *mime_b); +int _xdg_mime_cache_media_type_equal (const char *mime_a, + const char *mime_b); +int _xdg_mime_cache_mime_type_subclass (const char *mime_a, + const char *mime_b); +char **_xdg_mime_cache_list_mime_parents (const char *mime); +const char *_xdg_mime_cache_unalias_mime_type (const char *mime); +int _xdg_mime_cache_get_max_buffer_extents (void); +const char *_xdg_mime_cache_get_icon (const char *mime); +const char *_xdg_mime_cache_get_generic_icon (const char *mime); +void _xdg_mime_cache_glob_dump (void); + +#endif /* __XDG_MIME_CACHE_H__ */
diff --git a/src/base/third_party/xdg_mime/xdgmimeglob.c b/src/base/third_party/xdg_mime/xdgmimeglob.c new file mode 100644 index 0000000..f8434bc --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmimeglob.c
@@ -0,0 +1,691 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimeglob.c: Private file. Datastructure for storing the globs. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "xdgmimeglob.h" +#include "xdgmimeint.h" +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <fnmatch.h> + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +typedef struct XdgGlobHashNode XdgGlobHashNode; +typedef struct XdgGlobList XdgGlobList; + +struct XdgGlobHashNode +{ + xdg_unichar_t character; + const char *mime_type; + int weight; + int case_sensitive; + XdgGlobHashNode *next; + XdgGlobHashNode *child; +}; +struct XdgGlobList +{ + const char *data; + const char *mime_type; + int weight; + int case_sensitive; + XdgGlobList *next; +}; + +struct XdgGlobHash +{ + XdgGlobList *literal_list; + XdgGlobHashNode *simple_node; + XdgGlobList *full_list; +}; + + +/* XdgGlobList + */ +static XdgGlobList * +_xdg_glob_list_new (void) +{ + XdgGlobList *new_element; + + new_element = calloc (1, sizeof (XdgGlobList)); + + return new_element; +} + +/* Frees glob_list and all of it's children */ +static void +_xdg_glob_list_free (XdgGlobList *glob_list) +{ + XdgGlobList *ptr, *next; + + ptr = glob_list; + + while (ptr != NULL) + { + next = ptr->next; + + if (ptr->data) + free ((void *) ptr->data); + if (ptr->mime_type) + free ((void *) ptr->mime_type); + free (ptr); + + ptr = next; + } +} + +static XdgGlobList * +_xdg_glob_list_append (XdgGlobList *glob_list, + void *data, + const char *mime_type, + int weight, + int case_sensitive) +{ + XdgGlobList *new_element; + XdgGlobList *tmp_element; + + tmp_element = glob_list; + while (tmp_element != NULL) + { + if (strcmp (tmp_element->data, data) == 0 && + strcmp (tmp_element->mime_type, mime_type) == 0) + return glob_list; + + tmp_element = tmp_element->next; + } + + new_element = _xdg_glob_list_new (); + new_element->data = data; + new_element->mime_type = mime_type; + new_element->weight = weight; + new_element->case_sensitive = case_sensitive; + if (glob_list == NULL) + return new_element; + + tmp_element = glob_list; + while (tmp_element->next != NULL) + tmp_element = tmp_element->next; + + tmp_element->next = new_element; + + return glob_list; +} + +/* XdgGlobHashNode + */ + +static XdgGlobHashNode * +_xdg_glob_hash_node_new (void) +{ + XdgGlobHashNode *glob_hash_node; + + glob_hash_node = calloc (1, sizeof (XdgGlobHashNode)); + + return glob_hash_node; +} + +static void +_xdg_glob_hash_node_dump (XdgGlobHashNode *glob_hash_node, + int depth) +{ + int i; + for (i = 0; i < depth; i++) + printf (" "); + + printf ("%c", (char)glob_hash_node->character); + if (glob_hash_node->mime_type) + printf (" - %s %d\n", glob_hash_node->mime_type, glob_hash_node->weight); + else + printf ("\n"); + if (glob_hash_node->child) + _xdg_glob_hash_node_dump (glob_hash_node->child, depth + 1); + if (glob_hash_node->next) + _xdg_glob_hash_node_dump (glob_hash_node->next, depth); +} + +static XdgGlobHashNode * +_xdg_glob_hash_insert_ucs4 (XdgGlobHashNode *glob_hash_node, + xdg_unichar_t *text, + const char *mime_type, + int weight, + int case_sensitive) +{ + XdgGlobHashNode *node; + xdg_unichar_t character; + + character = text[0]; + + if ((glob_hash_node == NULL) || + (character < glob_hash_node->character)) + { + node = _xdg_glob_hash_node_new (); + node->character = character; + node->next = glob_hash_node; + glob_hash_node = node; + } + else if (character == glob_hash_node->character) + { + node = glob_hash_node; + } + else + { + XdgGlobHashNode *prev_node; + int found_node = FALSE; + + /* Look for the first character of text in glob_hash_node, and insert it if we + * have to.*/ + prev_node = glob_hash_node; + node = prev_node->next; + + while (node != NULL) + { + if (character < node->character) + { + node = _xdg_glob_hash_node_new (); + node->character = character; + node->next = prev_node->next; + prev_node->next = node; + + found_node = TRUE; + break; + } + else if (character == node->character) + { + found_node = TRUE; + break; + } + prev_node = node; + node = node->next; + } + + if (! found_node) + { + node = _xdg_glob_hash_node_new (); + node->character = character; + node->next = prev_node->next; + prev_node->next = node; + } + } + + text++; + if (*text == 0) + { + if (node->mime_type) + { + if (strcmp (node->mime_type, mime_type) != 0) + { + XdgGlobHashNode *child; + int found_node = FALSE; + + child = node->child; + while (child && child->character == 0) + { + if (strcmp (child->mime_type, mime_type) == 0) + { + found_node = TRUE; + break; + } + child = child->next; + } + + if (!found_node) + { + child = _xdg_glob_hash_node_new (); + child->character = 0; + child->mime_type = strdup (mime_type); + child->weight = weight; + child->case_sensitive = case_sensitive; + child->child = NULL; + child->next = node->child; + node->child = child; + } + } + } + else + { + node->mime_type = strdup (mime_type); + node->weight = weight; + node->case_sensitive = case_sensitive; + } + } + else + { + node->child = _xdg_glob_hash_insert_ucs4 (node->child, text, mime_type, weight, case_sensitive); + } + return glob_hash_node; +} + +/* glob must be valid UTF-8 */ +static XdgGlobHashNode * +_xdg_glob_hash_insert_text (XdgGlobHashNode *glob_hash_node, + const char *text, + const char *mime_type, + int weight, + int case_sensitive) +{ + XdgGlobHashNode *node; + xdg_unichar_t *unitext; + int len; + + unitext = _xdg_convert_to_ucs4 (text, &len); + _xdg_reverse_ucs4 (unitext, len); + node = _xdg_glob_hash_insert_ucs4 (glob_hash_node, unitext, mime_type, weight, case_sensitive); + free (unitext); + return node; +} + +typedef struct { + const char *mime; + int weight; +} MimeWeight; + +static int +_xdg_glob_hash_node_lookup_file_name (XdgGlobHashNode *glob_hash_node, + const char *file_name, + int len, + int case_sensitive_check, + MimeWeight mime_types[], + int n_mime_types) +{ + int n; + XdgGlobHashNode *node; + xdg_unichar_t character; + + if (glob_hash_node == NULL) + return 0; + + character = file_name[len - 1]; + + for (node = glob_hash_node; node && character >= node->character; node = node->next) + { + if (character == node->character) + { + len--; + n = 0; + if (len > 0) + { + n = _xdg_glob_hash_node_lookup_file_name (node->child, + file_name, + len, + case_sensitive_check, + mime_types, + n_mime_types); + } + if (n == 0) + { + if (node->mime_type && + (case_sensitive_check || + !node->case_sensitive)) + { + mime_types[n].mime = node->mime_type; + mime_types[n].weight = node->weight; + n++; + } + node = node->child; + while (n < n_mime_types && node && node->character == 0) + { + if (node->mime_type && + (case_sensitive_check || + !node->case_sensitive)) + { + mime_types[n].mime = node->mime_type; + mime_types[n].weight = node->weight; + n++; + } + node = node->next; + } + } + return n; + } + } + + return 0; +} + +static int compare_mime_weight (const void *a, const void *b) +{ + const MimeWeight *aa = (const MimeWeight *)a; + const MimeWeight *bb = (const MimeWeight *)b; + + return bb->weight - aa->weight; +} + +#define ISUPPER(c) ((c) >= 'A' && (c) <= 'Z') +static char * +ascii_tolower (const char *str) +{ + char *p, *lower; + + lower = strdup (str); + p = lower; + while (*p != 0) + { + char c = *p; + *p++ = ISUPPER (c) ? c - 'A' + 'a' : c; + } + return lower; +} + +int +_xdg_glob_hash_lookup_file_name (XdgGlobHash *glob_hash, + const char *file_name, + const char *mime_types[], + int n_mime_types) +{ + XdgGlobList *list; + int i, n; + MimeWeight mimes[10]; + int n_mimes = 10; + int len; + char *lower_case; + + /* First, check the literals */ + + assert (file_name != NULL && n_mime_types > 0); + + n = 0; + + lower_case = ascii_tolower (file_name); + + for (list = glob_hash->literal_list; list; list = list->next) + { + if (strcmp ((const char *)list->data, file_name) == 0) + { + mime_types[0] = list->mime_type; + free (lower_case); + return 1; + } + } + + for (list = glob_hash->literal_list; list; list = list->next) + { + if (!list->case_sensitive && + strcmp ((const char *)list->data, lower_case) == 0) + { + mime_types[0] = list->mime_type; + free (lower_case); + return 1; + } + } + + + len = strlen (file_name); + n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, lower_case, len, FALSE, + mimes, n_mimes); + if (n == 0) + n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, file_name, len, TRUE, + mimes, n_mimes); + + if (n == 0) + { + for (list = glob_hash->full_list; list && n < n_mime_types; list = list->next) + { + if (fnmatch ((const char *)list->data, file_name, 0) == 0) + { + mimes[n].mime = list->mime_type; + mimes[n].weight = list->weight; + n++; + } + } + } + free (lower_case); + + qsort (mimes, n, sizeof (MimeWeight), compare_mime_weight); + + if (n_mime_types < n) + n = n_mime_types; + + for (i = 0; i < n; i++) + mime_types[i] = mimes[i].mime; + + return n; +} + + + +/* XdgGlobHash + */ + +XdgGlobHash * +_xdg_glob_hash_new (void) +{ + XdgGlobHash *glob_hash; + + glob_hash = calloc (1, sizeof (XdgGlobHash)); + + return glob_hash; +} + + +static void +_xdg_glob_hash_free_nodes (XdgGlobHashNode *node) +{ + if (node) + { + if (node->child) + _xdg_glob_hash_free_nodes (node->child); + if (node->next) + _xdg_glob_hash_free_nodes (node->next); + if (node->mime_type) + free ((void *) node->mime_type); + free (node); + } +} + +void +_xdg_glob_hash_free (XdgGlobHash *glob_hash) +{ + _xdg_glob_list_free (glob_hash->literal_list); + _xdg_glob_list_free (glob_hash->full_list); + _xdg_glob_hash_free_nodes (glob_hash->simple_node); + free (glob_hash); +} + +XdgGlobType +_xdg_glob_determine_type (const char *glob) +{ + const char *ptr; + int maybe_in_simple_glob = FALSE; + int first_char = TRUE; + + ptr = glob; + + while (*ptr != '\0') + { + if (*ptr == '*' && first_char) + maybe_in_simple_glob = TRUE; + else if (*ptr == '\\' || *ptr == '[' || *ptr == '?' || *ptr == '*') + return XDG_GLOB_FULL; + + first_char = FALSE; + ptr = _xdg_utf8_next_char (ptr); + } + if (maybe_in_simple_glob) + return XDG_GLOB_SIMPLE; + else + return XDG_GLOB_LITERAL; +} + +/* glob must be valid UTF-8 */ +void +_xdg_glob_hash_append_glob (XdgGlobHash *glob_hash, + const char *glob, + const char *mime_type, + int weight, + int case_sensitive) +{ + XdgGlobType type; + + assert (glob_hash != NULL); + assert (glob != NULL); + + type = _xdg_glob_determine_type (glob); + + switch (type) + { + case XDG_GLOB_LITERAL: + glob_hash->literal_list = _xdg_glob_list_append (glob_hash->literal_list, strdup (glob), strdup (mime_type), weight, case_sensitive); + break; + case XDG_GLOB_SIMPLE: + glob_hash->simple_node = _xdg_glob_hash_insert_text (glob_hash->simple_node, glob + 1, mime_type, weight, case_sensitive); + break; + case XDG_GLOB_FULL: + glob_hash->full_list = _xdg_glob_list_append (glob_hash->full_list, strdup (glob), strdup (mime_type), weight, case_sensitive); + break; + } +} + +void +_xdg_glob_hash_dump (XdgGlobHash *glob_hash) +{ + XdgGlobList *list; + printf ("LITERAL STRINGS\n"); + if (!glob_hash || glob_hash->literal_list == NULL) + { + printf (" None\n"); + } + else + { + for (list = glob_hash->literal_list; list; list = list->next) + printf (" %s - %s %d\n", (char *)list->data, list->mime_type, list->weight); + } + printf ("\nSIMPLE GLOBS\n"); + if (!glob_hash || glob_hash->simple_node == NULL) + { + printf (" None\n"); + } + else + { + _xdg_glob_hash_node_dump (glob_hash->simple_node, 4); + } + + printf ("\nFULL GLOBS\n"); + if (!glob_hash || glob_hash->full_list == NULL) + { + printf (" None\n"); + } + else + { + for (list = glob_hash->full_list; list; list = list->next) + printf (" %s - %s %d\n", (char *)list->data, list->mime_type, list->weight); + } +} + + +void +_xdg_mime_glob_read_from_file (XdgGlobHash *glob_hash, + const char *file_name, + int version_two) +{ + FILE *glob_file; + char line[255]; + char *p; + + glob_file = fopen (file_name, "r"); + + if (glob_file == NULL) + return; + + /* FIXME: Not UTF-8 safe. Doesn't work if lines are greater than 255 chars. + * Blah */ + while (fgets (line, 255, glob_file) != NULL) + { + char *colon; + char *mimetype, *glob, *end; + int weight; + int case_sensitive; + + if (line[0] == '#' || line[0] == 0) + continue; + + end = line + strlen(line) - 1; + if (*end == '\n') + *end = 0; + + p = line; + if (version_two) + { + colon = strchr (p, ':'); + if (colon == NULL) + continue; + *colon = 0; + weight = atoi (p); + p = colon + 1; + } + else + weight = 50; + + colon = strchr (p, ':'); + if (colon == NULL) + continue; + *colon = 0; + + mimetype = p; + p = colon + 1; + glob = p; + case_sensitive = FALSE; + + colon = strchr (p, ':'); + if (version_two && colon != NULL) + { + char *flag; + + /* We got flags */ + *colon = 0; + p = colon + 1; + + /* Flags end at next colon */ + colon = strchr (p, ':'); + if (colon != NULL) + *colon = 0; + + flag = strstr (p, "cs"); + if (flag != NULL && + /* Start or after comma */ + (flag == p || + flag[-1] == ',') && + /* ends with comma or end of string */ + (flag[2] == 0 || + flag[2] == ',')) + case_sensitive = TRUE; + } + + _xdg_glob_hash_append_glob (glob_hash, glob, mimetype, weight, case_sensitive); + } + + fclose (glob_file); +}
diff --git a/src/base/third_party/xdg_mime/xdgmimeglob.h b/src/base/third_party/xdg_mime/xdgmimeglob.h new file mode 100644 index 0000000..0018292 --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmimeglob.h
@@ -0,0 +1,70 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimeglob.h: Private file. Datastructure for storing the globs. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __XDG_MIME_GLOB_H__ +#define __XDG_MIME_GLOB_H__ + +#include "xdgmime.h" + +typedef struct XdgGlobHash XdgGlobHash; + +typedef enum +{ + XDG_GLOB_LITERAL, /* Makefile */ + XDG_GLOB_SIMPLE, /* *.gif */ + XDG_GLOB_FULL /* x*.[ch] */ +} XdgGlobType; + + +#ifdef XDG_PREFIX +#define _xdg_mime_glob_read_from_file XDG_RESERVED_ENTRY(glob_read_from_file) +#define _xdg_glob_hash_new XDG_RESERVED_ENTRY(hash_new) +#define _xdg_glob_hash_free XDG_RESERVED_ENTRY(hash_free) +#define _xdg_glob_hash_lookup_file_name XDG_RESERVED_ENTRY(hash_lookup_file_name) +#define _xdg_glob_hash_append_glob XDG_RESERVED_ENTRY(hash_append_glob) +#define _xdg_glob_determine_type XDG_RESERVED_ENTRY(determine_type) +#define _xdg_glob_hash_dump XDG_RESERVED_ENTRY(hash_dump) +#endif + +void _xdg_mime_glob_read_from_file (XdgGlobHash *glob_hash, + const char *file_name, + int version_two); +XdgGlobHash *_xdg_glob_hash_new (void); +void _xdg_glob_hash_free (XdgGlobHash *glob_hash); +int _xdg_glob_hash_lookup_file_name (XdgGlobHash *glob_hash, + const char *text, + const char *mime_types[], + int n_mime_types); +void _xdg_glob_hash_append_glob (XdgGlobHash *glob_hash, + const char *glob, + const char *mime_type, + int weight, + int case_sensitive); +XdgGlobType _xdg_glob_determine_type (const char *glob); +void _xdg_glob_hash_dump (XdgGlobHash *glob_hash); + +#endif /* __XDG_MIME_GLOB_H__ */
diff --git a/src/base/third_party/xdg_mime/xdgmimeicon.c b/src/base/third_party/xdg_mime/xdgmimeicon.c new file mode 100644 index 0000000..05c9473 --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmimeicon.c
@@ -0,0 +1,183 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimeicon.c: Private file. Datastructure for storing the aliases. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2008 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "xdgmimeicon.h" +#include "xdgmimeint.h" +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <fnmatch.h> + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +typedef struct XdgIcon XdgIcon; + +struct XdgIcon +{ + char *mime_type; + char *icon_name; +}; + +struct XdgIconList +{ + struct XdgIcon *icons; + int n_icons; +}; + +XdgIconList * +_xdg_mime_icon_list_new (void) +{ + XdgIconList *list; + + list = malloc (sizeof (XdgIconList)); + + list->icons = NULL; + list->n_icons = 0; + + return list; +} + +void +_xdg_mime_icon_list_free (XdgIconList *list) +{ + int i; + + if (list->icons) + { + for (i = 0; i < list->n_icons; i++) + { + free (list->icons[i].mime_type); + free (list->icons[i].icon_name); + } + free (list->icons); + } + free (list); +} + +static int +icon_entry_cmp (const void *v1, const void *v2) +{ + return strcmp (((XdgIcon *)v1)->mime_type, ((XdgIcon *)v2)->mime_type); +} + +const char * +_xdg_mime_icon_list_lookup (XdgIconList *list, + const char *mime_type) +{ + XdgIcon *entry; + XdgIcon key; + + if (list->n_icons > 0) + { + key.mime_type = (char *)mime_type; + key.icon_name = NULL; + + entry = bsearch (&key, list->icons, list->n_icons, + sizeof (XdgIcon), icon_entry_cmp); + if (entry) + return entry->icon_name; + } + + return NULL; +} + +void +_xdg_mime_icon_read_from_file (XdgIconList *list, + const char *file_name) +{ + FILE *file; + char line[255]; + int alloc; + + file = fopen (file_name, "r"); + + if (file == NULL) + return; + + /* FIXME: Not UTF-8 safe. Doesn't work if lines are greater than 255 chars. + * Blah */ + alloc = list->n_icons + 16; + list->icons = realloc (list->icons, alloc * sizeof (XdgIcon)); + while (fgets (line, 255, file) != NULL) + { + char *sep; + if (line[0] == '#') + continue; + + sep = strchr (line, ':'); + if (sep == NULL) + continue; + *(sep++) = '\000'; + sep[strlen (sep) -1] = '\000'; + if (list->n_icons == alloc) + { + alloc <<= 1; + list->icons = realloc (list->icons, + alloc * sizeof (XdgIcon)); + } + list->icons[list->n_icons].mime_type = strdup (line); + list->icons[list->n_icons].icon_name = strdup (sep); + list->n_icons++; + } + list->icons = realloc (list->icons, + list->n_icons * sizeof (XdgIcon)); + + fclose (file); + + if (list->n_icons > 1) + qsort (list->icons, list->n_icons, + sizeof (XdgIcon), icon_entry_cmp); +} + + +void +_xdg_mime_icon_list_dump (XdgIconList *list) +{ + int i; + + if (list->icons) + { + for (i = 0; i < list->n_icons; i++) + { + printf ("%s %s\n", + list->icons[i].mime_type, + list->icons[i].icon_name); + } + } +} + +
diff --git a/src/base/third_party/xdg_mime/xdgmimeicon.h b/src/base/third_party/xdg_mime/xdgmimeicon.h new file mode 100644 index 0000000..b5f2583 --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmimeicon.h
@@ -0,0 +1,50 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimeicon.h: Private file. Datastructure for storing the aliases. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2008 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __XDG_MIME_ICON_H__ +#define __XDG_MIME_ICON_H__ + +#include "xdgmime.h" + +typedef struct XdgIconList XdgIconList; + +#ifdef XDG_PREFIX +#define _xdg_mime_icon_read_from_file XDG_ENTRY(icon_read_from_file) +#define _xdg_mime_icon_list_new XDG_ENTRY(icon_list_new) +#define _xdg_mime_icon_list_free XDG_ENTRY(icon_list_free) +#define _xdg_mime_icon_list_lookup XDG_ENTRY(icon_list_lookup) +#define _xdg_mime_icon_list_dump XDG_ENTRY(icon_list_dump) +#endif + +void _xdg_mime_icon_read_from_file (XdgIconList *list, + const char *file_name); +XdgIconList *_xdg_mime_icon_list_new (void); +void _xdg_mime_icon_list_free (XdgIconList *list); +const char *_xdg_mime_icon_list_lookup (XdgIconList *list, + const char *mime); +void _xdg_mime_icon_list_dump (XdgIconList *list); + +#endif /* __XDG_MIME_ICON_H__ */
diff --git a/src/base/third_party/xdg_mime/xdgmimeint.c b/src/base/third_party/xdg_mime/xdgmimeint.c new file mode 100644 index 0000000..cf789d9 --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmimeint.c
@@ -0,0 +1,206 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimeint.c: Internal defines and functions. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "xdgmimeint.h" +#include <ctype.h> +#include <string.h> + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +static const char _xdg_utf8_skip_data[256] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1 +}; + +const char * const _xdg_utf8_skip = _xdg_utf8_skip_data; + + + +/* Returns the number of unprocessed characters. */ +xdg_unichar_t +_xdg_utf8_to_ucs4(const char *source) +{ + xdg_unichar_t ucs32; + if( ! ( *source & 0x80 ) ) + { + ucs32 = *source; + } + else + { + int bytelength = 0; + xdg_unichar_t result; + if ( ! (*source & 0x40) ) + { + ucs32 = *source; + } + else + { + if ( ! (*source & 0x20) ) + { + result = *source++ & 0x1F; + bytelength = 2; + } + else if ( ! (*source & 0x10) ) + { + result = *source++ & 0x0F; + bytelength = 3; + } + else if ( ! (*source & 0x08) ) + { + result = *source++ & 0x07; + bytelength = 4; + } + else if ( ! (*source & 0x04) ) + { + result = *source++ & 0x03; + bytelength = 5; + } + else if ( ! (*source & 0x02) ) + { + result = *source++ & 0x01; + bytelength = 6; + } + else + { + result = *source++; + bytelength = 1; + } + + for ( bytelength --; bytelength > 0; bytelength -- ) + { + result <<= 6; + result |= *source++ & 0x3F; + } + ucs32 = result; + } + } + return ucs32; +} + + +/* hullo. this is great code. don't rewrite it */ + +xdg_unichar_t +_xdg_ucs4_to_lower (xdg_unichar_t source) +{ + /* FIXME: Do a real to_upper sometime */ + /* CaseFolding-3.2.0.txt has a table of rules. */ + if ((source & 0xFF) == source) + return (xdg_unichar_t) tolower ((unsigned char) source); + return source; +} + +int +_xdg_utf8_validate (const char *source) +{ + /* FIXME: actually write */ + return TRUE; +} + +const char * +_xdg_get_base_name (const char *file_name) +{ + const char *base_name; + + if (file_name == NULL) + return NULL; + + base_name = strrchr (file_name, '/'); + + if (base_name == NULL) + return file_name; + else + return base_name + 1; +} + +xdg_unichar_t * +_xdg_convert_to_ucs4 (const char *source, int *len) +{ + xdg_unichar_t *out; + int i; + const char *p; + + out = malloc (sizeof (xdg_unichar_t) * (strlen (source) + 1)); + + p = source; + i = 0; + while (*p) + { + out[i++] = _xdg_utf8_to_ucs4 (p); + p = _xdg_utf8_next_char (p); + } + out[i] = 0; + *len = i; + + return out; +} + +void +_xdg_reverse_ucs4 (xdg_unichar_t *source, int len) +{ + xdg_unichar_t c; + int i; + + for (i = 0; i < len - i - 1; i++) + { + c = source[i]; + source[i] = source[len - i - 1]; + source[len - i - 1] = c; + } +} + +const char * +_xdg_binary_or_text_fallback(const void *data, size_t len) +{ + unsigned char *chardata; + int i; + + chardata = (unsigned char *) data; + for (i = 0; i < 32 && i < len; ++i) + { + if (chardata[i] < 32 && chardata[i] != 9 && chardata[i] != 10 && chardata[i] != 13) + return XDG_MIME_TYPE_UNKNOWN; /* binary data */ + } + + return XDG_MIME_TYPE_TEXTPLAIN; +}
diff --git a/src/base/third_party/xdg_mime/xdgmimeint.h b/src/base/third_party/xdg_mime/xdgmimeint.h new file mode 100644 index 0000000..9e8b2cb --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmimeint.h
@@ -0,0 +1,78 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimeint.h: Internal defines and functions. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __XDG_MIME_INT_H__ +#define __XDG_MIME_INT_H__ + +#include "xdgmime.h" + + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +/* FIXME: Needs to be configure check */ +typedef unsigned int xdg_unichar_t; +typedef unsigned char xdg_uchar8_t; +typedef unsigned short xdg_uint16_t; +typedef unsigned int xdg_uint32_t; + +#ifdef XDG_PREFIX +#define _xdg_utf8_skip XDG_RESERVED_ENTRY(utf8_skip) +#define _xdg_utf8_to_ucs4 XDG_RESERVED_ENTRY(utf8_to_ucs4) +#define _xdg_ucs4_to_lower XDG_RESERVED_ENTRY(ucs4_to_lower) +#define _xdg_utf8_validate XDG_RESERVED_ENTRY(utf8_validate) +#define _xdg_get_base_name XDG_RESERVED_ENTRY(get_base_name) +#define _xdg_convert_to_ucs4 XDG_RESERVED_ENTRY(convert_to_ucs4) +#define _xdg_reverse_ucs4 XDG_RESERVED_ENTRY(reverse_ucs4) +#endif + +#define SWAP_BE16_TO_LE16(val) (xdg_uint16_t)(((xdg_uint16_t)(val) << 8)|((xdg_uint16_t)(val) >> 8)) + +#define SWAP_BE32_TO_LE32(val) (xdg_uint32_t)((((xdg_uint32_t)(val) & 0xFF000000U) >> 24) | \ + (((xdg_uint32_t)(val) & 0x00FF0000U) >> 8) | \ + (((xdg_uint32_t)(val) & 0x0000FF00U) << 8) | \ + (((xdg_uint32_t)(val) & 0x000000FFU) << 24)) +/* UTF-8 utils + */ +extern const char *const _xdg_utf8_skip; +#define _xdg_utf8_next_char(p) (char *)((p) + _xdg_utf8_skip[*(unsigned char *)(p)]) +#define _xdg_utf8_char_size(p) (int) (_xdg_utf8_skip[*(unsigned char *)(p)]) + +xdg_unichar_t _xdg_utf8_to_ucs4 (const char *source); +xdg_unichar_t _xdg_ucs4_to_lower (xdg_unichar_t source); +int _xdg_utf8_validate (const char *source); +xdg_unichar_t *_xdg_convert_to_ucs4 (const char *source, int *len); +void _xdg_reverse_ucs4 (xdg_unichar_t *source, int len); +const char *_xdg_get_base_name (const char *file_name); +const char *_xdg_binary_or_text_fallback(const void *data, size_t len); + +#endif /* __XDG_MIME_INT_H__ */
diff --git a/src/base/third_party/xdg_mime/xdgmimemagic.c b/src/base/third_party/xdg_mime/xdgmimemagic.c new file mode 100644 index 0000000..a2320f5 --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmimemagic.c
@@ -0,0 +1,813 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimemagic.: Private file. Datastructure for storing magic files. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <assert.h> +#include "xdgmimemagic.h" +#include "xdgmimeint.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +#if !defined getc_unlocked && !defined HAVE_GETC_UNLOCKED +# define getc_unlocked(fp) getc (fp) +#endif + +typedef struct XdgMimeMagicMatch XdgMimeMagicMatch; +typedef struct XdgMimeMagicMatchlet XdgMimeMagicMatchlet; + +typedef enum +{ + XDG_MIME_MAGIC_SECTION, + XDG_MIME_MAGIC_MAGIC, + XDG_MIME_MAGIC_ERROR, + XDG_MIME_MAGIC_EOF +} XdgMimeMagicState; + +struct XdgMimeMagicMatch +{ + const char *mime_type; + int priority; + XdgMimeMagicMatchlet *matchlet; + XdgMimeMagicMatch *next; +}; + + +struct XdgMimeMagicMatchlet +{ + int indent; + int offset; + unsigned int value_length; + unsigned char *value; + unsigned char *mask; + unsigned int range_length; + unsigned int word_size; + XdgMimeMagicMatchlet *next; +}; + + +struct XdgMimeMagic +{ + XdgMimeMagicMatch *match_list; + int max_extent; +}; + +static XdgMimeMagicMatch * +_xdg_mime_magic_match_new (void) +{ + return calloc (1, sizeof (XdgMimeMagicMatch)); +} + + +static XdgMimeMagicMatchlet * +_xdg_mime_magic_matchlet_new (void) +{ + XdgMimeMagicMatchlet *matchlet; + + matchlet = malloc (sizeof (XdgMimeMagicMatchlet)); + + matchlet->indent = 0; + matchlet->offset = 0; + matchlet->value_length = 0; + matchlet->value = NULL; + matchlet->mask = NULL; + matchlet->range_length = 1; + matchlet->word_size = 1; + matchlet->next = NULL; + + return matchlet; +} + + +static void +_xdg_mime_magic_matchlet_free (XdgMimeMagicMatchlet *mime_magic_matchlet) +{ + if (mime_magic_matchlet) + { + if (mime_magic_matchlet->next) + _xdg_mime_magic_matchlet_free (mime_magic_matchlet->next); + if (mime_magic_matchlet->value) + free (mime_magic_matchlet->value); + if (mime_magic_matchlet->mask) + free (mime_magic_matchlet->mask); + free (mime_magic_matchlet); + } +} + + +/* Frees mime_magic_match and the remainder of its list + */ +static void +_xdg_mime_magic_match_free (XdgMimeMagicMatch *mime_magic_match) +{ + XdgMimeMagicMatch *ptr, *next; + + ptr = mime_magic_match; + while (ptr) + { + next = ptr->next; + + if (ptr->mime_type) + free ((void *) ptr->mime_type); + if (ptr->matchlet) + _xdg_mime_magic_matchlet_free (ptr->matchlet); + free (ptr); + + ptr = next; + } +} + +/* Reads in a hunk of data until a newline character or a '\000' is hit. The + * returned string is null terminated, and doesn't include the newline. + */ +static unsigned char * +_xdg_mime_magic_read_to_newline (FILE *magic_file, + int *end_of_file) +{ + unsigned char *retval; + int c; + int len, pos; + + len = 128; + pos = 0; + retval = malloc (len); + *end_of_file = FALSE; + + while (TRUE) + { + c = getc_unlocked (magic_file); + if (c == EOF) + { + *end_of_file = TRUE; + break; + } + if (c == '\n' || c == '\000') + break; + retval[pos++] = (unsigned char) c; + if (pos % 128 == 127) + { + len = len + 128; + retval = realloc (retval, len); + } + } + + retval[pos] = '\000'; + return retval; +} + +/* Returns the number read from the file, or -1 if no number could be read. + */ +static int +_xdg_mime_magic_read_a_number (FILE *magic_file, + int *end_of_file) +{ + /* LONG_MAX is about 20 characters on my system */ +#define MAX_NUMBER_SIZE 30 + char number_string[MAX_NUMBER_SIZE + 1]; + int pos = 0; + int c; + long retval = -1; + + while (TRUE) + { + c = getc_unlocked (magic_file); + + if (c == EOF) + { + *end_of_file = TRUE; + break; + } + if (! isdigit (c)) + { + ungetc (c, magic_file); + break; + } + number_string[pos] = (char) c; + pos++; + if (pos == MAX_NUMBER_SIZE) + break; + } + if (pos > 0) + { + number_string[pos] = '\000'; + errno = 0; + retval = strtol (number_string, NULL, 10); + + if ((retval < INT_MIN) || (retval > INT_MAX) || (errno != 0)) + return -1; + } + + return retval; +} + +/* Headers are of the format: + * [<priority>:<mime-type>] + */ +static XdgMimeMagicState +_xdg_mime_magic_parse_header (FILE *magic_file, XdgMimeMagicMatch *match) +{ + int c; + char *buffer; + char *end_ptr; + int end_of_file = 0; + + assert (magic_file != NULL); + assert (match != NULL); + + c = getc_unlocked (magic_file); + if (c == EOF) + return XDG_MIME_MAGIC_EOF; + if (c != '[') + return XDG_MIME_MAGIC_ERROR; + + match->priority = _xdg_mime_magic_read_a_number (magic_file, &end_of_file); + if (end_of_file) + return XDG_MIME_MAGIC_EOF; + if (match->priority == -1) + return XDG_MIME_MAGIC_ERROR; + + c = getc_unlocked (magic_file); + if (c == EOF) + return XDG_MIME_MAGIC_EOF; + if (c != ':') + return XDG_MIME_MAGIC_ERROR; + + buffer = (char *)_xdg_mime_magic_read_to_newline (magic_file, &end_of_file); + if (end_of_file) + return XDG_MIME_MAGIC_EOF; + + end_ptr = buffer; + while (*end_ptr != ']' && *end_ptr != '\000' && *end_ptr != '\n') + end_ptr++; + if (*end_ptr != ']') + { + free (buffer); + return XDG_MIME_MAGIC_ERROR; + } + *end_ptr = '\000'; + + match->mime_type = strdup (buffer); + free (buffer); + + return XDG_MIME_MAGIC_MAGIC; +} + +static XdgMimeMagicState +_xdg_mime_magic_parse_error (FILE *magic_file) +{ + int c; + + while (1) + { + c = getc_unlocked (magic_file); + if (c == EOF) + return XDG_MIME_MAGIC_EOF; + if (c == '\n') + return XDG_MIME_MAGIC_SECTION; + } +} + +/* Headers are of the format: + * [ indent ] ">" start-offset "=" value + * [ "&" mask ] [ "~" word-size ] [ "+" range-length ] "\n" + */ +static XdgMimeMagicState +_xdg_mime_magic_parse_magic_line (FILE *magic_file, + XdgMimeMagicMatch *match) +{ + XdgMimeMagicMatchlet *matchlet; + int c; + int end_of_file; + int indent = 0; + int bytes_read; + + assert (magic_file != NULL); + + /* Sniff the buffer to make sure it's a valid line */ + c = getc_unlocked (magic_file); + if (c == EOF) + return XDG_MIME_MAGIC_EOF; + else if (c == '[') + { + ungetc (c, magic_file); + return XDG_MIME_MAGIC_SECTION; + } + else if (c == '\n') + return XDG_MIME_MAGIC_MAGIC; + + /* At this point, it must be a digit or a '>' */ + end_of_file = FALSE; + if (isdigit (c)) + { + ungetc (c, magic_file); + indent = _xdg_mime_magic_read_a_number (magic_file, &end_of_file); + if (end_of_file) + return XDG_MIME_MAGIC_EOF; + if (indent == -1) + return XDG_MIME_MAGIC_ERROR; + c = getc_unlocked (magic_file); + if (c == EOF) + return XDG_MIME_MAGIC_EOF; + } + + if (c != '>') + return XDG_MIME_MAGIC_ERROR; + + matchlet = _xdg_mime_magic_matchlet_new (); + matchlet->indent = indent; + matchlet->offset = _xdg_mime_magic_read_a_number (magic_file, &end_of_file); + if (end_of_file) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_EOF; + } + if (matchlet->offset == -1) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_ERROR; + } + c = getc_unlocked (magic_file); + if (c == EOF) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_EOF; + } + else if (c != '=') + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_ERROR; + } + + /* Next two bytes determine how long the value is */ + matchlet->value_length = 0; + c = getc_unlocked (magic_file); + if (c == EOF) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_EOF; + } + matchlet->value_length = c & 0xFF; + matchlet->value_length = matchlet->value_length << 8; + + c = getc_unlocked (magic_file); + if (c == EOF) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_EOF; + } + matchlet->value_length = matchlet->value_length + (c & 0xFF); + + matchlet->value = malloc (matchlet->value_length); + + /* OOM */ + if (matchlet->value == NULL) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_ERROR; + } + bytes_read = fread (matchlet->value, 1, matchlet->value_length, magic_file); + if (bytes_read != matchlet->value_length) + { + _xdg_mime_magic_matchlet_free (matchlet); + if (feof (magic_file)) + return XDG_MIME_MAGIC_EOF; + else + return XDG_MIME_MAGIC_ERROR; + } + + c = getc_unlocked (magic_file); + if (c == '&') + { + matchlet->mask = malloc (matchlet->value_length); + /* OOM */ + if (matchlet->mask == NULL) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_ERROR; + } + bytes_read = fread (matchlet->mask, 1, matchlet->value_length, magic_file); + if (bytes_read != matchlet->value_length) + { + _xdg_mime_magic_matchlet_free (matchlet); + if (feof (magic_file)) + return XDG_MIME_MAGIC_EOF; + else + return XDG_MIME_MAGIC_ERROR; + } + c = getc_unlocked (magic_file); + } + + if (c == '~') + { + matchlet->word_size = _xdg_mime_magic_read_a_number (magic_file, &end_of_file); + if (end_of_file) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_EOF; + } + if (matchlet->word_size != 0 && + matchlet->word_size != 1 && + matchlet->word_size != 2 && + matchlet->word_size != 4) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_ERROR; + } + c = getc_unlocked (magic_file); + } + + if (c == '+') + { + matchlet->range_length = _xdg_mime_magic_read_a_number (magic_file, &end_of_file); + if (end_of_file) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_EOF; + } + if (matchlet->range_length == -1) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_ERROR; + } + c = getc_unlocked (magic_file); + } + + + if (c == '\n') + { + /* We clean up the matchlet, byte swapping if needed */ + if (matchlet->word_size > 1) + { + int i; + if (matchlet->value_length % matchlet->word_size != 0) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_ERROR; + } + /* FIXME: need to get this defined in a <config.h> style file */ +#if LITTLE_ENDIAN + for (i = 0; i < matchlet->value_length; i = i + matchlet->word_size) + { + if (matchlet->word_size == 2) + *((xdg_uint16_t *) matchlet->value + i) = SWAP_BE16_TO_LE16 (*((xdg_uint16_t *) (matchlet->value + i))); + else if (matchlet->word_size == 4) + *((xdg_uint32_t *) matchlet->value + i) = SWAP_BE32_TO_LE32 (*((xdg_uint32_t *) (matchlet->value + i))); + if (matchlet->mask) + { + if (matchlet->word_size == 2) + *((xdg_uint16_t *) matchlet->mask + i) = SWAP_BE16_TO_LE16 (*((xdg_uint16_t *) (matchlet->mask + i))); + else if (matchlet->word_size == 4) + *((xdg_uint32_t *) matchlet->mask + i) = SWAP_BE32_TO_LE32 (*((xdg_uint32_t *) (matchlet->mask + i))); + + } + } +#endif + } + + matchlet->next = match->matchlet; + match->matchlet = matchlet; + + + return XDG_MIME_MAGIC_MAGIC; + } + + _xdg_mime_magic_matchlet_free (matchlet); + if (c == EOF) + return XDG_MIME_MAGIC_EOF; + + return XDG_MIME_MAGIC_ERROR; +} + +static int +_xdg_mime_magic_matchlet_compare_to_data (XdgMimeMagicMatchlet *matchlet, + const void *data, + size_t len) +{ + int i, j; + for (i = matchlet->offset; i < matchlet->offset + matchlet->range_length; i++) + { + int valid_matchlet = TRUE; + + if (i + matchlet->value_length > len) + return FALSE; + + if (matchlet->mask) + { + for (j = 0; j < matchlet->value_length; j++) + { + if ((matchlet->value[j] & matchlet->mask[j]) != + ((((unsigned char *) data)[j + i]) & matchlet->mask[j])) + { + valid_matchlet = FALSE; + break; + } + } + } + else + { + for (j = 0; j < matchlet->value_length; j++) + { + if (matchlet->value[j] != ((unsigned char *) data)[j + i]) + { + valid_matchlet = FALSE; + break; + } + } + } + if (valid_matchlet) + return TRUE; + } + return FALSE; +} + +static int +_xdg_mime_magic_matchlet_compare_level (XdgMimeMagicMatchlet *matchlet, + const void *data, + size_t len, + int indent) +{ + while ((matchlet != NULL) && (matchlet->indent == indent)) + { + if (_xdg_mime_magic_matchlet_compare_to_data (matchlet, data, len)) + { + if ((matchlet->next == NULL) || + (matchlet->next->indent <= indent)) + return TRUE; + + if (_xdg_mime_magic_matchlet_compare_level (matchlet->next, + data, + len, + indent + 1)) + return TRUE; + } + + do + { + matchlet = matchlet->next; + } + while (matchlet && matchlet->indent > indent); + } + + return FALSE; +} + +static int +_xdg_mime_magic_match_compare_to_data (XdgMimeMagicMatch *match, + const void *data, + size_t len) +{ + return _xdg_mime_magic_matchlet_compare_level (match->matchlet, data, len, 0); +} + +static void +_xdg_mime_magic_insert_match (XdgMimeMagic *mime_magic, + XdgMimeMagicMatch *match) +{ + XdgMimeMagicMatch *list; + + if (mime_magic->match_list == NULL) + { + mime_magic->match_list = match; + return; + } + + if (match->priority > mime_magic->match_list->priority) + { + match->next = mime_magic->match_list; + mime_magic->match_list = match; + return; + } + + list = mime_magic->match_list; + while (list->next != NULL) + { + if (list->next->priority < match->priority) + { + match->next = list->next; + list->next = match; + return; + } + list = list->next; + } + list->next = match; + match->next = NULL; +} + +XdgMimeMagic * +_xdg_mime_magic_new (void) +{ + return calloc (1, sizeof (XdgMimeMagic)); +} + +void +_xdg_mime_magic_free (XdgMimeMagic *mime_magic) +{ + if (mime_magic) { + _xdg_mime_magic_match_free (mime_magic->match_list); + free (mime_magic); + } +} + +int +_xdg_mime_magic_get_buffer_extents (XdgMimeMagic *mime_magic) +{ + return mime_magic->max_extent; +} + +const char * +_xdg_mime_magic_lookup_data (XdgMimeMagic *mime_magic, + const void *data, + size_t len, + int *result_prio, + const char *mime_types[], + int n_mime_types) +{ + XdgMimeMagicMatch *match; + const char *mime_type; + int n; + int prio; + + prio = 0; + mime_type = NULL; + for (match = mime_magic->match_list; match; match = match->next) + { + if (_xdg_mime_magic_match_compare_to_data (match, data, len)) + { + prio = match->priority; + mime_type = match->mime_type; + break; + } + else + { + for (n = 0; n < n_mime_types; n++) + { + if (mime_types[n] && + _xdg_mime_mime_type_equal (mime_types[n], match->mime_type)) + mime_types[n] = NULL; + } + } + } + + if (mime_type == NULL) + { + for (n = 0; n < n_mime_types; n++) + { + if (mime_types[n]) + mime_type = mime_types[n]; + } + } + + if (result_prio) + *result_prio = prio; + + return mime_type; +} + +static void +_xdg_mime_update_mime_magic_extents (XdgMimeMagic *mime_magic) +{ + XdgMimeMagicMatch *match; + int max_extent = 0; + + for (match = mime_magic->match_list; match; match = match->next) + { + XdgMimeMagicMatchlet *matchlet; + + for (matchlet = match->matchlet; matchlet; matchlet = matchlet->next) + { + int extent; + + extent = matchlet->value_length + matchlet->offset + matchlet->range_length; + if (max_extent < extent) + max_extent = extent; + } + } + + mime_magic->max_extent = max_extent; +} + +static XdgMimeMagicMatchlet * +_xdg_mime_magic_matchlet_mirror (XdgMimeMagicMatchlet *matchlets) +{ + XdgMimeMagicMatchlet *new_list; + XdgMimeMagicMatchlet *tmp; + + if ((matchlets == NULL) || (matchlets->next == NULL)) + return matchlets; + + new_list = NULL; + tmp = matchlets; + while (tmp != NULL) + { + XdgMimeMagicMatchlet *matchlet; + + matchlet = tmp; + tmp = tmp->next; + matchlet->next = new_list; + new_list = matchlet; + } + + return new_list; + +} + +static void +_xdg_mime_magic_read_magic_file (XdgMimeMagic *mime_magic, + FILE *magic_file) +{ + XdgMimeMagicState state; + XdgMimeMagicMatch *match = NULL; /* Quiet compiler */ + + state = XDG_MIME_MAGIC_SECTION; + + while (state != XDG_MIME_MAGIC_EOF) + { + switch (state) + { + case XDG_MIME_MAGIC_SECTION: + match = _xdg_mime_magic_match_new (); + state = _xdg_mime_magic_parse_header (magic_file, match); + if (state == XDG_MIME_MAGIC_EOF || state == XDG_MIME_MAGIC_ERROR) + _xdg_mime_magic_match_free (match); + break; + case XDG_MIME_MAGIC_MAGIC: + state = _xdg_mime_magic_parse_magic_line (magic_file, match); + if (state == XDG_MIME_MAGIC_SECTION || + (state == XDG_MIME_MAGIC_EOF && match->mime_type)) + { + match->matchlet = _xdg_mime_magic_matchlet_mirror (match->matchlet); + _xdg_mime_magic_insert_match (mime_magic, match); + } + else if (state == XDG_MIME_MAGIC_EOF || state == XDG_MIME_MAGIC_ERROR) + _xdg_mime_magic_match_free (match); + break; + case XDG_MIME_MAGIC_ERROR: + state = _xdg_mime_magic_parse_error (magic_file); + break; + case XDG_MIME_MAGIC_EOF: + default: + /* Make the compiler happy */ + assert (0); + } + } + _xdg_mime_update_mime_magic_extents (mime_magic); +} + +void +_xdg_mime_magic_read_from_file (XdgMimeMagic *mime_magic, + const char *file_name) +{ + FILE *magic_file; + char header[12]; + + magic_file = fopen (file_name, "r"); + + if (magic_file == NULL) + return; + + if (fread (header, 1, 12, magic_file) == 12) + { + if (memcmp ("MIME-Magic\0\n", header, 12) == 0) + _xdg_mime_magic_read_magic_file (mime_magic, magic_file); + } + + fclose (magic_file); +}
diff --git a/src/base/third_party/xdg_mime/xdgmimemagic.h b/src/base/third_party/xdg_mime/xdgmimemagic.h new file mode 100644 index 0000000..35c8039 --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmimemagic.h
@@ -0,0 +1,57 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimemagic.h: Private file. Datastructure for storing the magic files. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __XDG_MIME_MAGIC_H__ +#define __XDG_MIME_MAGIC_H__ + +#include <unistd.h> +#include "xdgmime.h" +typedef struct XdgMimeMagic XdgMimeMagic; + +#ifdef XDG_PREFIX +#define _xdg_mime_glob_read_from_file XDG_RESERVED_ENTRY(glob_read_from_file) +#define _xdg_mime_magic_new XDG_RESERVED_ENTRY(magic_new) +#define _xdg_mime_magic_read_from_file XDG_RESERVED_ENTRY(magic_read_from_file) +#define _xdg_mime_magic_free XDG_RESERVED_ENTRY(magic_free) +#define _xdg_mime_magic_get_buffer_extents XDG_RESERVED_ENTRY(magic_get_buffer_extents) +#define _xdg_mime_magic_lookup_data XDG_RESERVED_ENTRY(magic_lookup_data) +#endif + + +XdgMimeMagic *_xdg_mime_magic_new (void); +void _xdg_mime_magic_read_from_file (XdgMimeMagic *mime_magic, + const char *file_name); +void _xdg_mime_magic_free (XdgMimeMagic *mime_magic); +int _xdg_mime_magic_get_buffer_extents (XdgMimeMagic *mime_magic); +const char *_xdg_mime_magic_lookup_data (XdgMimeMagic *mime_magic, + const void *data, + size_t len, + int *result_prio, + const char *mime_types[], + int n_mime_types); + +#endif /* __XDG_MIME_MAGIC_H__ */
diff --git a/src/base/third_party/xdg_mime/xdgmimeparent.c b/src/base/third_party/xdg_mime/xdgmimeparent.c new file mode 100644 index 0000000..511bbac --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmimeparent.c
@@ -0,0 +1,219 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimealias.c: Private file. Datastructure for storing the hierarchy. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2004 Red Hat, Inc. + * Copyright (C) 2004 Matthias Clasen <mclasen@redhat.com> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "xdgmimeparent.h" +#include "xdgmimeint.h" +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <fnmatch.h> + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +typedef struct XdgMimeParents XdgMimeParents; + +struct XdgMimeParents +{ + char *mime; + char **parents; + int n_parents; +}; + +struct XdgParentList +{ + struct XdgMimeParents *parents; + int n_mimes; +}; + +XdgParentList * +_xdg_mime_parent_list_new (void) +{ + XdgParentList *list; + + list = malloc (sizeof (XdgParentList)); + + list->parents = NULL; + list->n_mimes = 0; + + return list; +} + +void +_xdg_mime_parent_list_free (XdgParentList *list) +{ + int i; + char **p; + + if (list->parents) + { + for (i = 0; i < list->n_mimes; i++) + { + for (p = list->parents[i].parents; *p; p++) + free (*p); + + free (list->parents[i].parents); + free (list->parents[i].mime); + } + free (list->parents); + } + free (list); +} + +static int +parent_entry_cmp (const void *v1, const void *v2) +{ + return strcmp (((XdgMimeParents *)v1)->mime, ((XdgMimeParents *)v2)->mime); +} + +const char ** +_xdg_mime_parent_list_lookup (XdgParentList *list, + const char *mime) +{ + XdgMimeParents *entry; + XdgMimeParents key; + + if (list->n_mimes > 0) + { + key.mime = (char *)mime; + key.parents = NULL; + + entry = bsearch (&key, list->parents, list->n_mimes, + sizeof (XdgMimeParents), &parent_entry_cmp); + if (entry) + return (const char **)entry->parents; + } + + return NULL; +} + +void +_xdg_mime_parent_read_from_file (XdgParentList *list, + const char *file_name) +{ + FILE *file; + char line[255]; + int i, alloc; + XdgMimeParents *entry; + + file = fopen (file_name, "r"); + + if (file == NULL) + return; + + /* FIXME: Not UTF-8 safe. Doesn't work if lines are greater than 255 chars. + * Blah */ + alloc = list->n_mimes + 16; + list->parents = realloc (list->parents, alloc * sizeof (XdgMimeParents)); + while (fgets (line, 255, file) != NULL) + { + char *sep; + if (line[0] == '#') + continue; + + sep = strchr (line, ' '); + if (sep == NULL) + continue; + *(sep++) = '\000'; + sep[strlen (sep) -1] = '\000'; + entry = NULL; + for (i = 0; i < list->n_mimes; i++) + { + if (strcmp (list->parents[i].mime, line) == 0) + { + entry = &(list->parents[i]); + break; + } + } + + if (!entry) + { + if (list->n_mimes == alloc) + { + alloc <<= 1; + list->parents = realloc (list->parents, + alloc * sizeof (XdgMimeParents)); + } + list->parents[list->n_mimes].mime = strdup (line); + list->parents[list->n_mimes].parents = NULL; + entry = &(list->parents[list->n_mimes]); + list->n_mimes++; + } + + if (!entry->parents) + { + entry->n_parents = 1; + entry->parents = malloc ((entry->n_parents + 1) * sizeof (char *)); + } + else + { + entry->n_parents += 1; + entry->parents = realloc (entry->parents, + (entry->n_parents + 2) * sizeof (char *)); + } + entry->parents[entry->n_parents - 1] = strdup (sep); + entry->parents[entry->n_parents] = NULL; + } + + list->parents = realloc (list->parents, + list->n_mimes * sizeof (XdgMimeParents)); + + fclose (file); + + if (list->n_mimes > 1) + qsort (list->parents, list->n_mimes, + sizeof (XdgMimeParents), &parent_entry_cmp); +} + + +void +_xdg_mime_parent_list_dump (XdgParentList *list) +{ + int i; + char **p; + + if (list->parents) + { + for (i = 0; i < list->n_mimes; i++) + { + for (p = list->parents[i].parents; *p; p++) + printf ("%s %s\n", list->parents[i].mime, *p); + } + } +} + +
diff --git a/src/base/third_party/xdg_mime/xdgmimeparent.h b/src/base/third_party/xdg_mime/xdgmimeparent.h new file mode 100644 index 0000000..b564f41 --- /dev/null +++ b/src/base/third_party/xdg_mime/xdgmimeparent.h
@@ -0,0 +1,51 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimeparent.h: Private file. Datastructure for storing the hierarchy. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2004 Red Hat, Inc. + * Copyright (C) 200 Matthias Clasen <mclasen@redhat.com> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __XDG_MIME_PARENT_H__ +#define __XDG_MIME_PARENT_H__ + +#include "xdgmime.h" + +typedef struct XdgParentList XdgParentList; + +#ifdef XDG_PREFIX +#define _xdg_mime_parent_read_from_file XDG_RESERVED_ENTRY(parent_read_from_file) +#define _xdg_mime_parent_list_new XDG_RESERVED_ENTRY(parent_list_new) +#define _xdg_mime_parent_list_free XDG_RESERVED_ENTRY(parent_list_free) +#define _xdg_mime_parent_list_lookup XDG_RESERVED_ENTRY(parent_list_lookup) +#define _xdg_mime_parent_list_dump XDG_RESERVED_ENTRY(parent_list_dump) +#endif + +void _xdg_mime_parent_read_from_file (XdgParentList *list, + const char *file_name); +XdgParentList *_xdg_mime_parent_list_new (void); +void _xdg_mime_parent_list_free (XdgParentList *list); +const char **_xdg_mime_parent_list_lookup (XdgParentList *list, + const char *mime); +void _xdg_mime_parent_list_dump (XdgParentList *list); + +#endif /* __XDG_MIME_PARENT_H__ */
diff --git a/src/base/third_party/xdg_user_dirs/LICENSE b/src/base/third_party/xdg_user_dirs/LICENSE new file mode 100644 index 0000000..540e803 --- /dev/null +++ b/src/base/third_party/xdg_user_dirs/LICENSE
@@ -0,0 +1,21 @@ + Copyright (c) 2007 Red Hat, inc + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE.
diff --git a/src/base/third_party/xdg_user_dirs/README.chromium b/src/base/third_party/xdg_user_dirs/README.chromium new file mode 100644 index 0000000..b4cf6d7 --- /dev/null +++ b/src/base/third_party/xdg_user_dirs/README.chromium
@@ -0,0 +1,7 @@ +Name: xdg-user-dirs +URL: http://www.freedesktop.org/wiki/Software/xdg-user-dirs +License: MIT + +This directory include xdg-user-dir-lookup.c renamed as xdg_user_dir_lookup.cc +from xdg-user-dirs 0.10. We made xdg_user_dir_lookup() non-static and added a +xdg_user_dir_lookup.h.
diff --git a/src/base/third_party/xdg_user_dirs/xdg_user_dir_lookup.cc b/src/base/third_party/xdg_user_dirs/xdg_user_dir_lookup.cc new file mode 100644 index 0000000..343f70c --- /dev/null +++ b/src/base/third_party/xdg_user_dirs/xdg_user_dir_lookup.cc
@@ -0,0 +1,232 @@ +/* + This file is not licenced under the GPL like the rest of the code. + Its is under the MIT license, to encourage reuse by cut-and-paste. + + Copyright (c) 2007 Red Hat, inc + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/** + * xdg_user_dir_lookup_with_fallback: + * @type: a string specifying the type of directory + * @fallback: value to use if the directory isn't specified by the user + * @returns: a newly allocated absolute pathname + * + * Looks up a XDG user directory of the specified type. + * Example of types are "DESKTOP" and "DOWNLOAD". + * + * In case the user hasn't specified any directory for the specified + * type the value returned is @fallback. + * + * The return value is newly allocated and must be freed with + * free(). The return value is never NULL if @fallback != NULL, unless + * out of memory. + **/ +static char * +xdg_user_dir_lookup_with_fallback (const char *type, const char *fallback) +{ + FILE *file; + char *home_dir, *config_home, *config_file; + char buffer[512]; + char *user_dir; + char *p, *d; + int len; + int relative; + + home_dir = getenv ("HOME"); + + if (home_dir == NULL) + goto error; + + config_home = getenv ("XDG_CONFIG_HOME"); + if (config_home == NULL || config_home[0] == 0) + { + config_file = (char*) malloc (strlen (home_dir) + strlen ("/.config/user-dirs.dirs") + 1); + if (config_file == NULL) + goto error; + + strcpy (config_file, home_dir); + strcat (config_file, "/.config/user-dirs.dirs"); + } + else + { + config_file = (char*) malloc (strlen (config_home) + strlen ("/user-dirs.dirs") + 1); + if (config_file == NULL) + goto error; + + strcpy (config_file, config_home); + strcat (config_file, "/user-dirs.dirs"); + } + + file = fopen (config_file, "r"); + free (config_file); + if (file == NULL) + goto error; + + user_dir = NULL; + while (fgets (buffer, sizeof (buffer), file)) + { + /* Remove newline at end */ + len = strlen (buffer); + if (len > 0 && buffer[len-1] == '\n') + buffer[len-1] = 0; + + p = buffer; + while (*p == ' ' || *p == '\t') + p++; + + if (strncmp (p, "XDG_", 4) != 0) + continue; + p += 4; + if (strncmp (p, type, strlen (type)) != 0) + continue; + p += strlen (type); + if (strncmp (p, "_DIR", 4) != 0) + continue; + p += 4; + + while (*p == ' ' || *p == '\t') + p++; + + if (*p != '=') + continue; + p++; + + while (*p == ' ' || *p == '\t') + p++; + + if (*p != '"') + continue; + p++; + + relative = 0; + if (strncmp (p, "$HOME/", 6) == 0) + { + p += 6; + relative = 1; + } + else if (*p != '/') + continue; + + if (relative) + { + user_dir = (char*) malloc (strlen (home_dir) + 1 + strlen (p) + 1); + if (user_dir == NULL) + goto error2; + + strcpy (user_dir, home_dir); + strcat (user_dir, "/"); + } + else + { + user_dir = (char*) malloc (strlen (p) + 1); + if (user_dir == NULL) + goto error2; + + *user_dir = 0; + } + + d = user_dir + strlen (user_dir); + while (*p && *p != '"') + { + if ((*p == '\\') && (*(p+1) != 0)) + p++; + *d++ = *p++; + } + *d = 0; + } +error2: + fclose (file); + + if (user_dir) + return user_dir; + + error: + if (fallback) + return strdup (fallback); + return NULL; +} + +/** + * xdg_user_dir_lookup: + * @type: a string specifying the type of directory + * @returns: a newly allocated absolute pathname + * + * Looks up a XDG user directory of the specified type. + * Example of types are "DESKTOP" and "DOWNLOAD". + * + * The return value is always != NULL (unless out of memory), + * and if a directory + * for the type is not specified by the user the default + * is the home directory. Except for DESKTOP which defaults + * to ~/Desktop. + * + * The return value is newly allocated and must be freed with + * free(). + **/ +char * +xdg_user_dir_lookup (const char *type) +{ + char *dir, *home_dir, *user_dir; + + dir = xdg_user_dir_lookup_with_fallback (type, NULL); + if (dir != NULL) + return dir; + + home_dir = getenv ("HOME"); + + if (home_dir == NULL) + return strdup ("/tmp"); + + /* Special case desktop for historical compatibility */ + if (strcmp (type, "DESKTOP") == 0) + { + user_dir = (char*) malloc (strlen (home_dir) + strlen ("/Desktop") + 1); + if (user_dir == NULL) + return NULL; + + strcpy (user_dir, home_dir); + strcat (user_dir, "/Desktop"); + return user_dir; + } + + return strdup (home_dir); +} + +#ifdef STANDALONE_XDG_USER_DIR_LOOKUP +int +main (int argc, char *argv[]) +{ + if (argc != 2) + { + fprintf (stderr, "Usage %s <dir-type>\n", argv[0]); + exit (1); + } + + printf ("%s\n", xdg_user_dir_lookup (argv[1])); + return 0; +} +#endif
diff --git a/src/base/third_party/xdg_user_dirs/xdg_user_dir_lookup.h b/src/base/third_party/xdg_user_dirs/xdg_user_dir_lookup.h new file mode 100644 index 0000000..9e81e1b --- /dev/null +++ b/src/base/third_party/xdg_user_dirs/xdg_user_dir_lookup.h
@@ -0,0 +1,33 @@ +/* + This file is not licenced under the GPL like the rest of the code. + Its is under the MIT license, to encourage reuse by cut-and-paste. + + Copyright (c) 2007 Red Hat, inc + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#ifndef CHROME_THIRD_PARTY_XDG_USER_DIRS_XDG_USER_DIR_LOOKUP_H_ +#define CHROME_THIRD_PARTY_XDG_USER_DIRS_XDG_USER_DIR_LOOKUP_H_ + +char* xdg_user_dir_lookup(const char *type); + +#endif // CHROME_THIRD_PARTY_XDG_USER_DIRS_XDG_USER_DIR_LOOKUP_H_
diff --git a/src/base/thread_task_runner_handle.cc b/src/base/thread_task_runner_handle.cc new file mode 100644 index 0000000..d1b07aa --- /dev/null +++ b/src/base/thread_task_runner_handle.cc
@@ -0,0 +1,41 @@ +// 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/thread_task_runner_handle.h" + +#include "base/lazy_instance.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_local.h" + +namespace base { + +namespace { + +base::LazyInstance<base::ThreadLocalPointer<ThreadTaskRunnerHandle> > + lazy_tls_ptr = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// static +scoped_refptr<SingleThreadTaskRunner> ThreadTaskRunnerHandle::Get() { + ThreadTaskRunnerHandle* current = lazy_tls_ptr.Pointer()->Get(); + DCHECK(current); + return current->task_runner_; +} + +ThreadTaskRunnerHandle::ThreadTaskRunnerHandle( + const scoped_refptr<SingleThreadTaskRunner>& task_runner) + : task_runner_(task_runner) { + DCHECK(task_runner_->BelongsToCurrentThread()); + DCHECK(!lazy_tls_ptr.Pointer()->Get()); + lazy_tls_ptr.Pointer()->Set(this); +} + +ThreadTaskRunnerHandle::~ThreadTaskRunnerHandle() { + DCHECK(task_runner_->BelongsToCurrentThread()); + DCHECK_EQ(lazy_tls_ptr.Pointer()->Get(), this); + lazy_tls_ptr.Pointer()->Set(NULL); +} + +} // namespace base
diff --git a/src/base/thread_task_runner_handle.h b/src/base/thread_task_runner_handle.h new file mode 100644 index 0000000..599d15b --- /dev/null +++ b/src/base/thread_task_runner_handle.h
@@ -0,0 +1,35 @@ +// 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_THREAD_TASK_RUNNER_HANDLE_H_ +#define BASE_THREAD_TASK_RUNNER_HANDLE_H_ + +#include "base/base_export.h" +#include "base/memory/ref_counted.h" + +namespace base { + +class SingleThreadTaskRunner; + +// ThreadTaskRunnerHandle stores a reference to the main task runner +// for each thread. Not more than one of these objects can be created +// per thread. After an instance of this object is created the Get() +// function will return the |task_runner| specified in the +// constructor. +class BASE_EXPORT ThreadTaskRunnerHandle { + public: + // Gets the SingleThreadTaskRunner for the current thread. + static scoped_refptr<SingleThreadTaskRunner> Get(); + + ThreadTaskRunnerHandle( + const scoped_refptr<SingleThreadTaskRunner>& task_runner); + ~ThreadTaskRunnerHandle(); + + private: + scoped_refptr<SingleThreadTaskRunner> task_runner_; +}; + +} // namespace base + +#endif // BASE_THREAD_TASK_RUNNER_HANDLE_H_
diff --git a/src/base/threading/non_thread_safe.h b/src/base/threading/non_thread_safe.h new file mode 100644 index 0000000..cf7a418 --- /dev/null +++ b/src/base/threading/non_thread_safe.h
@@ -0,0 +1,73 @@ +// 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_THREADING_NON_THREAD_SAFE_H_ +#define BASE_THREADING_NON_THREAD_SAFE_H_ + +// Classes deriving from NonThreadSafe may need to suppress MSVC warning 4275: +// non dll-interface class 'Bar' used as base for dll-interface class 'Foo'. +// There is a specific macro to do it: NON_EXPORTED_BASE(), defined in +// compiler_specific.h +#include "base/compiler_specific.h" + +// See comment at top of thread_checker.h +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) +#define ENABLE_NON_THREAD_SAFE 1 +#else +#define ENABLE_NON_THREAD_SAFE 0 +#endif + +#if ENABLE_NON_THREAD_SAFE +#include "base/threading/non_thread_safe_impl.h" +#endif + +namespace base { + +// Do nothing implementation of NonThreadSafe, for release mode. +// +// Note: You should almost always use the NonThreadSafe class to get +// the right version of the class for your build configuration. +class NonThreadSafeDoNothing { + public: + bool CalledOnValidThread() const { + return true; + } + + protected: + ~NonThreadSafeDoNothing() {} + void DetachFromThread() {} +}; + +// NonThreadSafe is a helper class used to help verify that methods of a +// class are called from the same thread. One can inherit from this class +// and use CalledOnValidThread() to verify. +// +// This is intended to be used with classes that appear to be thread safe, but +// aren't. For example, a service or a singleton like the preferences system. +// +// Example: +// class MyClass : public base::NonThreadSafe { +// public: +// void Foo() { +// DCHECK(CalledOnValidThread()); +// ... (do stuff) ... +// } +// } +// +// Note that base::ThreadChecker offers identical functionality to +// NonThreadSafe, but does not require inheritence. 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. +#if ENABLE_NON_THREAD_SAFE +typedef NonThreadSafeImpl NonThreadSafe; +#else +typedef NonThreadSafeDoNothing NonThreadSafe; +#endif // ENABLE_NON_THREAD_SAFE + +#undef ENABLE_NON_THREAD_SAFE + +} // namespace base + +#endif // BASE_NON_THREAD_SAFE_H_
diff --git a/src/base/threading/non_thread_safe_impl.cc b/src/base/threading/non_thread_safe_impl.cc new file mode 100644 index 0000000..7e729d9 --- /dev/null +++ b/src/base/threading/non_thread_safe_impl.cc
@@ -0,0 +1,23 @@ +// 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/threading/non_thread_safe_impl.h" + +#include "base/logging.h" + +namespace base { + +bool NonThreadSafeImpl::CalledOnValidThread() const { + return thread_checker_.CalledOnValidThread(); +} + +NonThreadSafeImpl::~NonThreadSafeImpl() { + DCHECK(CalledOnValidThread()); +} + +void NonThreadSafeImpl::DetachFromThread() { + thread_checker_.DetachFromThread(); +} + +} // namespace base
diff --git a/src/base/threading/non_thread_safe_impl.h b/src/base/threading/non_thread_safe_impl.h new file mode 100644 index 0000000..a3a356d --- /dev/null +++ b/src/base/threading/non_thread_safe_impl.h
@@ -0,0 +1,39 @@ +// 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_THREADING_NON_THREAD_SAFE_IMPL_H_ +#define BASE_THREADING_NON_THREAD_SAFE_IMPL_H_ + +#include "base/base_export.h" +#include "base/threading/thread_checker_impl.h" + +namespace base { + +// Full implementation of NonThreadSafe, for debug mode or for occasional +// temporary use in release mode e.g. when you need to CHECK on a thread +// bug that only occurs in the wild. +// +// Note: You should almost always use the NonThreadSafe class to get +// the right version of the class for your build configuration. +class BASE_EXPORT NonThreadSafeImpl { + public: + bool CalledOnValidThread() const; + + protected: + ~NonThreadSafeImpl(); + + // Changes the thread that is checked for in CalledOnValidThread. The next + // call to CalledOnValidThread will attach this class to a new thread. It is + // up to the NonThreadSafe derived class to decide to expose this or not. + // This may be useful when an object may be created on one thread and then + // used exclusively on another thread. + void DetachFromThread(); + + private: + ThreadCheckerImpl thread_checker_; +}; + +} // namespace base + +#endif // BASE_THREADING_NON_THREAD_SAFE_IMPL_H_
diff --git a/src/base/threading/non_thread_safe_unittest.cc b/src/base/threading/non_thread_safe_unittest.cc new file mode 100644 index 0000000..ee31701 --- /dev/null +++ b/src/base/threading/non_thread_safe_unittest.cc
@@ -0,0 +1,166 @@ +// 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/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "base/threading/simple_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Duplicated from base/threading/non_thread_safe.h so that we can be +// good citizens there and undef the macro. +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) +#define ENABLE_NON_THREAD_SAFE 1 +#else +#define ENABLE_NON_THREAD_SAFE 0 +#endif + +namespace base { + +namespace { + +// Simple class to exersice the basics of NonThreadSafe. +// Both the destructor and DoStuff should verify that they were +// called on the same thread as the constructor. +class NonThreadSafeClass : public NonThreadSafe { + public: + NonThreadSafeClass() {} + + // Verifies that it was called on the same thread as the constructor. + void DoStuff() { + DCHECK(CalledOnValidThread()); + } + + void DetachFromThread() { + NonThreadSafe::DetachFromThread(); + } + + static void MethodOnDifferentThreadImpl(); + static void DestructorOnDifferentThreadImpl(); + + private: + DISALLOW_COPY_AND_ASSIGN(NonThreadSafeClass); +}; + +// Calls NonThreadSafeClass::DoStuff on another thread. +class CallDoStuffOnThread : public SimpleThread { + public: + CallDoStuffOnThread(NonThreadSafeClass* non_thread_safe_class) + : SimpleThread("call_do_stuff_on_thread"), + non_thread_safe_class_(non_thread_safe_class) { + } + + virtual void Run() OVERRIDE { + non_thread_safe_class_->DoStuff(); + } + + private: + NonThreadSafeClass* non_thread_safe_class_; + + DISALLOW_COPY_AND_ASSIGN(CallDoStuffOnThread); +}; + +// Deletes NonThreadSafeClass on a different thread. +class DeleteNonThreadSafeClassOnThread : public SimpleThread { + public: + DeleteNonThreadSafeClassOnThread(NonThreadSafeClass* non_thread_safe_class) + : SimpleThread("delete_non_thread_safe_class_on_thread"), + non_thread_safe_class_(non_thread_safe_class) { + } + + virtual void Run() OVERRIDE { + non_thread_safe_class_.reset(); + } + + private: + scoped_ptr<NonThreadSafeClass> non_thread_safe_class_; + + DISALLOW_COPY_AND_ASSIGN(DeleteNonThreadSafeClassOnThread); +}; + +} // namespace + +TEST(NonThreadSafeTest, CallsAllowedOnSameThread) { + scoped_ptr<NonThreadSafeClass> non_thread_safe_class( + new NonThreadSafeClass); + + // Verify that DoStuff doesn't assert. + non_thread_safe_class->DoStuff(); + + // Verify that the destructor doesn't assert. + non_thread_safe_class.reset(); +} + +TEST(NonThreadSafeTest, DetachThenDestructOnDifferentThread) { + scoped_ptr<NonThreadSafeClass> non_thread_safe_class( + new NonThreadSafeClass); + + // Verify that the destructor doesn't assert when called on a different thread + // after a detach. + non_thread_safe_class->DetachFromThread(); + DeleteNonThreadSafeClassOnThread delete_on_thread( + non_thread_safe_class.release()); + + delete_on_thread.Start(); + delete_on_thread.Join(); +} + +#if GTEST_HAS_DEATH_TEST || !ENABLE_NON_THREAD_SAFE + +void NonThreadSafeClass::MethodOnDifferentThreadImpl() { + scoped_ptr<NonThreadSafeClass> non_thread_safe_class( + new NonThreadSafeClass); + + // Verify that DoStuff asserts in debug builds only when called + // on a different thread. + CallDoStuffOnThread call_on_thread(non_thread_safe_class.get()); + + call_on_thread.Start(); + call_on_thread.Join(); +} + +#if ENABLE_NON_THREAD_SAFE +TEST(NonThreadSafeDeathTest, MethodNotAllowedOnDifferentThreadInDebug) { + ASSERT_DEATH({ + NonThreadSafeClass::MethodOnDifferentThreadImpl(); + }, ""); +} +#else +TEST(NonThreadSafeTest, MethodAllowedOnDifferentThreadInRelease) { + NonThreadSafeClass::MethodOnDifferentThreadImpl(); +} +#endif // ENABLE_NON_THREAD_SAFE + +void NonThreadSafeClass::DestructorOnDifferentThreadImpl() { + scoped_ptr<NonThreadSafeClass> non_thread_safe_class( + new NonThreadSafeClass); + + // Verify that the destructor asserts in debug builds only + // when called on a different thread. + DeleteNonThreadSafeClassOnThread delete_on_thread( + non_thread_safe_class.release()); + + delete_on_thread.Start(); + delete_on_thread.Join(); +} + +#if ENABLE_NON_THREAD_SAFE +TEST(NonThreadSafeDeathTest, DestructorNotAllowedOnDifferentThreadInDebug) { + ASSERT_DEATH({ + NonThreadSafeClass::DestructorOnDifferentThreadImpl(); + }, ""); +} +#else +TEST(NonThreadSafeTest, DestructorAllowedOnDifferentThreadInRelease) { + NonThreadSafeClass::DestructorOnDifferentThreadImpl(); +} +#endif // ENABLE_NON_THREAD_SAFE + +#endif // GTEST_HAS_DEATH_TEST || !ENABLE_NON_THREAD_SAFE + +// Just in case we ever get lumped together with other compilation units. +#undef ENABLE_NON_THREAD_SAFE + +} // namespace base
diff --git a/src/base/threading/platform_thread.h b/src/base/threading/platform_thread.h new file mode 100644 index 0000000..89452c1 --- /dev/null +++ b/src/base/threading/platform_thread.h
@@ -0,0 +1,181 @@ +// 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. + +// WARNING: You should *NOT* be using this class directly. PlatformThread is +// the low-level platform-specific abstraction to the OS's threading interface. +// You should instead be using a message-loop driven Thread, see thread.h. + +#ifndef BASE_THREADING_PLATFORM_THREAD_H_ +#define BASE_THREADING_PLATFORM_THREAD_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/time.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#elif defined(OS_POSIX) +#include <pthread.h> +#include <unistd.h> +#elif defined(OS_STARBOARD) +#include "starboard/thread.h" +#endif + +namespace base { + +// PlatformThreadHandle should not be assumed to be a numeric type, since the +// standard intends to allow pthread_t to be a structure. This means you +// should not initialize it to a value, like 0. If it's a member variable, the +// constructor can safely "value initialize" using () in the initializer list. +// +// ... Note that PlatformThreadHandle is assumed to be a scalar type below. +#if defined(OS_WIN) +typedef DWORD PlatformThreadId; +typedef void* PlatformThreadHandle; // HANDLE +const PlatformThreadHandle kNullThreadHandle = NULL; +const PlatformThreadId kInvalidThreadId = 0; +#elif defined(OS_POSIX) +typedef pthread_t PlatformThreadHandle; +const PlatformThreadHandle kNullThreadHandle = 0; +typedef pid_t PlatformThreadId; +const PlatformThreadId kInvalidThreadId = 0; +#if defined(COBALT) +typedef int ThreadAffinity; +const ThreadAffinity kNoThreadAffinity = -1; +#endif +#elif defined(OS_STARBOARD) +typedef SbThread PlatformThreadHandle; +const PlatformThreadHandle kNullThreadHandle = kSbThreadInvalid; +typedef SbThreadId PlatformThreadId; +const PlatformThreadId kInvalidThreadId = kSbThreadInvalidId; +typedef SbThreadAffinity ThreadAffinity; +const ThreadAffinity kNoThreadAffinity = kSbThreadNoAffinity; +#endif + +// Valid values for SetThreadPriority() +enum ThreadPriority { +#if defined(OS_STARBOARD) + // See the priorities defined in "starboard/thread.h" + + kThreadPriority_Default = kSbThreadNoPriority, + kThreadPriority_Lowest = kSbThreadPriorityLowest, + kThreadPriority_Low = kSbThreadPriorityLow, + kThreadPriority_Normal = kSbThreadPriorityNormal, + kThreadPriority_High = kSbThreadPriorityHigh, + kThreadPriority_Highest = kSbThreadPriorityHighest, + kThreadPriority_RealTime = kSbThreadPriorityRealTime, + kThreadPriority_RealtimeAudio = kThreadPriority_RealTime, +#elif defined(COBALT) + // Cobalt on non-Starboard platforms. + + kThreadPriority_Default = -1, + kThreadPriority_Lowest = 0, + kThreadPriority_Low, + kThreadPriority_Normal, + kThreadPriority_High, + kThreadPriority_Highest, + kThreadPriority_RealTime, + kThreadPriority_RealtimeAudio = kThreadPriority_RealTime, +#else + // The original Chromium priorities. + + kThreadPriority_Normal, + // Suitable for low-latency, glitch-resistant audio. + kThreadPriority_RealtimeAudio, +#endif +}; + +// A namespace for low-level thread functions. +class BASE_EXPORT PlatformThread { + public: + // Implement this interface to run code on a background thread. Your + // ThreadMain method will be called on the newly created thread. + class BASE_EXPORT Delegate { + public: + virtual void ThreadMain() = 0; + + protected: + virtual ~Delegate() {} + }; + +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) + struct PlatformThreadOptions { + // <= 0 indicates default stack size + int64_t stack_size; + + // The priority to start the thread at, or kThreadPriority_Default for the + // default priority assignment policy. + ThreadPriority priority; + + // The CPU index to pin the thread to, or kNoThreadAffinity for no affinity. + ThreadAffinity affinity; + }; +#endif + + // Gets the current thread id, which may be useful for logging purposes. + static PlatformThreadId CurrentId(); + + // Yield the current thread so another thread can be scheduled. + static void YieldCurrentThread(); + + // Sleeps for the specified duration. + static void Sleep(base::TimeDelta duration); + + // Sets the thread name visible to debuggers/tools. This has no effect + // otherwise. This name pointer is not copied internally. Thus, it must stay + // valid until the thread ends. + static void SetName(const char* name); + + // Gets the thread name, if previously set by SetName. + static const char* GetName(); + + // Creates a new thread. The |stack_size| parameter can be 0 to indicate + // that the default stack size should be used. Upon success, + // |*thread_handle| will be assigned a handle to the newly created thread, + // and |delegate|'s ThreadMain method will be executed on the newly created + // thread. + // NOTE: When you are done with the thread handle, you must call Join to + // release system resources associated with the thread. You must ensure that + // the Delegate object outlives the thread. + static bool Create(size_t stack_size, Delegate* delegate, + PlatformThreadHandle* thread_handle); + + // CreateWithPriority() does the same thing as Create() except the priority of + // the thread is set based on |priority|. Can be used in place of Create() + // followed by SetThreadPriority(). SetThreadPriority() has not been + // implemented on the Linux platform yet, this is the only way to get a high + // priority thread on Linux. + static bool CreateWithPriority(size_t stack_size, Delegate* delegate, + PlatformThreadHandle* thread_handle, + ThreadPriority priority); + + // CreateNonJoinable() does the same thing as Create() except the thread + // cannot be Join()'d. Therefore, it also does not output a + // PlatformThreadHandle. + static bool CreateNonJoinable(size_t stack_size, Delegate* delegate); + +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) + static bool CreateWithOptions(const PlatformThreadOptions& options, + Delegate* delegate, + PlatformThreadHandle* thread_handle); +#endif + + // Joins with a thread created via the Create function. This function blocks + // the caller until the designated thread exits. This will invalidate + // |thread_handle|. + static void Join(PlatformThreadHandle thread_handle); + + // Sets the priority of the thread specified in |handle| to |priority|. + // This does not work on Linux, use CreateWithPriority() instead. + static void SetThreadPriority(PlatformThreadHandle handle, + ThreadPriority priority); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(PlatformThread); +}; + +} // namespace base + +#endif // BASE_THREADING_PLATFORM_THREAD_H_
diff --git a/src/base/threading/platform_thread_mac.mm b/src/base/threading/platform_thread_mac.mm new file mode 100644 index 0000000..96c6720 --- /dev/null +++ b/src/base/threading/platform_thread_mac.mm
@@ -0,0 +1,190 @@ +// 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/threading/platform_thread.h" + +#import <Foundation/Foundation.h> +#include <dlfcn.h> +#include <mach/mach.h> +#include <mach/mach_time.h> +#include <mach/thread_policy.h> + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/thread_local.h" +#include "base/tracked_objects.h" + +namespace base { + +namespace { + +LazyInstance<ThreadLocalPointer<char> >::Leaky + current_thread_name = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// If Cocoa is to be used on more than one thread, it must know that the +// application is multithreaded. Since it's possible to enter Cocoa code +// from threads created by pthread_thread_create, Cocoa won't necessarily +// be aware that the application is multithreaded. Spawning an NSThread is +// enough to get Cocoa to set up for multithreaded operation, so this is done +// if necessary before pthread_thread_create spawns any threads. +// +// http://developer.apple.com/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/chapter_4_section_4.html +void InitThreading() { + static BOOL multithreaded = [NSThread isMultiThreaded]; + if (!multithreaded) { + // +[NSObject class] is idempotent. + [NSThread detachNewThreadSelector:@selector(class) + toTarget:[NSObject class] + withObject:nil]; + multithreaded = YES; + + DCHECK([NSThread isMultiThreaded]); + } +} + +// static +void PlatformThread::SetName(const char* name) { + current_thread_name.Pointer()->Set(const_cast<char*>(name)); + tracked_objects::ThreadData::InitializeThreadContext(name); + + // pthread_setname_np is only available in 10.6 or later, so test + // for it at runtime. + int (*dynamic_pthread_setname_np)(const char*); + *reinterpret_cast<void**>(&dynamic_pthread_setname_np) = + dlsym(RTLD_DEFAULT, "pthread_setname_np"); + if (!dynamic_pthread_setname_np) + return; + + // Mac OS X does not expose the length limit of the name, so + // hardcode it. + const int kMaxNameLength = 63; + std::string shortened_name = std::string(name).substr(0, kMaxNameLength); + // pthread_setname() fails (harmlessly) in the sandbox, ignore when it does. + // See http://crbug.com/47058 + dynamic_pthread_setname_np(shortened_name.c_str()); +} + +// static +const char* PlatformThread::GetName() { + return current_thread_name.Pointer()->Get(); +} + +namespace { + +void SetPriorityNormal(mach_port_t mach_thread_id) { + // Make thread standard policy. + // Please note that this call could fail in rare cases depending + // on runtime conditions. + thread_standard_policy policy; + kern_return_t result = thread_policy_set(mach_thread_id, + THREAD_STANDARD_POLICY, + (thread_policy_t)&policy, + THREAD_STANDARD_POLICY_COUNT); + + if (result != KERN_SUCCESS) + DVLOG(1) << "thread_policy_set() failure: " << result; +} + +// Enables time-contraint policy and priority suitable for low-latency, +// glitch-resistant audio. +void SetPriorityRealtimeAudio(mach_port_t mach_thread_id) { + kern_return_t result; + + // Increase thread priority to real-time. + + // Please note that the thread_policy_set() calls may fail in + // rare cases if the kernel decides the system is under heavy load + // and is unable to handle boosting the thread priority. + // In these cases we just return early and go on with life. + + // Make thread fixed priority. + thread_extended_policy_data_t policy; + policy.timeshare = 0; // Set to 1 for a non-fixed thread. + result = thread_policy_set(mach_thread_id, + THREAD_EXTENDED_POLICY, + (thread_policy_t)&policy, + THREAD_EXTENDED_POLICY_COUNT); + if (result != KERN_SUCCESS) { + DVLOG(1) << "thread_policy_set() failure: " << result; + return; + } + + // Set to relatively high priority. + thread_precedence_policy_data_t precedence; + precedence.importance = 63; + result = thread_policy_set(mach_thread_id, + THREAD_PRECEDENCE_POLICY, + (thread_policy_t)&precedence, + THREAD_PRECEDENCE_POLICY_COUNT); + if (result != KERN_SUCCESS) { + DVLOG(1) << "thread_policy_set() failure: " << result; + return; + } + + // Most important, set real-time constraints. + + // Define the guaranteed and max fraction of time for the audio thread. + // These "duty cycle" values can range from 0 to 1. A value of 0.5 + // means the scheduler would give half the time to the thread. + // These values have empirically been found to yield good behavior. + // Good means that audio performance is high and other threads won't starve. + const double kGuaranteedAudioDutyCycle = 0.75; + const double kMaxAudioDutyCycle = 0.85; + + // Define constants determining how much time the audio thread can + // use in a given time quantum. All times are in milliseconds. + + // About 128 frames @44.1KHz + const double kTimeQuantum = 2.9; + + // Time guaranteed each quantum. + const double kAudioTimeNeeded = kGuaranteedAudioDutyCycle * kTimeQuantum; + + // Maximum time each quantum. + const double kMaxTimeAllowed = kMaxAudioDutyCycle * kTimeQuantum; + + // Get the conversion factor from milliseconds to absolute time + // which is what the time-constraints call needs. + mach_timebase_info_data_t tb_info; + mach_timebase_info(&tb_info); + double ms_to_abs_time = + ((double)tb_info.denom / (double)tb_info.numer) * 1000000; + + thread_time_constraint_policy_data_t time_constraints; + time_constraints.period = kTimeQuantum * ms_to_abs_time; + time_constraints.computation = kAudioTimeNeeded * ms_to_abs_time; + time_constraints.constraint = kMaxTimeAllowed * ms_to_abs_time; + time_constraints.preemptible = 0; + + result = thread_policy_set(mach_thread_id, + THREAD_TIME_CONSTRAINT_POLICY, + (thread_policy_t)&time_constraints, + THREAD_TIME_CONSTRAINT_POLICY_COUNT); + if (result != KERN_SUCCESS) + DVLOG(1) << "thread_policy_set() failure: " << result; + + return; +} + +} // anonymous namespace + +// static +void PlatformThread::SetThreadPriority(PlatformThreadHandle handle, + ThreadPriority priority) { + // Convert from pthread_t to mach thread identifier. + mach_port_t mach_thread_id = pthread_mach_thread_np(handle); + + switch (priority) { + case kThreadPriority_Normal: + SetPriorityNormal(mach_thread_id); + break; + case kThreadPriority_RealtimeAudio: + SetPriorityRealtimeAudio(mach_thread_id); + break; + } +} + +} // namespace base
diff --git a/src/base/threading/platform_thread_posix.cc b/src/base/threading/platform_thread_posix.cc new file mode 100644 index 0000000..038ab04 --- /dev/null +++ b/src/base/threading/platform_thread_posix.cc
@@ -0,0 +1,286 @@ +// 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/threading/platform_thread.h" + +#include <errno.h> +#include <sched.h> + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/safe_strerror_posix.h" +#include "base/threading/thread_local.h" +#include "base/threading/thread_restrictions.h" +#include "base/tracked_objects.h" + +#if defined(OS_MACOSX) +#include <sys/resource.h> +#include <algorithm> +#endif + +#if defined(OS_LINUX) +#include <sys/prctl.h> +#include <sys/resource.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <unistd.h> +#endif + +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) +#include "base/android/jni_android.h" +#endif + +// TODO(bbudge) Use time.h when NaCl toolchain supports _POSIX_TIMERS +#if defined(OS_NACL) +#include <sys/nacl_syscalls.h> +#endif + +namespace base { + +#if defined(OS_MACOSX) +void InitThreading(); +#endif + +namespace { + +#if !defined(OS_MACOSX) +// Mac name code is in in platform_thread_mac.mm. +LazyInstance<ThreadLocalPointer<char> >::Leaky + current_thread_name = LAZY_INSTANCE_INITIALIZER; +#endif + +struct ThreadParams { + PlatformThread::Delegate* delegate; + bool joinable; +}; + +void* ThreadFunc(void* params) { + ThreadParams* thread_params = static_cast<ThreadParams*>(params); + PlatformThread::Delegate* delegate = thread_params->delegate; + if (!thread_params->joinable) + base::ThreadRestrictions::SetSingletonAllowed(false); + delete thread_params; + delegate->ThreadMain(); +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) + base::android::DetachFromVM(); +#endif + return NULL; +} + +bool CreateThread(size_t stack_size, bool joinable, + PlatformThread::Delegate* delegate, + PlatformThreadHandle* thread_handle, + ThreadPriority priority) { +#if defined(OS_MACOSX) + base::InitThreading(); +#endif // OS_MACOSX + + bool success = false; + pthread_attr_t attributes; + pthread_attr_init(&attributes); + + // Pthreads are joinable by default, so only specify the detached attribute if + // the thread should be non-joinable. + if (!joinable) { + pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED); + } + +#if defined(OS_MACOSX) && !defined(OS_IOS) + // The Mac OS X default for a pthread stack size is 512kB. + // Libc-594.1.4/pthreads/pthread.c's pthread_attr_init uses + // DEFAULT_STACK_SIZE for this purpose. + // + // 512kB isn't quite generous enough for some deeply recursive threads that + // otherwise request the default stack size by specifying 0. Here, adopt + // glibc's behavior as on Linux, which is to use the current stack size + // limit (ulimit -s) as the default stack size. See + // glibc-2.11.1/nptl/nptl-init.c's __pthread_initialize_minimal_internal. To + // avoid setting the limit below the Mac OS X default or the minimum usable + // stack size, these values are also considered. If any of these values + // can't be determined, or if stack size is unlimited (ulimit -s unlimited), + // stack_size is left at 0 to get the system default. + // + // Mac OS X normally only applies ulimit -s to the main thread stack. On + // contemporary OS X and Linux systems alike, this value is generally 8MB + // or in that neighborhood. + if (stack_size == 0) { + size_t default_stack_size; + struct rlimit stack_rlimit; + if (pthread_attr_getstacksize(&attributes, &default_stack_size) == 0 && + getrlimit(RLIMIT_STACK, &stack_rlimit) == 0 && + stack_rlimit.rlim_cur != RLIM_INFINITY) { + stack_size = std::max(std::max(default_stack_size, + static_cast<size_t>(PTHREAD_STACK_MIN)), + static_cast<size_t>(stack_rlimit.rlim_cur)); + } + } +#endif // OS_MACOSX && !OS_IOS + + if (stack_size > 0) + pthread_attr_setstacksize(&attributes, stack_size); + + ThreadParams* params = new ThreadParams; + params->delegate = delegate; + params->joinable = joinable; + success = !pthread_create(thread_handle, &attributes, ThreadFunc, params); + + if (priority != kThreadPriority_Normal) { +#if defined(OS_LINUX) + if (priority == kThreadPriority_RealtimeAudio) { + // Linux isn't posix compliant with setpriority(2), it will set a thread + // priority if it is passed a tid, not affecting the rest of the threads + // in the process. Setting this priority will only succeed if the user + // has been granted permission to adjust nice values on the system. + const int kNiceSetting = -10; + if (setpriority(PRIO_PROCESS, PlatformThread::CurrentId(), kNiceSetting)) + DVLOG(1) << "Failed to set nice value of thread to " << kNiceSetting; + } else { + NOTREACHED() << "Unknown thread priority."; + } +#else + PlatformThread::SetThreadPriority(*thread_handle, priority); +#endif + } + + pthread_attr_destroy(&attributes); + if (!success) + delete params; + return success; +} + +} // namespace + +// static +PlatformThreadId PlatformThread::CurrentId() { + // Pthreads doesn't have the concept of a thread ID, so we have to reach down + // into the kernel. +#if defined(OS_LINUX) + return syscall(__NR_gettid); +#elif defined(OS_ANDROID) || defined(__LB_ANDROID__) + return gettid(); +#elif defined(OS_SOLARIS) + return pthread_self(); +#elif defined(OS_NACL) && defined(__GLIBC__) + return pthread_self(); +#elif defined(OS_NACL) && !defined(__GLIBC__) + // Pointers are 32-bits in NaCl. + return reinterpret_cast<int32>(pthread_self()); +#elif defined(OS_POSIX) + return reinterpret_cast<int64>(pthread_self()); +#endif +} + +// static +void PlatformThread::YieldCurrentThread() { + sched_yield(); +} + +// static +void PlatformThread::Sleep(TimeDelta duration) { + struct timespec sleep_time, remaining; + + // Break the duration into seconds and nanoseconds. + // NOTE: TimeDelta's microseconds are int64s while timespec's + // nanoseconds are longs, so this unpacking must prevent overflow. + sleep_time.tv_sec = duration.InSeconds(); + duration -= TimeDelta::FromSeconds(sleep_time.tv_sec); + sleep_time.tv_nsec = duration.InMicroseconds() * 1000; // nanoseconds + + while (nanosleep(&sleep_time, &remaining) == -1 && errno == EINTR) + sleep_time = remaining; +} + +#if defined(OS_LINUX) +// static +void PlatformThread::SetName(const char* name) { + // have to cast away const because ThreadLocalPointer does not support const + // void* + current_thread_name.Pointer()->Set(const_cast<char*>(name)); + tracked_objects::ThreadData::InitializeThreadContext(name); + + // On linux we can get the thread names to show up in the debugger by setting + // the process name for the LWP. We don't want to do this for the main + // thread because that would rename the process, causing tools like killall + // to stop working. + if (PlatformThread::CurrentId() == getpid()) + return; + + // http://0pointer.de/blog/projects/name-your-threads.html + // Set the name for the LWP (which gets truncated to 15 characters). + // Note that glibc also has a 'pthread_setname_np' api, but it may not be + // available everywhere and it's only benefit over using prctl directly is + // that it can set the name of threads other than the current thread. + int err = prctl(PR_SET_NAME, name); + // We expect EPERM failures in sandboxed processes, just ignore those. + if (err < 0 && errno != EPERM) + DPLOG(ERROR) << "prctl(PR_SET_NAME)"; +} +#elif defined(OS_MACOSX) +// Mac is implemented in platform_thread_mac.mm. +#else +// static +void PlatformThread::SetName(const char* name) { + // have to cast away const because ThreadLocalPointer does not support const + // void* + current_thread_name.Pointer()->Set(const_cast<char*>(name)); + tracked_objects::ThreadData::InitializeThreadContext(name); + + // (This should be relatively simple to implement for the BSDs; I + // just don't have one handy to test the code on.) +} +#endif // defined(OS_LINUX) + + +#if !defined(OS_MACOSX) +// Mac is implemented in platform_thread_mac.mm. +// static +const char* PlatformThread::GetName() { + return current_thread_name.Pointer()->Get(); +} +#endif + +// static +bool PlatformThread::Create(size_t stack_size, Delegate* delegate, + PlatformThreadHandle* thread_handle) { + return CreateThread(stack_size, true /* joinable thread */, + delegate, thread_handle, kThreadPriority_Normal); +} + +// static +bool PlatformThread::CreateWithPriority(size_t stack_size, Delegate* delegate, + PlatformThreadHandle* thread_handle, + ThreadPriority priority) { + return CreateThread(stack_size, true, // joinable thread + delegate, thread_handle, priority); +} + +// static +bool PlatformThread::CreateNonJoinable(size_t stack_size, Delegate* delegate) { + PlatformThreadHandle unused; + + bool result = CreateThread(stack_size, false /* non-joinable thread */, + delegate, &unused, kThreadPriority_Normal); + return result; +} + +// static +void PlatformThread::Join(PlatformThreadHandle thread_handle) { + // Joining another thread may block the current thread for a long time, since + // the thread referred to by |thread_handle| may still be running long-lived / + // blocking tasks. + base::ThreadRestrictions::AssertIOAllowed(); + pthread_join(thread_handle, NULL); +} + +#if !defined(OS_MACOSX) +// Mac OS X uses lower-level mach APIs. + +// static +void PlatformThread::SetThreadPriority(PlatformThreadHandle, ThreadPriority) { + // TODO(crogers): Implement, see http://crbug.com/116172 +} +#endif + +} // namespace base
diff --git a/src/base/threading/platform_thread_starboard.cc b/src/base/threading/platform_thread_starboard.cc new file mode 100644 index 0000000..20f5cdf --- /dev/null +++ b/src/base/threading/platform_thread_starboard.cc
@@ -0,0 +1,147 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/threading/platform_thread.h" + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/thread_local.h" +#include "base/threading/thread_restrictions.h" +#include "base/tracked_objects.h" +#include "starboard/thread.h" + +namespace base { + +namespace { + +LazyInstance< ThreadLocalPointer<char> >::Leaky current_thread_name = + LAZY_INSTANCE_INITIALIZER; + +struct ThreadParams { + PlatformThread::Delegate* delegate; + bool joinable; +}; + +void* ThreadFunc(void* params) { + ThreadParams* thread_params = static_cast<ThreadParams*>(params); + PlatformThread::Delegate* delegate = thread_params->delegate; + if (!thread_params->joinable) { + base::ThreadRestrictions::SetSingletonAllowed(false); + } + + delete thread_params; + delegate->ThreadMain(); + return NULL; +} + +bool CreateThread(size_t stack_size, + SbThreadPriority priority, + SbThreadAffinity affinity, + bool joinable, + const char* name, + PlatformThread::Delegate* delegate, + PlatformThreadHandle* thread_handle) { + ThreadParams* params = new ThreadParams; + params->delegate = delegate; + params->joinable = joinable; + + SbThread thread = SbThreadCreate(stack_size, priority, affinity, joinable, + name, ThreadFunc, params); + if (SbThreadIsValid(thread)) { + if (thread_handle) { + *thread_handle = thread; + } + + return true; + } + + return false; +} + +inline SbThreadPriority toSbPriority(ThreadPriority priority) { + return static_cast<SbThreadPriority>(priority); +} + +} // namespace + +// static +PlatformThreadId PlatformThread::CurrentId() { + return SbThreadGetId(); +} + +// static +void PlatformThread::YieldCurrentThread() { + SbThreadYield(); +} + +// static +void PlatformThread::Sleep(TimeDelta duration) { + SbThreadSleep(duration.ToSbTime()); +} + +// static +void PlatformThread::SetName(const char* name) { + // have to cast away const because ThreadLocalPointer does not support const + // void* + current_thread_name.Pointer()->Set(const_cast<char*>(name)); + SbThreadSetName(name); +} + +// static +const char* PlatformThread::GetName() { + return current_thread_name.Pointer()->Get(); +} + +// static +bool PlatformThread::Create(size_t stack_size, Delegate* delegate, + PlatformThreadHandle* thread_handle) { + return CreateThread(stack_size, kSbThreadNoPriority, kSbThreadNoAffinity, + true /* joinable thread */, NULL, delegate, + thread_handle); +} + +// static +bool PlatformThread::CreateWithPriority(size_t stack_size, Delegate* delegate, + PlatformThreadHandle* thread_handle, + ThreadPriority priority) { + return CreateThread(stack_size, toSbPriority(priority), kSbThreadNoAffinity, + true /* joinable thread */, NULL, delegate, + thread_handle); +} + +// static +bool PlatformThread::CreateNonJoinable(size_t stack_size, Delegate* delegate) { + return CreateThread(stack_size, kSbThreadNoPriority, kSbThreadNoAffinity, + false /* joinable thread */, NULL, delegate, NULL); +} + +// static +void PlatformThread::Join(PlatformThreadHandle thread_handle) { + // Joining another thread may block the current thread for a long time, since + // the thread referred to by |thread_handle| may still be running long-lived / + // blocking tasks. + base::ThreadRestrictions::AssertIOAllowed(); + SbThreadJoin(thread_handle, NULL); +} + +// static +bool PlatformThread::CreateWithOptions(const PlatformThreadOptions& options, + Delegate* delegate, + PlatformThreadHandle* thread_handle) { + return CreateThread(options.stack_size, toSbPriority(options.priority), + options.affinity, true /* joinable */, NULL, delegate, + thread_handle); +} + +} // namespace base
diff --git a/src/base/threading/platform_thread_unittest.cc b/src/base/threading/platform_thread_unittest.cc new file mode 100644 index 0000000..e37709a --- /dev/null +++ b/src/base/threading/platform_thread_unittest.cc
@@ -0,0 +1,121 @@ +// 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/compiler_specific.h" +#include "base/threading/platform_thread.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +// Trivial tests that thread runs and doesn't crash on create and join --------- + +class TrivialThread : public PlatformThread::Delegate { + public: + TrivialThread() : did_run_(false) {} + + virtual void ThreadMain() OVERRIDE { + did_run_ = true; + } + + bool did_run() const { return did_run_; } + + private: + bool did_run_; + + DISALLOW_COPY_AND_ASSIGN(TrivialThread); +}; + +TEST(PlatformThreadTest, Trivial) { + TrivialThread thread; + PlatformThreadHandle handle = kNullThreadHandle; + + ASSERT_FALSE(thread.did_run()); + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + PlatformThread::Join(handle); + ASSERT_TRUE(thread.did_run()); +} + +TEST(PlatformThreadTest, TrivialTimesTen) { + TrivialThread thread[10]; + PlatformThreadHandle handle[arraysize(thread)]; + + for (size_t n = 0; n < arraysize(thread); n++) + ASSERT_FALSE(thread[n].did_run()); + for (size_t n = 0; n < arraysize(thread); n++) + ASSERT_TRUE(PlatformThread::Create(0, &thread[n], &handle[n])); + for (size_t n = 0; n < arraysize(thread); n++) + PlatformThread::Join(handle[n]); + for (size_t n = 0; n < arraysize(thread); n++) + ASSERT_TRUE(thread[n].did_run()); +} + +// Tests of basic thread functions --------------------------------------------- + +class FunctionTestThread : public TrivialThread { + public: + FunctionTestThread() : thread_id_(0) {} + + virtual void ThreadMain() OVERRIDE { + thread_id_ = PlatformThread::CurrentId(); + PlatformThread::YieldCurrentThread(); + PlatformThread::Sleep(TimeDelta::FromMilliseconds(50)); + + // Make sure that the thread ID is the same across calls. + EXPECT_EQ(thread_id_, PlatformThread::CurrentId()); + + TrivialThread::ThreadMain(); + } + + PlatformThreadId thread_id() const { return thread_id_; } + + private: + PlatformThreadId thread_id_; + + DISALLOW_COPY_AND_ASSIGN(FunctionTestThread); +}; + +TEST(PlatformThreadTest, Function) { + PlatformThreadId main_thread_id = PlatformThread::CurrentId(); + + FunctionTestThread thread; + PlatformThreadHandle handle = kNullThreadHandle; + + ASSERT_FALSE(thread.did_run()); + ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); + PlatformThread::Join(handle); + ASSERT_TRUE(thread.did_run()); + EXPECT_NE(thread.thread_id(), main_thread_id); + + // Make sure that the thread ID is the same across calls. + EXPECT_EQ(main_thread_id, PlatformThread::CurrentId()); +} + +TEST(PlatformThreadTest, FunctionTimesTen) { + PlatformThreadId main_thread_id = PlatformThread::CurrentId(); + + FunctionTestThread thread[10]; + PlatformThreadHandle handle[arraysize(thread)]; + + for (size_t n = 0; n < arraysize(thread); n++) + ASSERT_FALSE(thread[n].did_run()); + for (size_t n = 0; n < arraysize(thread); n++) + ASSERT_TRUE(PlatformThread::Create(0, &thread[n], &handle[n])); + for (size_t n = 0; n < arraysize(thread); n++) + PlatformThread::Join(handle[n]); + for (size_t n = 0; n < arraysize(thread); n++) { + ASSERT_TRUE(thread[n].did_run()); + EXPECT_NE(thread[n].thread_id(), main_thread_id); + + // Make sure no two threads get the same ID. + for (size_t i = 0; i < n; ++i) { + EXPECT_NE(thread[i].thread_id(), thread[n].thread_id()); + } + } + + // Make sure that the thread ID is the same across calls. + EXPECT_EQ(main_thread_id, PlatformThread::CurrentId()); +} + +} // namespace base
diff --git a/src/base/threading/platform_thread_win.cc b/src/base/threading/platform_thread_win.cc new file mode 100644 index 0000000..82981ad --- /dev/null +++ b/src/base/threading/platform_thread_win.cc
@@ -0,0 +1,207 @@ +// 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/threading/platform_thread.h" + +#include "base/debug/alias.h" +#include "base/debug/profiler.h" +#include "base/logging.h" +#include "base/threading/thread_local.h" +#include "base/threading/thread_restrictions.h" +#include "base/tracked_objects.h" + +#include "base/win/windows_version.h" + +namespace base { + +namespace { + +static ThreadLocalPointer<char> current_thread_name; + +// The information on how to set the thread name comes from +// a MSDN article: http://msdn2.microsoft.com/en-us/library/xcb2z8hs.aspx +const DWORD kVCThreadNameException = 0x406D1388; + +typedef struct tagTHREADNAME_INFO { + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. +} THREADNAME_INFO; + +// This function has try handling, so it is separated out of its caller. +void SetNameInternal(PlatformThreadId thread_id, const char* name) { + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = thread_id; + info.dwFlags = 0; + + __try { + RaiseException(kVCThreadNameException, 0, sizeof(info)/sizeof(DWORD), + reinterpret_cast<DWORD_PTR*>(&info)); + } __except(EXCEPTION_CONTINUE_EXECUTION) { + } +} + +struct ThreadParams { + PlatformThread::Delegate* delegate; + bool joinable; +}; + +DWORD __stdcall ThreadFunc(void* params) { + ThreadParams* thread_params = static_cast<ThreadParams*>(params); + PlatformThread::Delegate* delegate = thread_params->delegate; + if (!thread_params->joinable) + base::ThreadRestrictions::SetSingletonAllowed(false); + delete thread_params; + delegate->ThreadMain(); + return NULL; +} + +// CreateThreadInternal() matches PlatformThread::Create(), except that +// |out_thread_handle| may be NULL, in which case a non-joinable thread is +// created. +bool CreateThreadInternal(size_t stack_size, + PlatformThread::Delegate* delegate, + PlatformThreadHandle* out_thread_handle) { + PlatformThreadHandle thread_handle; + unsigned int flags = 0; + if (stack_size > 0 && base::win::GetVersion() >= base::win::VERSION_XP) { + flags = STACK_SIZE_PARAM_IS_A_RESERVATION; + } else { + stack_size = 0; + } + + ThreadParams* params = new ThreadParams; + params->delegate = delegate; + params->joinable = out_thread_handle != NULL; + + // Using CreateThread here vs _beginthreadex makes thread creation a bit + // faster and doesn't require the loader lock to be available. Our code will + // have to work running on CreateThread() threads anyway, since we run code + // on the Windows thread pool, etc. For some background on the difference: + // http://www.microsoft.com/msj/1099/win32/win321099.aspx + thread_handle = CreateThread( + NULL, stack_size, ThreadFunc, params, flags, NULL); + if (!thread_handle) { + delete params; + return false; + } + + if (out_thread_handle) + *out_thread_handle = thread_handle; + else + CloseHandle(thread_handle); + return true; +} + +} // namespace + +// static +PlatformThreadId PlatformThread::CurrentId() { + return GetCurrentThreadId(); +} + +// static +void PlatformThread::YieldCurrentThread() { + ::Sleep(0); +} + +// static +void PlatformThread::Sleep(TimeDelta duration) { + ::Sleep(duration.InMillisecondsRoundedUp()); +} + +// static +void PlatformThread::SetName(const char* name) { + current_thread_name.Set(const_cast<char*>(name)); + + // On Windows only, we don't need to tell the profiler about the "BrokerEvent" + // thread, as it exists only in the chrome.exe image, and never spawns or runs + // tasks (items which could be profiled). This test avoids the notification, + // which would also (as a side effect) initialize the profiler in this unused + // context, including setting up thread local storage, etc. The performance + // impact is not terrible, but there is no reason to do initialize it. + if (0 != strcmp(name, "BrokerEvent")) + tracked_objects::ThreadData::InitializeThreadContext(name); + + // The debugger needs to be around to catch the name in the exception. If + // there isn't a debugger, we are just needlessly throwing an exception. + // If this image file is instrumented, we raise the exception anyway + // to provide the profiler with human-readable thread names. + if (!::IsDebuggerPresent() && !base::debug::IsBinaryInstrumented()) + return; + + SetNameInternal(CurrentId(), name); +} + +// static +const char* PlatformThread::GetName() { + return current_thread_name.Get(); +} + +// static +bool PlatformThread::Create(size_t stack_size, Delegate* delegate, + PlatformThreadHandle* thread_handle) { + DCHECK(thread_handle); + return CreateThreadInternal(stack_size, delegate, thread_handle); +} + +// static +bool PlatformThread::CreateWithPriority(size_t stack_size, Delegate* delegate, + PlatformThreadHandle* thread_handle, + ThreadPriority priority) { + bool result = Create(stack_size, delegate, thread_handle); + if (result) + SetThreadPriority(*thread_handle, priority); + return result; +} + +// static +bool PlatformThread::CreateNonJoinable(size_t stack_size, Delegate* delegate) { + return CreateThreadInternal(stack_size, delegate, NULL); +} + +// static +void PlatformThread::Join(PlatformThreadHandle thread_handle) { + DCHECK(thread_handle); + // TODO(willchan): Enable this check once I can get it to work for Windows + // shutdown. + // Joining another thread may block the current thread for a long time, since + // the thread referred to by |thread_handle| may still be running long-lived / + // blocking tasks. +#if 0 + base::ThreadRestrictions::AssertIOAllowed(); +#endif + + // Wait for the thread to exit. It should already have terminated but make + // sure this assumption is valid. + DWORD result = WaitForSingleObject(thread_handle, INFINITE); + if (result != WAIT_OBJECT_0) { + // Debug info for bug 127931. + DWORD error = GetLastError(); + debug::Alias(&error); + debug::Alias(&result); + debug::Alias(&thread_handle); + CHECK(false); + } + + CloseHandle(thread_handle); +} + +// static +void PlatformThread::SetThreadPriority(PlatformThreadHandle handle, + ThreadPriority priority) { + switch (priority) { + case kThreadPriority_Normal: + ::SetThreadPriority(handle, THREAD_PRIORITY_NORMAL); + break; + case kThreadPriority_RealtimeAudio: + ::SetThreadPriority(handle, THREAD_PRIORITY_TIME_CRITICAL); + break; + } +} + +} // namespace base
diff --git a/src/base/threading/post_task_and_reply_impl.cc b/src/base/threading/post_task_and_reply_impl.cc new file mode 100644 index 0000000..192198b --- /dev/null +++ b/src/base/threading/post_task_and_reply_impl.cc
@@ -0,0 +1,90 @@ +// 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/threading/post_task_and_reply_impl.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop_proxy.h" + +namespace base { + +namespace { + +// This relay class remembers the MessageLoop that it was created on, and +// ensures that both the |task| and |reply| Closures are deleted on this same +// thread. Also, |task| is guaranteed to be deleted before |reply| is run or +// deleted. +// +// If this is not possible because the originating MessageLoop is no longer +// available, the the |task| and |reply| Closures are leaked. Leaking is +// considered preferable to having a thread-safetey violations caused by +// invoking the Closure destructor on the wrong thread. +class PostTaskAndReplyRelay { + public: + PostTaskAndReplyRelay(const tracked_objects::Location& from_here, + const Closure& task, const Closure& reply) + : from_here_(from_here), + origin_loop_(MessageLoopProxy::current()) { + task_ = task; + reply_ = reply; + } + + ~PostTaskAndReplyRelay() { + DCHECK(origin_loop_->BelongsToCurrentThread()); + task_.Reset(); + reply_.Reset(); + } + + void Run() { + task_.Run(); + origin_loop_->PostTask( + from_here_, + Bind(&PostTaskAndReplyRelay::RunReplyAndSelfDestruct, + base::Unretained(this))); + } + + private: + void RunReplyAndSelfDestruct() { + DCHECK(origin_loop_->BelongsToCurrentThread()); + + // Force |task_| to be released before |reply_| is to ensure that no one + // accidentally depends on |task_| keeping one of its arguments alive while + // |reply_| is executing. + task_.Reset(); + + reply_.Run(); + + // Cue mission impossible theme. + delete this; + } + + tracked_objects::Location from_here_; + scoped_refptr<MessageLoopProxy> origin_loop_; + Closure reply_; + Closure task_; +}; + +} // namespace + +namespace internal { + +bool PostTaskAndReplyImpl::PostTaskAndReply( + const tracked_objects::Location& from_here, + const Closure& task, + const Closure& reply) { + PostTaskAndReplyRelay* relay = + new PostTaskAndReplyRelay(from_here, task, reply); + if (!PostTask(from_here, Bind(&PostTaskAndReplyRelay::Run, + Unretained(relay)))) { + delete relay; + return false; + } + + return true; +} + +} // namespace internal + +} // namespace base
diff --git a/src/base/threading/post_task_and_reply_impl.h b/src/base/threading/post_task_and_reply_impl.h new file mode 100644 index 0000000..076a46d --- /dev/null +++ b/src/base/threading/post_task_and_reply_impl.h
@@ -0,0 +1,42 @@ +// 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 contains the implementation shared by +// MessageLoopProxy::PostTaskAndReply and WorkerPool::PostTaskAndReply. + +#ifndef BASE_THREADING_POST_TASK_AND_REPLY_IMPL_H_ +#define BASE_THREADING_POST_TASK_AND_REPLY_IMPL_H_ + +#include "base/callback_forward.h" +#include "base/location.h" + +namespace base { +namespace internal { + +// Inherit from this in a class that implements PostTask appropriately +// for sending to a destination thread. +// +// Note that 'reply' will always get posted back to your current +// MessageLoop. +// +// If you're looking for a concrete implementation of +// PostTaskAndReply, you probably want base::MessageLoopProxy, or you +// may want base::WorkerPool. +class PostTaskAndReplyImpl { + public: + // Implementation for MessageLoopProxy::PostTaskAndReply and + // WorkerPool::PostTaskAndReply. + bool PostTaskAndReply(const tracked_objects::Location& from_here, + const Closure& task, + const Closure& reply); + + private: + virtual bool PostTask(const tracked_objects::Location& from_here, + const Closure& task) = 0; +}; + +} // namespace internal +} // namespace base + +#endif // BASE_THREADING_POST_TASK_AND_REPLY_IMPL_H_
diff --git a/src/base/threading/sequenced_worker_pool.cc b/src/base/threading/sequenced_worker_pool.cc new file mode 100644 index 0000000..56f908b --- /dev/null +++ b/src/base/threading/sequenced_worker_pool.cc
@@ -0,0 +1,1122 @@ +// 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/threading/sequenced_worker_pool.h" + +#include <list> +#include <map> +#include <set> +#include <utility> +#include <vector> + +#include "base/atomicops.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/critical_closure.h" +#include "base/debug/trace_event.h" +#include "base/logging.h" +#include "base/memory/linked_ptr.h" +#include "base/message_loop_proxy.h" +#include "base/metrics/histogram.h" +#include "base/stl_util.h" +#include "base/stringprintf.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/threading/platform_thread.h" +#include "base/threading/simple_thread.h" +#include "base/threading/thread_restrictions.h" +#include "base/time.h" +#include "base/tracked_objects.h" + +#if defined(OS_MACOSX) +#include "base/mac/scoped_nsautorelease_pool.h" +#endif + +namespace base { + +namespace { + +struct SequencedTask : public TrackingInfo { + SequencedTask() + : sequence_token_id(0), + trace_id(0), + sequence_task_number(0), + shutdown_behavior(SequencedWorkerPool::BLOCK_SHUTDOWN) {} + + explicit SequencedTask(const tracked_objects::Location& from_here) + : base::TrackingInfo(from_here, TimeTicks()), + sequence_token_id(0), + trace_id(0), + sequence_task_number(0), + shutdown_behavior(SequencedWorkerPool::BLOCK_SHUTDOWN) {} + + ~SequencedTask() {} + + int sequence_token_id; + int trace_id; + int64 sequence_task_number; + SequencedWorkerPool::WorkerShutdown shutdown_behavior; + tracked_objects::Location posted_from; + Closure task; + + // Non-delayed tasks and delayed tasks are managed together by time-to-run + // order. We calculate the time by adding the posted time and the given delay. + TimeTicks time_to_run; +}; + +struct SequencedTaskLessThan { + public: + bool operator()(const SequencedTask& lhs, const SequencedTask& rhs) const { + if (lhs.time_to_run < rhs.time_to_run) + return true; + + if (lhs.time_to_run > rhs.time_to_run) + return false; + + // If the time happen to match, then we use the sequence number to decide. + return lhs.sequence_task_number < rhs.sequence_task_number; + } +}; + +// SequencedWorkerPoolTaskRunner --------------------------------------------- +// A TaskRunner which posts tasks to a SequencedWorkerPool with a +// fixed ShutdownBehavior. +// +// Note that this class is RefCountedThreadSafe (inherited from TaskRunner). +class SequencedWorkerPoolTaskRunner : public TaskRunner { + public: + SequencedWorkerPoolTaskRunner( + const scoped_refptr<SequencedWorkerPool>& pool, + SequencedWorkerPool::WorkerShutdown shutdown_behavior); + + // TaskRunner implementation + virtual bool PostDelayedTask(const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) OVERRIDE; + virtual bool RunsTasksOnCurrentThread() const OVERRIDE; + + private: + virtual ~SequencedWorkerPoolTaskRunner(); + + const scoped_refptr<SequencedWorkerPool> pool_; + + const SequencedWorkerPool::WorkerShutdown shutdown_behavior_; + + DISALLOW_COPY_AND_ASSIGN(SequencedWorkerPoolTaskRunner); +}; + +SequencedWorkerPoolTaskRunner::SequencedWorkerPoolTaskRunner( + const scoped_refptr<SequencedWorkerPool>& pool, + SequencedWorkerPool::WorkerShutdown shutdown_behavior) + : pool_(pool), + shutdown_behavior_(shutdown_behavior) { +} + +SequencedWorkerPoolTaskRunner::~SequencedWorkerPoolTaskRunner() { +} + +bool SequencedWorkerPoolTaskRunner::PostDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) { + if (delay == TimeDelta()) { + return pool_->PostWorkerTaskWithShutdownBehavior( + from_here, task, shutdown_behavior_); + } + return pool_->PostDelayedWorkerTask(from_here, task, delay); +} + +bool SequencedWorkerPoolTaskRunner::RunsTasksOnCurrentThread() const { + return pool_->RunsTasksOnCurrentThread(); +} + +// SequencedWorkerPoolSequencedTaskRunner ------------------------------------ +// A SequencedTaskRunner which posts tasks to a SequencedWorkerPool with a +// fixed sequence token. +// +// Note that this class is RefCountedThreadSafe (inherited from TaskRunner). +class SequencedWorkerPoolSequencedTaskRunner : public SequencedTaskRunner { + public: + SequencedWorkerPoolSequencedTaskRunner( + const scoped_refptr<SequencedWorkerPool>& pool, + SequencedWorkerPool::SequenceToken token, + SequencedWorkerPool::WorkerShutdown shutdown_behavior); + + // TaskRunner implementation + virtual bool PostDelayedTask(const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) OVERRIDE; + virtual bool RunsTasksOnCurrentThread() const OVERRIDE; + + // SequencedTaskRunner implementation + virtual bool PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) OVERRIDE; + + private: + virtual ~SequencedWorkerPoolSequencedTaskRunner(); + + const scoped_refptr<SequencedWorkerPool> pool_; + + const SequencedWorkerPool::SequenceToken token_; + + const SequencedWorkerPool::WorkerShutdown shutdown_behavior_; + + DISALLOW_COPY_AND_ASSIGN(SequencedWorkerPoolSequencedTaskRunner); +}; + +SequencedWorkerPoolSequencedTaskRunner::SequencedWorkerPoolSequencedTaskRunner( + const scoped_refptr<SequencedWorkerPool>& pool, + SequencedWorkerPool::SequenceToken token, + SequencedWorkerPool::WorkerShutdown shutdown_behavior) + : pool_(pool), + token_(token), + shutdown_behavior_(shutdown_behavior) { +} + +SequencedWorkerPoolSequencedTaskRunner:: +~SequencedWorkerPoolSequencedTaskRunner() { +} + +bool SequencedWorkerPoolSequencedTaskRunner::PostDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) { + if (delay == TimeDelta()) { + return pool_->PostSequencedWorkerTaskWithShutdownBehavior( + token_, from_here, task, shutdown_behavior_); + } + return pool_->PostDelayedSequencedWorkerTask(token_, from_here, task, delay); +} + +bool SequencedWorkerPoolSequencedTaskRunner::RunsTasksOnCurrentThread() const { + return pool_->IsRunningSequenceOnCurrentThread(token_); +} + +bool SequencedWorkerPoolSequencedTaskRunner::PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) { + // There's no way to run nested tasks, so simply forward to + // PostDelayedTask. + return PostDelayedTask(from_here, task, delay); +} + +// Create a process-wide unique ID to represent this task in trace events. This +// will be mangled with a Process ID hash to reduce the likelyhood of colliding +// with MessageLoop pointers on other processes. +uint64 GetTaskTraceID(const SequencedTask& task, + void* pool) { + return (static_cast<uint64>(task.trace_id) << 32) | + static_cast<uint64>(reinterpret_cast<intptr_t>(pool)); +} + +} // namespace + +// Worker --------------------------------------------------------------------- + +class SequencedWorkerPool::Worker : public SimpleThread { + public: + // Hold a (cyclic) ref to |worker_pool|, since we want to keep it + // around as long as we are running. + Worker(const scoped_refptr<SequencedWorkerPool>& worker_pool, + int thread_number, + const std::string& thread_name_prefix); + virtual ~Worker(); + + // SimpleThread implementation. This actually runs the background thread. + virtual void Run() OVERRIDE; + + void set_running_sequence(SequenceToken token) { + running_sequence_ = token; + } + + SequenceToken running_sequence() const { + return running_sequence_; + } + + private: + scoped_refptr<SequencedWorkerPool> worker_pool_; + SequenceToken running_sequence_; + + DISALLOW_COPY_AND_ASSIGN(Worker); +}; + +// Inner ---------------------------------------------------------------------- + +class SequencedWorkerPool::Inner { + public: + // Take a raw pointer to |worker| to avoid cycles (since we're owned + // by it). + Inner(SequencedWorkerPool* worker_pool, size_t max_threads, + const std::string& thread_name_prefix, + TestingObserver* observer); + + ~Inner(); + + SequenceToken GetSequenceToken(); + + SequenceToken GetNamedSequenceToken(const std::string& name); + + // This function accepts a name and an ID. If the name is null, the + // token ID is used. This allows us to implement the optional name lookup + // from a single function without having to enter the lock a separate time. + bool PostTask(const std::string* optional_token_name, + SequenceToken sequence_token, + WorkerShutdown shutdown_behavior, + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay); + + bool RunsTasksOnCurrentThread() const; + + bool IsRunningSequenceOnCurrentThread(SequenceToken sequence_token) const; + + void FlushForTesting(); + + void SignalHasWorkForTesting(); + + int GetWorkSignalCountForTesting() const; + + void Shutdown(); + + // Runs the worker loop on the background thread. + void ThreadLoop(Worker* this_worker); + + private: + enum GetWorkStatus { + GET_WORK_FOUND, + GET_WORK_NOT_FOUND, + GET_WORK_WAIT, + }; + + // Returns whether there are no more pending tasks and all threads + // are idle. Must be called under lock. + bool IsIdle() const; + + // Called from within the lock, this converts the given token name into a + // token ID, creating a new one if necessary. + int LockedGetNamedTokenID(const std::string& name); + + // Called from within the lock, this returns the next sequence task number. + int64 LockedGetNextSequenceTaskNumber(); + + // Gets new task. There are 3 cases depending on the return value: + // + // 1) If the return value is |GET_WORK_FOUND|, |task| is filled in and should + // be run immediately. + // 2) If the return value is |GET_WORK_NOT_FOUND|, there are no tasks to run, + // and |task| is not filled in. In this case, the caller should wait until + // a task is posted. + // 3) If the return value is |GET_WORK_WAIT|, there are no tasks to run + // immediately, and |task| is not filled in. Likewise, |wait_time| is + // filled in the time to wait until the next task to run. In this case, the + // caller should wait the time. + // + // In any case, the calling code should clear the given + // delete_these_outside_lock vector the next time the lock is released. + // See the implementation for a more detailed description. + GetWorkStatus GetWork(SequencedTask* task, + TimeDelta* wait_time, + std::vector<Closure>* delete_these_outside_lock); + + // Peforms init and cleanup around running the given task. WillRun... + // returns the value from PrepareToStartAdditionalThreadIfNecessary. + // The calling code should call FinishStartingAdditionalThread once the + // lock is released if the return values is nonzero. + int WillRunWorkerTask(const SequencedTask& task); + void DidRunWorkerTask(const SequencedTask& task); + + // Returns true if there are no threads currently running the given + // sequence token. + bool IsSequenceTokenRunnable(int sequence_token_id) const; + + // Checks if all threads are busy and the addition of one more could run an + // additional task waiting in the queue. This must be called from within + // the lock. + // + // If another thread is helpful, this will mark the thread as being in the + // process of starting and returns the index of the new thread which will be + // 0 or more. The caller should then call FinishStartingAdditionalThread to + // complete initialization once the lock is released. + // + // If another thread is not necessary, returne 0; + // + // See the implementedion for more. + int PrepareToStartAdditionalThreadIfHelpful(); + + // The second part of thread creation after + // PrepareToStartAdditionalThreadIfHelpful with the thread number it + // generated. This actually creates the thread and should be called outside + // the lock to avoid blocking important work starting a thread in the lock. + void FinishStartingAdditionalThread(int thread_number); + + // Signal |has_work_| and increment |has_work_signal_count_|. + void SignalHasWork(); + + // Checks whether there is work left that's blocking shutdown. Must be + // called inside the lock. + bool CanShutdown() const; + + SequencedWorkerPool* const worker_pool_; + + // The last sequence number used. Managed by GetSequenceToken, since this + // only does threadsafe increment operations, you do not need to hold the + // lock. + volatile subtle::Atomic32 last_sequence_number_; + + // This lock protects |everything in this class|. Do not read or modify + // anything without holding this lock. Do not block while holding this + // lock. + mutable Lock lock_; + + // Condition variable that is waited on by worker threads until new + // tasks are posted or shutdown starts. + ConditionVariable has_work_cv_; + + // Condition variable that is waited on by non-worker threads (in + // FlushForTesting()) until IsIdle() goes to true. + ConditionVariable is_idle_cv_; + + // Condition variable that is waited on by non-worker threads (in + // Shutdown()) until CanShutdown() goes to true. + ConditionVariable can_shutdown_cv_; + + // The maximum number of worker threads we'll create. + const size_t max_threads_; + + const std::string thread_name_prefix_; + + // Associates all known sequence token names with their IDs. + std::map<std::string, int> named_sequence_tokens_; + + // Owning pointers to all threads we've created so far, indexed by + // ID. Since we lazily create threads, this may be less than + // max_threads_ and will be initially empty. + typedef std::map<PlatformThreadId, linked_ptr<Worker> > ThreadMap; + ThreadMap threads_; + + // Set to true when we're in the process of creating another thread. + // See PrepareToStartAdditionalThreadIfHelpful for more. + bool thread_being_created_; + + // Number of threads currently waiting for work. + size_t waiting_thread_count_; + + // Number of threads currently running tasks that have the BLOCK_SHUTDOWN + // or SKIP_ON_SHUTDOWN flag set. + size_t blocking_shutdown_thread_count_; + + // A set of all pending tasks in time-to-run order. These are tasks that are + // either waiting for a thread to run on, waiting for their time to run, + // or blocked on a previous task in their sequence. We have to iterate over + // the tasks by time-to-run order, so we use the set instead of the + // traditional priority_queue. + typedef std::set<SequencedTask, SequencedTaskLessThan> PendingTaskSet; + PendingTaskSet pending_tasks_; + + // The next sequence number for a new sequenced task. + int64 next_sequence_task_number_; + + // Number of tasks in the pending_tasks_ list that are marked as blocking + // shutdown. + size_t blocking_shutdown_pending_task_count_; + + // Lists all sequence tokens currently executing. + std::set<int> current_sequences_; + + // An ID for each posted task to distinguish the task from others in traces. + int trace_id_; + + // Set when Shutdown is called and no further tasks should be + // allowed, though we may still be running existing tasks. + bool shutdown_called_; + + TestingObserver* const testing_observer_; + + DISALLOW_COPY_AND_ASSIGN(Inner); +}; + +// Worker definitions --------------------------------------------------------- + +SequencedWorkerPool::Worker::Worker( + const scoped_refptr<SequencedWorkerPool>& worker_pool, + int thread_number, + const std::string& prefix) + : SimpleThread( + prefix + StringPrintf("Worker%d", thread_number).c_str()), + worker_pool_(worker_pool) { + Start(); +} + +SequencedWorkerPool::Worker::~Worker() { +} + +void SequencedWorkerPool::Worker::Run() { + // Just jump back to the Inner object to run the thread, since it has all the + // tracking information and queues. It might be more natural to implement + // using DelegateSimpleThread and have Inner implement the Delegate to avoid + // having these worker objects at all, but that method lacks the ability to + // send thread-specific information easily to the thread loop. + worker_pool_->inner_->ThreadLoop(this); + // Release our cyclic reference once we're done. + worker_pool_ = NULL; +} + +// Inner definitions --------------------------------------------------------- + +SequencedWorkerPool::Inner::Inner( + SequencedWorkerPool* worker_pool, + size_t max_threads, + const std::string& thread_name_prefix, + TestingObserver* observer) + : worker_pool_(worker_pool), + last_sequence_number_(0), + lock_(), + has_work_cv_(&lock_), + is_idle_cv_(&lock_), + can_shutdown_cv_(&lock_), + max_threads_(max_threads), + thread_name_prefix_(thread_name_prefix), + thread_being_created_(false), + waiting_thread_count_(0), + blocking_shutdown_thread_count_(0), + next_sequence_task_number_(0), + blocking_shutdown_pending_task_count_(0), + trace_id_(0), + shutdown_called_(false), + testing_observer_(observer) {} + +SequencedWorkerPool::Inner::~Inner() { + // You must call Shutdown() before destroying the pool. + DCHECK(shutdown_called_); + + // Need to explicitly join with the threads before they're destroyed or else + // they will be running when our object is half torn down. + for (ThreadMap::iterator it = threads_.begin(); it != threads_.end(); ++it) + it->second->Join(); + threads_.clear(); + + if (testing_observer_) + testing_observer_->OnDestruct(); +} + +SequencedWorkerPool::SequenceToken +SequencedWorkerPool::Inner::GetSequenceToken() { + subtle::Atomic32 result = + subtle::NoBarrier_AtomicIncrement(&last_sequence_number_, 1); + return SequenceToken(static_cast<int>(result)); +} + +SequencedWorkerPool::SequenceToken +SequencedWorkerPool::Inner::GetNamedSequenceToken(const std::string& name) { + AutoLock lock(lock_); + return SequenceToken(LockedGetNamedTokenID(name)); +} + +bool SequencedWorkerPool::Inner::PostTask( + const std::string* optional_token_name, + SequenceToken sequence_token, + WorkerShutdown shutdown_behavior, + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) { + DCHECK(delay == TimeDelta() || shutdown_behavior == SKIP_ON_SHUTDOWN); + SequencedTask sequenced(from_here); + sequenced.sequence_token_id = sequence_token.id_; + sequenced.shutdown_behavior = shutdown_behavior; + sequenced.posted_from = from_here; + sequenced.task = + shutdown_behavior == BLOCK_SHUTDOWN ? + base::MakeCriticalClosure(task) : task; + sequenced.time_to_run = TimeTicks::Now() + delay; + + int create_thread_id = 0; + { + AutoLock lock(lock_); + if (shutdown_called_) + return false; + + // The trace_id is used for identifying the task in about:tracing. + sequenced.trace_id = trace_id_++; + + TRACE_EVENT_FLOW_BEGIN0("task", "SequencedWorkerPool::PostTask", + TRACE_ID_MANGLE(GetTaskTraceID(sequenced, static_cast<void*>(this)))); + + sequenced.sequence_task_number = LockedGetNextSequenceTaskNumber(); + + // Now that we have the lock, apply the named token rules. + if (optional_token_name) + sequenced.sequence_token_id = LockedGetNamedTokenID(*optional_token_name); + + pending_tasks_.insert(sequenced); + if (shutdown_behavior == BLOCK_SHUTDOWN) + blocking_shutdown_pending_task_count_++; + + create_thread_id = PrepareToStartAdditionalThreadIfHelpful(); + } + + // Actually start the additional thread or signal an existing one now that + // we're outside the lock. + if (create_thread_id) + FinishStartingAdditionalThread(create_thread_id); + else + SignalHasWork(); + + return true; +} + +bool SequencedWorkerPool::Inner::RunsTasksOnCurrentThread() const { + AutoLock lock(lock_); + return ContainsKey(threads_, PlatformThread::CurrentId()); +} + +bool SequencedWorkerPool::Inner::IsRunningSequenceOnCurrentThread( + SequenceToken sequence_token) const { + AutoLock lock(lock_); + ThreadMap::const_iterator found = threads_.find(PlatformThread::CurrentId()); + if (found == threads_.end()) + return false; + return found->second->running_sequence().Equals(sequence_token); +} + +void SequencedWorkerPool::Inner::FlushForTesting() { + AutoLock lock(lock_); + while (!IsIdle()) + is_idle_cv_.Wait(); +} + +void SequencedWorkerPool::Inner::SignalHasWorkForTesting() { + SignalHasWork(); +} + +void SequencedWorkerPool::Inner::Shutdown() { + // Mark us as terminated and go through and drop all tasks that aren't + // required to run on shutdown. Since no new tasks will get posted once the + // terminated flag is set, this ensures that all remaining tasks are required + // for shutdown whenever the termianted_ flag is set. + { + AutoLock lock(lock_); + + if (shutdown_called_) + return; + shutdown_called_ = true; + + // Tickle the threads. This will wake up a waiting one so it will know that + // it can exit, which in turn will wake up any other waiting ones. + SignalHasWork(); + + // There are no pending or running tasks blocking shutdown, we're done. + if (CanShutdown()) + return; + } + + // If we're here, then something is blocking shutdown. So wait for + // CanShutdown() to go to true. + + if (testing_observer_) + testing_observer_->WillWaitForShutdown(); + + TimeTicks shutdown_wait_begin = TimeTicks::Now(); + + { + base::ThreadRestrictions::ScopedAllowWait allow_wait; + AutoLock lock(lock_); + while (!CanShutdown()) + can_shutdown_cv_.Wait(); + } + UMA_HISTOGRAM_TIMES("SequencedWorkerPool.ShutdownDelayTime", + TimeTicks::Now() - shutdown_wait_begin); +} + +void SequencedWorkerPool::Inner::ThreadLoop(Worker* this_worker) { + { + AutoLock lock(lock_); + DCHECK(thread_being_created_); + thread_being_created_ = false; + std::pair<ThreadMap::iterator, bool> result = + threads_.insert( + std::make_pair(this_worker->tid(), make_linked_ptr(this_worker))); + DCHECK(result.second); + + while (true) { +#if defined(OS_MACOSX) + base::mac::ScopedNSAutoreleasePool autorelease_pool; +#endif + + // See GetWork for what delete_these_outside_lock is doing. + SequencedTask task; + TimeDelta wait_time; + std::vector<Closure> delete_these_outside_lock; + GetWorkStatus status = + GetWork(&task, &wait_time, &delete_these_outside_lock); + if (status == GET_WORK_FOUND) { + TRACE_EVENT_FLOW_END0("task", "SequencedWorkerPool::PostTask", + TRACE_ID_MANGLE(GetTaskTraceID(task, static_cast<void*>(this)))); + TRACE_EVENT2("task", "SequencedWorkerPool::ThreadLoop", + "src_file", task.posted_from.file_name(), + "src_func", task.posted_from.function_name()); + int new_thread_id = WillRunWorkerTask(task); + { + AutoUnlock unlock(lock_); + // There may be more work available, so wake up another + // worker thread. (Technically not required, since we + // already get a signal for each new task, but it doesn't + // hurt.) + SignalHasWork(); + delete_these_outside_lock.clear(); + + // Complete thread creation outside the lock if necessary. + if (new_thread_id) + FinishStartingAdditionalThread(new_thread_id); + + this_worker->set_running_sequence( + SequenceToken(task.sequence_token_id)); + + tracked_objects::TrackedTime start_time = + tracked_objects::ThreadData::NowForStartOfRun(task.birth_tally); + + task.task.Run(); + + tracked_objects::ThreadData::TallyRunOnNamedThreadIfTracking(task, + start_time, tracked_objects::ThreadData::NowForEndOfRun()); + + this_worker->set_running_sequence(SequenceToken()); + + // Make sure our task is erased outside the lock for the same reason + // we do this with delete_these_oustide_lock. + task.task = Closure(); + } + DidRunWorkerTask(task); // Must be done inside the lock. + } else { + // When we're terminating and there's no more work, we can + // shut down. You can't get more tasks posted once + // shutdown_called_ is set. There may be some tasks stuck + // behind running ones with the same sequence token, but + // additional threads won't help this case. + if (shutdown_called_ && + blocking_shutdown_pending_task_count_ == 0) + break; + waiting_thread_count_++; + // This is the only time that IsIdle() can go to true. + if (IsIdle()) + is_idle_cv_.Signal(); + + switch (status) { + case GET_WORK_NOT_FOUND: + has_work_cv_.Wait(); + break; + case GET_WORK_WAIT: + has_work_cv_.TimedWait(wait_time); + break; + default: + NOTREACHED(); + } + waiting_thread_count_--; + } + } + } // Release lock_. + + // We noticed we should exit. Wake up the next worker so it knows it should + // exit as well (because the Shutdown() code only signals once). + SignalHasWork(); + + // Possibly unblock shutdown. + can_shutdown_cv_.Signal(); +} + +bool SequencedWorkerPool::Inner::IsIdle() const { + lock_.AssertAcquired(); + return pending_tasks_.empty() && waiting_thread_count_ == threads_.size(); +} + +int SequencedWorkerPool::Inner::LockedGetNamedTokenID( + const std::string& name) { + lock_.AssertAcquired(); + DCHECK(!name.empty()); + + std::map<std::string, int>::const_iterator found = + named_sequence_tokens_.find(name); + if (found != named_sequence_tokens_.end()) + return found->second; // Got an existing one. + + // Create a new one for this name. + SequenceToken result = GetSequenceToken(); + named_sequence_tokens_.insert(std::make_pair(name, result.id_)); + return result.id_; +} + +int64 SequencedWorkerPool::Inner::LockedGetNextSequenceTaskNumber() { + lock_.AssertAcquired(); + // We assume that we never create enough tasks to wrap around. + return next_sequence_task_number_++; +} + +SequencedWorkerPool::Inner::GetWorkStatus SequencedWorkerPool::Inner::GetWork( + SequencedTask* task, + TimeDelta* wait_time, + std::vector<Closure>* delete_these_outside_lock) { + lock_.AssertAcquired(); + + UMA_HISTOGRAM_COUNTS_100("SequencedWorkerPool.TaskCount", + static_cast<int>(pending_tasks_.size())); + + // Find the next task with a sequence token that's not currently in use. + // If the token is in use, that means another thread is running something + // in that sequence, and we can't run it without going out-of-order. + // + // This algorithm is simple and fair, but inefficient in some cases. For + // example, say somebody schedules 1000 slow tasks with the same sequence + // number. We'll have to go through all those tasks each time we feel like + // there might be work to schedule. If this proves to be a problem, we + // should make this more efficient. + // + // One possible enhancement would be to keep a map from sequence ID to a + // list of pending but currently blocked SequencedTasks for that ID. + // When a worker finishes a task of one sequence token, it can pick up the + // next one from that token right away. + // + // This may lead to starvation if there are sufficient numbers of sequences + // in use. To alleviate this, we could add an incrementing priority counter + // to each SequencedTask. Then maintain a priority_queue of all runnable + // tasks, sorted by priority counter. When a sequenced task is completed + // we would pop the head element off of that tasks pending list and add it + // to the priority queue. Then we would run the first item in the priority + // queue. + + GetWorkStatus status = GET_WORK_NOT_FOUND; + int unrunnable_tasks = 0; + PendingTaskSet::iterator i = pending_tasks_.begin(); + // We assume that the loop below doesn't take too long and so we can just do + // a single call to TimeTicks::Now(). + const TimeTicks current_time = TimeTicks::Now(); + while (i != pending_tasks_.end()) { + if (!IsSequenceTokenRunnable(i->sequence_token_id)) { + unrunnable_tasks++; + ++i; + continue; + } + + if (i->time_to_run > current_time) { + // The time to run has not come yet. + *wait_time = i->time_to_run - current_time; + status = GET_WORK_WAIT; + break; + } + + if (shutdown_called_ && i->shutdown_behavior != BLOCK_SHUTDOWN) { + // We're shutting down and the task we just found isn't blocking + // shutdown. Delete it and get more work. + // + // Note that we do not want to delete unrunnable tasks. Deleting a task + // can have side effects (like freeing some objects) and deleting a + // task that's supposed to run after one that's currently running could + // cause an obscure crash. + // + // We really want to delete these tasks outside the lock in case the + // closures are holding refs to objects that want to post work from + // their destructorss (which would deadlock). The closures are + // internally refcounted, so we just need to keep a copy of them alive + // until the lock is exited. The calling code can just clear() the + // vector they passed to us once the lock is exited to make this + // happen. + delete_these_outside_lock->push_back(i->task); + pending_tasks_.erase(i++); + } else { + // Found a runnable task. + *task = *i; + pending_tasks_.erase(i); + if (task->shutdown_behavior == BLOCK_SHUTDOWN) { + blocking_shutdown_pending_task_count_--; + } + + status = GET_WORK_FOUND; + break; + } + } + + // Track the number of tasks we had to skip over to see if we should be + // making this more efficient. If this number ever becomes large or is + // frequently "some", we should consider the optimization above. + UMA_HISTOGRAM_COUNTS_100("SequencedWorkerPool.UnrunnableTaskCount", + unrunnable_tasks); + return status; +} + +int SequencedWorkerPool::Inner::WillRunWorkerTask(const SequencedTask& task) { + lock_.AssertAcquired(); + + // Mark the task's sequence number as in use. + if (task.sequence_token_id) + current_sequences_.insert(task.sequence_token_id); + + // Ensure that threads running tasks posted with either SKIP_ON_SHUTDOWN + // or BLOCK_SHUTDOWN will prevent shutdown until that task or thread + // completes. + if (task.shutdown_behavior != CONTINUE_ON_SHUTDOWN) + blocking_shutdown_thread_count_++; + + // We just picked up a task. Since StartAdditionalThreadIfHelpful only + // creates a new thread if there is no free one, there is a race when posting + // tasks that many tasks could have been posted before a thread started + // running them, so only one thread would have been created. So we also check + // whether we should create more threads after removing our task from the + // queue, which also has the nice side effect of creating the workers from + // background threads rather than the main thread of the app. + // + // If another thread wasn't created, we want to wake up an existing thread + // if there is one waiting to pick up the next task. + // + // Note that we really need to do this *before* running the task, not + // after. Otherwise, if more than one task is posted, the creation of the + // second thread (since we only create one at a time) will be blocked by + // the execution of the first task, which could be arbitrarily long. + return PrepareToStartAdditionalThreadIfHelpful(); +} + +void SequencedWorkerPool::Inner::DidRunWorkerTask(const SequencedTask& task) { + lock_.AssertAcquired(); + + if (task.shutdown_behavior != CONTINUE_ON_SHUTDOWN) { + DCHECK_GT(blocking_shutdown_thread_count_, 0u); + blocking_shutdown_thread_count_--; + } + + if (task.sequence_token_id) + current_sequences_.erase(task.sequence_token_id); +} + +bool SequencedWorkerPool::Inner::IsSequenceTokenRunnable( + int sequence_token_id) const { + lock_.AssertAcquired(); + return !sequence_token_id || + current_sequences_.find(sequence_token_id) == + current_sequences_.end(); +} + +int SequencedWorkerPool::Inner::PrepareToStartAdditionalThreadIfHelpful() { + lock_.AssertAcquired(); + // How thread creation works: + // + // We'de like to avoid creating threads with the lock held. However, we + // need to be sure that we have an accurate accounting of the threads for + // proper Joining and deltion on shutdown. + // + // We need to figure out if we need another thread with the lock held, which + // is what this function does. It then marks us as in the process of creating + // a thread. When we do shutdown, we wait until the thread_being_created_ + // flag is cleared, which ensures that the new thread is properly added to + // all the data structures and we can't leak it. Once shutdown starts, we'll + // refuse to create more threads or they would be leaked. + // + // Note that this creates a mostly benign race condition on shutdown that + // will cause fewer workers to be created than one would expect. It isn't + // much of an issue in real life, but affects some tests. Since we only spawn + // one worker at a time, the following sequence of events can happen: + // + // 1. Main thread posts a bunch of unrelated tasks that would normally be + // run on separate threads. + // 2. The first task post causes us to start a worker. Other tasks do not + // cause a worker to start since one is pending. + // 3. Main thread initiates shutdown. + // 4. No more threads are created since the shutdown_called_ flag is set. + // + // The result is that one may expect that max_threads_ workers to be created + // given the workload, but in reality fewer may be created because the + // sequence of thread creation on the background threads is racing with the + // shutdown call. + if (!shutdown_called_ && + !thread_being_created_ && + threads_.size() < max_threads_ && + waiting_thread_count_ == 0) { + // We could use an additional thread if there's work to be done. + for (PendingTaskSet::const_iterator i = pending_tasks_.begin(); + i != pending_tasks_.end(); ++i) { + if (IsSequenceTokenRunnable(i->sequence_token_id)) { + // Found a runnable task, mark the thread as being started. + thread_being_created_ = true; + return static_cast<int>(threads_.size() + 1); + } + } + } + return 0; +} + +void SequencedWorkerPool::Inner::FinishStartingAdditionalThread( + int thread_number) { + // Called outside of the lock. + DCHECK(thread_number > 0); + + // The worker is assigned to the list when the thread actually starts, which + // will manage the memory of the pointer. + new Worker(worker_pool_, thread_number, thread_name_prefix_); +} + +void SequencedWorkerPool::Inner::SignalHasWork() { + has_work_cv_.Signal(); + if (testing_observer_) { + testing_observer_->OnHasWork(); + } +} + +bool SequencedWorkerPool::Inner::CanShutdown() const { + lock_.AssertAcquired(); + // See PrepareToStartAdditionalThreadIfHelpful for how thread creation works. + return !thread_being_created_ && + blocking_shutdown_thread_count_ == 0 && + blocking_shutdown_pending_task_count_ == 0; +} + +// SequencedWorkerPool -------------------------------------------------------- + +SequencedWorkerPool::SequencedWorkerPool( + size_t max_threads, + const std::string& thread_name_prefix) + : constructor_message_loop_(MessageLoopProxy::current()), + inner_(new Inner(ALLOW_THIS_IN_INITIALIZER_LIST(this), + max_threads, thread_name_prefix, NULL)) { +} + +SequencedWorkerPool::SequencedWorkerPool( + size_t max_threads, + const std::string& thread_name_prefix, + TestingObserver* observer) + : constructor_message_loop_(MessageLoopProxy::current()), + inner_(new Inner(ALLOW_THIS_IN_INITIALIZER_LIST(this), + max_threads, thread_name_prefix, observer)) { +} + +SequencedWorkerPool::~SequencedWorkerPool() {} + +void SequencedWorkerPool::OnDestruct() const { + DCHECK(constructor_message_loop_.get()); + // Avoid deleting ourselves on a worker thread (which would + // deadlock). + if (RunsTasksOnCurrentThread()) { + constructor_message_loop_->DeleteSoon(FROM_HERE, this); + } else { + delete this; + } +} + +SequencedWorkerPool::SequenceToken SequencedWorkerPool::GetSequenceToken() { + return inner_->GetSequenceToken(); +} + +SequencedWorkerPool::SequenceToken SequencedWorkerPool::GetNamedSequenceToken( + const std::string& name) { + return inner_->GetNamedSequenceToken(name); +} + +scoped_refptr<SequencedTaskRunner> SequencedWorkerPool::GetSequencedTaskRunner( + SequenceToken token) { + return GetSequencedTaskRunnerWithShutdownBehavior(token, BLOCK_SHUTDOWN); +} + +scoped_refptr<SequencedTaskRunner> +SequencedWorkerPool::GetSequencedTaskRunnerWithShutdownBehavior( + SequenceToken token, WorkerShutdown shutdown_behavior) { + return new SequencedWorkerPoolSequencedTaskRunner( + this, token, shutdown_behavior); +} + +scoped_refptr<TaskRunner> +SequencedWorkerPool::GetTaskRunnerWithShutdownBehavior( + WorkerShutdown shutdown_behavior) { + return new SequencedWorkerPoolTaskRunner(this, shutdown_behavior); +} + +bool SequencedWorkerPool::PostWorkerTask( + const tracked_objects::Location& from_here, + const Closure& task) { + return inner_->PostTask(NULL, SequenceToken(), BLOCK_SHUTDOWN, + from_here, task, TimeDelta()); +} + +bool SequencedWorkerPool::PostDelayedWorkerTask( + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) { + WorkerShutdown shutdown_behavior = + delay == TimeDelta() ? BLOCK_SHUTDOWN : SKIP_ON_SHUTDOWN; + return inner_->PostTask(NULL, SequenceToken(), shutdown_behavior, + from_here, task, delay); +} + +bool SequencedWorkerPool::PostWorkerTaskWithShutdownBehavior( + const tracked_objects::Location& from_here, + const Closure& task, + WorkerShutdown shutdown_behavior) { + return inner_->PostTask(NULL, SequenceToken(), shutdown_behavior, + from_here, task, TimeDelta()); +} + +bool SequencedWorkerPool::PostSequencedWorkerTask( + SequenceToken sequence_token, + const tracked_objects::Location& from_here, + const Closure& task) { + return inner_->PostTask(NULL, sequence_token, BLOCK_SHUTDOWN, + from_here, task, TimeDelta()); +} + +bool SequencedWorkerPool::PostDelayedSequencedWorkerTask( + SequenceToken sequence_token, + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) { + WorkerShutdown shutdown_behavior = + delay == TimeDelta() ? BLOCK_SHUTDOWN : SKIP_ON_SHUTDOWN; + return inner_->PostTask(NULL, sequence_token, shutdown_behavior, + from_here, task, delay); +} + +bool SequencedWorkerPool::PostNamedSequencedWorkerTask( + const std::string& token_name, + const tracked_objects::Location& from_here, + const Closure& task) { + DCHECK(!token_name.empty()); + return inner_->PostTask(&token_name, SequenceToken(), BLOCK_SHUTDOWN, + from_here, task, TimeDelta()); +} + +bool SequencedWorkerPool::PostSequencedWorkerTaskWithShutdownBehavior( + SequenceToken sequence_token, + const tracked_objects::Location& from_here, + const Closure& task, + WorkerShutdown shutdown_behavior) { + return inner_->PostTask(NULL, sequence_token, shutdown_behavior, + from_here, task, TimeDelta()); +} + +bool SequencedWorkerPool::PostDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) { + return PostDelayedWorkerTask(from_here, task, delay); +} + +bool SequencedWorkerPool::RunsTasksOnCurrentThread() const { + return inner_->RunsTasksOnCurrentThread(); +} + +bool SequencedWorkerPool::IsRunningSequenceOnCurrentThread( + SequenceToken sequence_token) const { + return inner_->IsRunningSequenceOnCurrentThread(sequence_token); +} + +void SequencedWorkerPool::FlushForTesting() { + inner_->FlushForTesting(); +} + +void SequencedWorkerPool::SignalHasWorkForTesting() { + inner_->SignalHasWorkForTesting(); +} + +void SequencedWorkerPool::Shutdown() { + DCHECK(constructor_message_loop_->BelongsToCurrentThread()); + inner_->Shutdown(); +} + +} // namespace base
diff --git a/src/base/threading/sequenced_worker_pool.h b/src/base/threading/sequenced_worker_pool.h new file mode 100644 index 0000000..1a26a4a --- /dev/null +++ b/src/base/threading/sequenced_worker_pool.h
@@ -0,0 +1,336 @@ +// 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_THREADING_SEQUENCED_WORKER_POOL_H_ +#define BASE_THREADING_SEQUENCED_WORKER_POOL_H_ + +#include <cstddef> +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/task_runner.h" +#include "base/sequenced_task_runner.h" + +namespace tracked_objects { +class Location; +} // namespace tracked_objects + +namespace base { + +class MessageLoopProxy; + +template <class T> class DeleteHelper; + +class SequencedTaskRunner; + +// A worker thread pool that enforces ordering between sets of tasks. It also +// allows you to specify what should happen to your tasks on shutdown. +// +// To enforce ordering, get a unique sequence token from the pool and post all +// tasks you want to order with the token. All tasks with the same token are +// guaranteed to execute serially, though not necessarily on the same thread. +// This means that: +// +// - No two tasks with the same token will run at the same time. +// +// - Given two tasks T1 and T2 with the same token such that T2 will +// run after T1, then T2 will start after T1 is destroyed. +// +// - If T2 will run after T1, then all memory changes in T1 and T1's +// destruction will be visible to T2. +// +// Example: +// SequencedWorkerPool::SequenceToken token = pool.GetSequenceToken(); +// pool.PostSequencedWorkerTask(token, SequencedWorkerPool::SKIP_ON_SHUTDOWN, +// FROM_HERE, base::Bind(...)); +// pool.PostSequencedWorkerTask(token, SequencedWorkerPool::SKIP_ON_SHUTDOWN, +// FROM_HERE, base::Bind(...)); +// +// You can make named sequence tokens to make it easier to share a token +// across different components. +// +// You can also post tasks to the pool without ordering using PostWorkerTask. +// These will be executed in an unspecified order. The order of execution +// between tasks with different sequence tokens is also unspecified. +// +// This class is designed to be leaked on shutdown to allow the +// CONTINUE_ON_SHUTDOWN behavior to be implemented. To enforce the +// BLOCK_SHUTDOWN behavior, you must call Shutdown() which will wait until +// the necessary tasks have completed. +// +// Implementation note: This does not use a base::WorkerPool since that does +// not enforce shutdown semantics or allow us to specify how many worker +// threads to run. For the typical use case of random background work, we don't +// necessarily want to be super aggressive about creating threads. +// +// Note that SequencedWorkerPool is RefCountedThreadSafe (inherited +// from TaskRunner). +class BASE_EXPORT SequencedWorkerPool : public TaskRunner { + public: + // Defines what should happen to a task posted to the worker pool on + // shutdown. + enum WorkerShutdown { + // Tasks posted with this mode which have not run at shutdown will be + // deleted rather than run, and any tasks with this mode running at + // shutdown will be ignored (the worker thread will not be joined). + // + // This option provides a nice way to post stuff you don't want blocking + // shutdown. For example, you might be doing a slow DNS lookup and if it's + // blocked on the OS, you may not want to stop shutdown, since the result + // doesn't really matter at that point. + // + // However, you need to be very careful what you do in your callback when + // you use this option. Since the thread will continue to run until the OS + // terminates the process, the app can be in the process of tearing down + // when you're running. This means any singletons or global objects you + // use may suddenly become invalid out from under you. For this reason, + // it's best to use this only for slow but simple operations like the DNS + // example. + CONTINUE_ON_SHUTDOWN, + + // Tasks posted with this mode that have not started executing at + // shutdown will be deleted rather than executed. However, any tasks that + // have already begun executing when shutdown is called will be allowed + // to continue, and will block shutdown until completion. + // + // Note: Because Shutdown() may block while these tasks are executing, + // care must be taken to ensure that they do not block on the thread that + // called Shutdown(), as this may lead to deadlock. + SKIP_ON_SHUTDOWN, + + // Tasks posted with this mode will block shutdown until they're + // executed. Since this can have significant performance implications, + // use sparingly. + // + // Generally, this should be used only for user data, for example, a task + // writing a preference file. + // + // If a task is posted during shutdown, it will not get run since the + // workers may already be stopped. In this case, the post operation will + // fail (return false) and the task will be deleted. + BLOCK_SHUTDOWN, + }; + + private: + class Inner; + + public: + // Opaque identifier that defines sequencing of tasks posted to the worker + // pool. + class SequenceToken { + public: + SequenceToken() : id_(0) {} + ~SequenceToken() {} + + bool Equals(const SequenceToken& other) const { + return id_ == other.id_; + } + + private: + friend class SequencedWorkerPool; + + explicit SequenceToken(int id) : id_(id) {} + + int id_; + }; + + // Allows tests to perform certain actions. + class TestingObserver { + public: + virtual ~TestingObserver() {} + virtual void OnHasWork() = 0; + virtual void WillWaitForShutdown() = 0; + virtual void OnDestruct() = 0; + }; + + // When constructing a SequencedWorkerPool, there must be a + // MessageLoop on the current thread unless you plan to deliberately + // leak it. + + // Pass the maximum number of threads (they will be lazily created as needed) + // and a prefix for the thread name to aid in debugging. + SequencedWorkerPool(size_t max_threads, + const std::string& thread_name_prefix); + + // Like above, but with |observer| for testing. Does not take + // ownership of |observer|. + SequencedWorkerPool(size_t max_threads, + const std::string& thread_name_prefix, + TestingObserver* observer); + + // Returns a unique token that can be used to sequence tasks posted to + // PostSequencedWorkerTask(). Valid tokens are alwys nonzero. + SequenceToken GetSequenceToken(); + + // Returns the sequence token associated with the given name. Calling this + // function multiple times with the same string will always produce the + // same sequence token. If the name has not been used before, a new token + // will be created. + SequenceToken GetNamedSequenceToken(const std::string& name); + + // Returns a SequencedTaskRunner wrapper which posts to this + // SequencedWorkerPool using the given sequence token. Tasks with nonzero + // delay are posted with SKIP_ON_SHUTDOWN behavior and tasks with zero delay + // are posted with BLOCK_SHUTDOWN behavior. + scoped_refptr<SequencedTaskRunner> GetSequencedTaskRunner( + SequenceToken token); + + // Returns a SequencedTaskRunner wrapper which posts to this + // SequencedWorkerPool using the given sequence token. Tasks with nonzero + // delay are posted with SKIP_ON_SHUTDOWN behavior and tasks with zero delay + // are posted with the given shutdown behavior. + scoped_refptr<SequencedTaskRunner> GetSequencedTaskRunnerWithShutdownBehavior( + SequenceToken token, + WorkerShutdown shutdown_behavior); + + // Returns a TaskRunner wrapper which posts to this SequencedWorkerPool using + // the given shutdown behavior. Tasks with nonzero delay are posted with + // SKIP_ON_SHUTDOWN behavior and tasks with zero delay are posted with the + // given shutdown behavior. + scoped_refptr<TaskRunner> GetTaskRunnerWithShutdownBehavior( + WorkerShutdown shutdown_behavior); + + // Posts the given task for execution in the worker pool. Tasks posted with + // this function will execute in an unspecified order on a background thread. + // Returns true if the task was posted. If your tasks have ordering + // requirements, see PostSequencedWorkerTask(). + // + // This class will attempt to delete tasks that aren't run + // (non-block-shutdown semantics) but can't guarantee that this happens. If + // all worker threads are busy running CONTINUE_ON_SHUTDOWN tasks, there + // will be no workers available to delete these tasks. And there may be + // tasks with the same sequence token behind those CONTINUE_ON_SHUTDOWN + // tasks. Deleting those tasks before the previous one has completed could + // cause nondeterministic crashes because the task could be keeping some + // objects alive which do work in their destructor, which could voilate the + // assumptions of the running task. + // + // The task will be guaranteed to run to completion before shutdown + // (BLOCK_SHUTDOWN semantics). + // + // Returns true if the task was posted successfully. This may fail during + // shutdown regardless of the specified ShutdownBehavior. + bool PostWorkerTask(const tracked_objects::Location& from_here, + const Closure& task); + + // Same as PostWorkerTask but allows a delay to be specified (although doing + // so changes the shutdown behavior). The task will be run after the given + // delay has elapsed. + // + // If the delay is nonzero, the task won't be guaranteed to run to completion + // before shutdown (SKIP_ON_SHUTDOWN semantics) to avoid shutdown hangs. + // If the delay is zero, this behaves exactly like PostWorkerTask, i.e. the + // task will be guaranteed to run to completion before shutdown + // (BLOCK_SHUTDOWN semantics). + bool PostDelayedWorkerTask(const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay); + + // Same as PostWorkerTask but allows specification of the shutdown behavior. + bool PostWorkerTaskWithShutdownBehavior( + const tracked_objects::Location& from_here, + const Closure& task, + WorkerShutdown shutdown_behavior); + + // Like PostWorkerTask above, but provides sequencing semantics. This means + // that tasks posted with the same sequence token (see GetSequenceToken()) + // are guaranteed to execute in order. This is useful in cases where you're + // doing operations that may depend on previous ones, like appending to a + // file. + // + // The task will be guaranteed to run to completion before shutdown + // (BLOCK_SHUTDOWN semantics). + // + // Returns true if the task was posted successfully. This may fail during + // shutdown regardless of the specified ShutdownBehavior. + bool PostSequencedWorkerTask(SequenceToken sequence_token, + const tracked_objects::Location& from_here, + const Closure& task); + + // Like PostSequencedWorkerTask above, but allows you to specify a named + // token, which saves an extra call to GetNamedSequenceToken. + bool PostNamedSequencedWorkerTask(const std::string& token_name, + const tracked_objects::Location& from_here, + const Closure& task); + + // Same as PostSequencedWorkerTask but allows a delay to be specified + // (although doing so changes the shutdown behavior). The task will be run + // after the given delay has elapsed. + // + // If the delay is nonzero, the task won't be guaranteed to run to completion + // before shutdown (SKIP_ON_SHUTDOWN semantics) to avoid shutdown hangs. + // If the delay is zero, this behaves exactly like PostSequencedWorkerTask, + // i.e. the task will be guaranteed to run to completion before shutdown + // (BLOCK_SHUTDOWN semantics). + bool PostDelayedSequencedWorkerTask( + SequenceToken sequence_token, + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay); + + // Same as PostSequencedWorkerTask but allows specification of the shutdown + // behavior. + bool PostSequencedWorkerTaskWithShutdownBehavior( + SequenceToken sequence_token, + const tracked_objects::Location& from_here, + const Closure& task, + WorkerShutdown shutdown_behavior); + + // TaskRunner implementation. Forwards to PostDelayedWorkerTask(). + virtual bool PostDelayedTask(const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) OVERRIDE; + virtual bool RunsTasksOnCurrentThread() const OVERRIDE; + + // Returns true if the current thread is processing a task with the given + // sequence_token. + bool IsRunningSequenceOnCurrentThread(SequenceToken sequence_token) const; + + // Blocks until all pending tasks are complete. This should only be called in + // unit tests when you want to validate something that should have happened. + // + // Note that calling this will not prevent other threads from posting work to + // the queue while the calling thread is waiting on Flush(). In this case, + // Flush will return only when there's no more work in the queue. Normally, + // this doesn't come up since in a test, all the work is being posted from + // the main thread. + void FlushForTesting(); + + // Spuriously signal that there is work to be done. + void SignalHasWorkForTesting(); + + // Implements the worker pool shutdown. This should be called during app + // shutdown, and will discard/join with appropriate tasks before returning. + // After this call, subsequent calls to post tasks will fail. + // + // Must be called from the same thread this object was constructed on. + void Shutdown(); + + protected: + virtual ~SequencedWorkerPool(); + + virtual void OnDestruct() const OVERRIDE; + + private: + friend class RefCountedThreadSafe<SequencedWorkerPool>; + friend class DeleteHelper<SequencedWorkerPool>; + + class Worker; + + const scoped_refptr<MessageLoopProxy> constructor_message_loop_; + + // Avoid pulling in too many headers by putting (almost) everything + // into |inner_|. + const scoped_ptr<Inner> inner_; + + DISALLOW_COPY_AND_ASSIGN(SequencedWorkerPool); +}; + +} // namespace base + +#endif // BASE_THREADING_SEQUENCED_WORKER_POOL_H_
diff --git a/src/base/threading/sequenced_worker_pool_unittest.cc b/src/base/threading/sequenced_worker_pool_unittest.cc new file mode 100644 index 0000000..2dcda60 --- /dev/null +++ b/src/base/threading/sequenced_worker_pool_unittest.cc
@@ -0,0 +1,752 @@ +// 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/threading/sequenced_worker_pool.h" + +#include <algorithm> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/test/sequenced_worker_pool_owner.h" +#include "base/test/sequenced_task_runner_test_template.h" +#include "base/test/task_runner_test_template.h" +#include "base/threading/platform_thread.h" +#include "base/tracked_objects.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +// IMPORTANT NOTE: +// +// Many of these tests have failure modes where they'll hang forever. These +// tests should not be flaky, and hangling indicates a type of failure. Do not +// mark as flaky if they're hanging, it's likely an actual bug. + +namespace { + +const size_t kNumWorkerThreads = 3; + +// Allows a number of threads to all be blocked on the same event, and +// provides a way to unblock a certain number of them. +class ThreadBlocker { + public: + ThreadBlocker() : lock_(), cond_var_(&lock_), unblock_counter_(0) {} + + void Block() { + { + base::AutoLock lock(lock_); + while (unblock_counter_ == 0) + cond_var_.Wait(); + unblock_counter_--; + } + cond_var_.Signal(); + } + + void Unblock(size_t count) { + { + base::AutoLock lock(lock_); + DCHECK(unblock_counter_ == 0); + unblock_counter_ = count; + } + cond_var_.Signal(); + } + + private: + base::Lock lock_; + base::ConditionVariable cond_var_; + + size_t unblock_counter_; +}; + +class TestTracker : public base::RefCountedThreadSafe<TestTracker> { + public: + TestTracker() + : lock_(), + cond_var_(&lock_), + started_events_(0) { + } + + // Each of these tasks appends the argument to the complete sequence vector + // so calling code can see what order they finished in. + void FastTask(int id) { + SignalWorkerDone(id); + } + + void SlowTask(int id) { + base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); + SignalWorkerDone(id); + } + + void BlockTask(int id, ThreadBlocker* blocker) { + // Note that this task has started and signal anybody waiting for that + // to happen. + { + base::AutoLock lock(lock_); + started_events_++; + } + cond_var_.Signal(); + + blocker->Block(); + SignalWorkerDone(id); + } + + // Waits until the given number of tasks have started executing. + void WaitUntilTasksBlocked(size_t count) { + { + base::AutoLock lock(lock_); + while (started_events_ < count) + cond_var_.Wait(); + } + cond_var_.Signal(); + } + + // Blocks the current thread until at least the given number of tasks are in + // the completed vector, and then returns a copy. + std::vector<int> WaitUntilTasksComplete(size_t num_tasks) { + std::vector<int> ret; + { + base::AutoLock lock(lock_); + while (complete_sequence_.size() < num_tasks) + cond_var_.Wait(); + ret = complete_sequence_; + } + cond_var_.Signal(); + return ret; + } + + void ClearCompleteSequence() { + base::AutoLock lock(lock_); + complete_sequence_.clear(); + started_events_ = 0; + } + + private: + friend class base::RefCountedThreadSafe<TestTracker>; + ~TestTracker() {} + + void SignalWorkerDone(int id) { + { + base::AutoLock lock(lock_); + complete_sequence_.push_back(id); + } + cond_var_.Signal(); + } + + // Protects the complete_sequence. + base::Lock lock_; + + base::ConditionVariable cond_var_; + + // Protected by lock_. + std::vector<int> complete_sequence_; + + // Counter of the number of "block" workers that have started. + size_t started_events_; +}; + +class SequencedWorkerPoolTest : public testing::Test { + public: + SequencedWorkerPoolTest() + : pool_owner_(kNumWorkerThreads, "test"), + tracker_(new TestTracker) { + } + + virtual ~SequencedWorkerPoolTest() {} + + virtual void SetUp() OVERRIDE {} + + virtual void TearDown() OVERRIDE { + pool()->Shutdown(); + } + + const scoped_refptr<SequencedWorkerPool>& pool() { + return pool_owner_.pool(); + } + TestTracker* tracker() { return tracker_.get(); } + + void SetWillWaitForShutdownCallback(const Closure& callback) { + pool_owner_.SetWillWaitForShutdownCallback(callback); + } + + // Ensures that the given number of worker threads is created by adding + // tasks and waiting until they complete. Worker thread creation is + // serialized, can happen on background threads asynchronously, and doesn't + // happen any more at shutdown. This means that if a test posts a bunch of + // tasks and calls shutdown, fewer workers will be created than the test may + // expect. + // + // This function ensures that this condition can't happen so tests can make + // assumptions about the number of workers active. See the comment in + // PrepareToStartAdditionalThreadIfNecessary in the .cc file for more + // details. + // + // It will post tasks to the queue with id -1. It also assumes this is the + // first thing called in a test since it will clear the complete_sequence_. + void EnsureAllWorkersCreated() { + // Create a bunch of threads, all waiting. This will cause that may + // workers to be created. + ThreadBlocker blocker; + for (size_t i = 0; i < kNumWorkerThreads; i++) { + pool()->PostWorkerTask(FROM_HERE, + base::Bind(&TestTracker::BlockTask, + tracker(), -1, &blocker)); + } + tracker()->WaitUntilTasksBlocked(kNumWorkerThreads); + + // Now wake them up and wait until they're done. + blocker.Unblock(kNumWorkerThreads); + tracker()->WaitUntilTasksComplete(kNumWorkerThreads); + + // Clean up the task IDs we added. + tracker()->ClearCompleteSequence(); + } + + int has_work_call_count() const { + return pool_owner_.has_work_call_count(); + } + + private: + MessageLoop message_loop_; + SequencedWorkerPoolOwner pool_owner_; + const scoped_refptr<TestTracker> tracker_; +}; + +// Checks that the given number of entries are in the tasks to complete of +// the given tracker, and then signals the given event the given number of +// times. This is used to wakt up blocked background threads before blocking +// on shutdown. +void EnsureTasksToCompleteCountAndUnblock(scoped_refptr<TestTracker> tracker, + size_t expected_tasks_to_complete, + ThreadBlocker* blocker, + size_t threads_to_awake) { + EXPECT_EQ( + expected_tasks_to_complete, + tracker->WaitUntilTasksComplete(expected_tasks_to_complete).size()); + + blocker->Unblock(threads_to_awake); +} + +// Tests that same-named tokens have the same ID. +TEST_F(SequencedWorkerPoolTest, NamedTokens) { + const std::string name1("hello"); + SequencedWorkerPool::SequenceToken token1 = + pool()->GetNamedSequenceToken(name1); + + SequencedWorkerPool::SequenceToken token2 = pool()->GetSequenceToken(); + + const std::string name3("goodbye"); + SequencedWorkerPool::SequenceToken token3 = + pool()->GetNamedSequenceToken(name3); + + // All 3 tokens should be different. + EXPECT_FALSE(token1.Equals(token2)); + EXPECT_FALSE(token1.Equals(token3)); + EXPECT_FALSE(token2.Equals(token3)); + + // Requesting the same name again should give the same value. + SequencedWorkerPool::SequenceToken token1again = + pool()->GetNamedSequenceToken(name1); + EXPECT_TRUE(token1.Equals(token1again)); + + SequencedWorkerPool::SequenceToken token3again = + pool()->GetNamedSequenceToken(name3); + EXPECT_TRUE(token3.Equals(token3again)); +} + +// Tests that posting a bunch of tasks (many more than the number of worker +// threads) runs them all. +TEST_F(SequencedWorkerPoolTest, LotsOfTasks) { + pool()->PostWorkerTask(FROM_HERE, + base::Bind(&TestTracker::SlowTask, tracker(), 0)); + + const size_t kNumTasks = 20; + for (size_t i = 1; i < kNumTasks; i++) { + pool()->PostWorkerTask(FROM_HERE, + base::Bind(&TestTracker::FastTask, tracker(), i)); + } + + std::vector<int> result = tracker()->WaitUntilTasksComplete(kNumTasks); + EXPECT_EQ(kNumTasks, result.size()); +} + +// Tests that posting a bunch of tasks (many more than the number of +// worker threads) to two pools simultaneously runs them all twice. +// This test is meant to shake out any concurrency issues between +// pools (like histograms). +TEST_F(SequencedWorkerPoolTest, LotsOfTasksTwoPools) { + SequencedWorkerPoolOwner pool1(kNumWorkerThreads, "test1"); + SequencedWorkerPoolOwner pool2(kNumWorkerThreads, "test2"); + + base::Closure slow_task = base::Bind(&TestTracker::SlowTask, tracker(), 0); + pool1.pool()->PostWorkerTask(FROM_HERE, slow_task); + pool2.pool()->PostWorkerTask(FROM_HERE, slow_task); + + const size_t kNumTasks = 20; + for (size_t i = 1; i < kNumTasks; i++) { + base::Closure fast_task = + base::Bind(&TestTracker::FastTask, tracker(), i); + pool1.pool()->PostWorkerTask(FROM_HERE, fast_task); + pool2.pool()->PostWorkerTask(FROM_HERE, fast_task); + } + + std::vector<int> result = + tracker()->WaitUntilTasksComplete(2*kNumTasks); + EXPECT_EQ(2 * kNumTasks, result.size()); + + pool2.pool()->Shutdown(); + pool1.pool()->Shutdown(); +} + +// Test that tasks with the same sequence token are executed in order but don't +// affect other tasks. +TEST_F(SequencedWorkerPoolTest, Sequence) { + // Fill all the worker threads except one. + const size_t kNumBackgroundTasks = kNumWorkerThreads - 1; + ThreadBlocker background_blocker; + for (size_t i = 0; i < kNumBackgroundTasks; i++) { + pool()->PostWorkerTask(FROM_HERE, + base::Bind(&TestTracker::BlockTask, + tracker(), i, &background_blocker)); + } + tracker()->WaitUntilTasksBlocked(kNumBackgroundTasks); + + // Create two tasks with the same sequence token, one that will block on the + // event, and one which will just complete quickly when it's run. Since there + // is one worker thread free, the first task will start and then block, and + // the second task should be waiting. + ThreadBlocker blocker; + SequencedWorkerPool::SequenceToken token1 = pool()->GetSequenceToken(); + pool()->PostSequencedWorkerTask( + token1, FROM_HERE, + base::Bind(&TestTracker::BlockTask, tracker(), 100, &blocker)); + pool()->PostSequencedWorkerTask( + token1, FROM_HERE, + base::Bind(&TestTracker::FastTask, tracker(), 101)); + EXPECT_EQ(0u, tracker()->WaitUntilTasksComplete(0).size()); + + // Create another two tasks as above with a different token. These will be + // blocked since there are no slots to run. + SequencedWorkerPool::SequenceToken token2 = pool()->GetSequenceToken(); + pool()->PostSequencedWorkerTask( + token2, FROM_HERE, + base::Bind(&TestTracker::FastTask, tracker(), 200)); + pool()->PostSequencedWorkerTask( + token2, FROM_HERE, + base::Bind(&TestTracker::FastTask, tracker(), 201)); + EXPECT_EQ(0u, tracker()->WaitUntilTasksComplete(0).size()); + + // Let one background task complete. This should then let both tasks of + // token2 run to completion in order. The second task of token1 should still + // be blocked. + background_blocker.Unblock(1); + std::vector<int> result = tracker()->WaitUntilTasksComplete(3); + ASSERT_EQ(3u, result.size()); + EXPECT_EQ(200, result[1]); + EXPECT_EQ(201, result[2]); + + // Finish the rest of the background tasks. This should leave some workers + // free with the second token1 task still blocked on the first. + background_blocker.Unblock(kNumBackgroundTasks - 1); + EXPECT_EQ(kNumBackgroundTasks + 2, + tracker()->WaitUntilTasksComplete(kNumBackgroundTasks + 2).size()); + + // Allow the first task of token1 to complete. This should run the second. + blocker.Unblock(1); + result = tracker()->WaitUntilTasksComplete(kNumBackgroundTasks + 4); + ASSERT_EQ(kNumBackgroundTasks + 4, result.size()); + EXPECT_EQ(100, result[result.size() - 2]); + EXPECT_EQ(101, result[result.size() - 1]); +} + +// Tests that any tasks posted after Shutdown are ignored. +TEST_F(SequencedWorkerPoolTest, IgnoresAfterShutdown) { + // Start tasks to take all the threads and block them. + EnsureAllWorkersCreated(); + ThreadBlocker blocker; + for (size_t i = 0; i < kNumWorkerThreads; i++) { + pool()->PostWorkerTask(FROM_HERE, + base::Bind(&TestTracker::BlockTask, + tracker(), i, &blocker)); + } + tracker()->WaitUntilTasksBlocked(kNumWorkerThreads); + + // Shutdown the worker pool. This should discard all non-blocking tasks. + SetWillWaitForShutdownCallback( + base::Bind(&EnsureTasksToCompleteCountAndUnblock, + scoped_refptr<TestTracker>(tracker()), 0, + &blocker, kNumWorkerThreads)); + pool()->Shutdown(); + + int old_has_work_call_count = has_work_call_count(); + + std::vector<int> result = + tracker()->WaitUntilTasksComplete(kNumWorkerThreads); + + // The kNumWorkerThread items should have completed, in no particular + // order. + ASSERT_EQ(kNumWorkerThreads, result.size()); + for (size_t i = 0; i < kNumWorkerThreads; i++) { + EXPECT_TRUE(std::find(result.begin(), result.end(), static_cast<int>(i)) != + result.end()); + } + + // No further tasks, regardless of shutdown mode, should be allowed. + EXPECT_FALSE(pool()->PostWorkerTaskWithShutdownBehavior( + FROM_HERE, + base::Bind(&TestTracker::FastTask, tracker(), 100), + SequencedWorkerPool::CONTINUE_ON_SHUTDOWN)); + EXPECT_FALSE(pool()->PostWorkerTaskWithShutdownBehavior( + FROM_HERE, + base::Bind(&TestTracker::FastTask, tracker(), 101), + SequencedWorkerPool::SKIP_ON_SHUTDOWN)); + EXPECT_FALSE(pool()->PostWorkerTaskWithShutdownBehavior( + FROM_HERE, + base::Bind(&TestTracker::FastTask, tracker(), 102), + SequencedWorkerPool::BLOCK_SHUTDOWN)); + + ASSERT_EQ(old_has_work_call_count, has_work_call_count()); +} + +// Tests that unrun tasks are discarded properly according to their shutdown +// mode. +TEST_F(SequencedWorkerPoolTest, DiscardOnShutdown) { + // Start tasks to take all the threads and block them. + EnsureAllWorkersCreated(); + ThreadBlocker blocker; + for (size_t i = 0; i < kNumWorkerThreads; i++) { + pool()->PostWorkerTask(FROM_HERE, + base::Bind(&TestTracker::BlockTask, + tracker(), i, &blocker)); + } + tracker()->WaitUntilTasksBlocked(kNumWorkerThreads); + + // Create some tasks with different shutdown modes. + pool()->PostWorkerTaskWithShutdownBehavior( + FROM_HERE, + base::Bind(&TestTracker::FastTask, tracker(), 100), + SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); + pool()->PostWorkerTaskWithShutdownBehavior( + FROM_HERE, + base::Bind(&TestTracker::FastTask, tracker(), 101), + SequencedWorkerPool::SKIP_ON_SHUTDOWN); + pool()->PostWorkerTaskWithShutdownBehavior( + FROM_HERE, + base::Bind(&TestTracker::FastTask, tracker(), 102), + SequencedWorkerPool::BLOCK_SHUTDOWN); + + // Shutdown the worker pool. This should discard all non-blocking tasks. + SetWillWaitForShutdownCallback( + base::Bind(&EnsureTasksToCompleteCountAndUnblock, + scoped_refptr<TestTracker>(tracker()), 0, + &blocker, kNumWorkerThreads)); + pool()->Shutdown(); + + std::vector<int> result = + tracker()->WaitUntilTasksComplete(kNumWorkerThreads + 1); + + // The kNumWorkerThread items should have completed, plus the BLOCK_SHUTDOWN + // one, in no particular order. + ASSERT_EQ(kNumWorkerThreads + 1, result.size()); + for (size_t i = 0; i < kNumWorkerThreads; i++) { + EXPECT_TRUE(std::find(result.begin(), result.end(), static_cast<int>(i)) != + result.end()); + } + EXPECT_TRUE(std::find(result.begin(), result.end(), 102) != result.end()); +} + +// Tests that CONTINUE_ON_SHUTDOWN tasks don't block shutdown. +TEST_F(SequencedWorkerPoolTest, ContinueOnShutdown) { + scoped_refptr<TaskRunner> runner(pool()->GetTaskRunnerWithShutdownBehavior( + SequencedWorkerPool::CONTINUE_ON_SHUTDOWN)); + scoped_refptr<SequencedTaskRunner> sequenced_runner( + pool()->GetSequencedTaskRunnerWithShutdownBehavior( + pool()->GetSequenceToken(), + SequencedWorkerPool::CONTINUE_ON_SHUTDOWN)); + EnsureAllWorkersCreated(); + ThreadBlocker blocker; + pool()->PostWorkerTaskWithShutdownBehavior( + FROM_HERE, + base::Bind(&TestTracker::BlockTask, + tracker(), 0, &blocker), + SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); + runner->PostTask( + FROM_HERE, + base::Bind(&TestTracker::BlockTask, + tracker(), 1, &blocker)); + sequenced_runner->PostTask( + FROM_HERE, + base::Bind(&TestTracker::BlockTask, + tracker(), 2, &blocker)); + + tracker()->WaitUntilTasksBlocked(3); + + // This should not block. If this test hangs, it means it failed. + pool()->Shutdown(); + + // The task should not have completed yet. + EXPECT_EQ(0u, tracker()->WaitUntilTasksComplete(0).size()); + + // Posting more tasks should fail. + EXPECT_FALSE(pool()->PostWorkerTaskWithShutdownBehavior( + FROM_HERE, base::Bind(&TestTracker::FastTask, tracker(), 0), + SequencedWorkerPool::CONTINUE_ON_SHUTDOWN)); + EXPECT_FALSE(runner->PostTask( + FROM_HERE, base::Bind(&TestTracker::FastTask, tracker(), 0))); + EXPECT_FALSE(sequenced_runner->PostTask( + FROM_HERE, base::Bind(&TestTracker::FastTask, tracker(), 0))); + + // Continue the background thread and make sure the tasks can complete. + blocker.Unblock(3); + std::vector<int> result = tracker()->WaitUntilTasksComplete(3); + EXPECT_EQ(3u, result.size()); +} + +// Tests that SKIP_ON_SHUTDOWN tasks that have been started block Shutdown +// until they stop, but tasks not yet started do not. +TEST_F(SequencedWorkerPoolTest, SkipOnShutdown) { + // Start tasks to take all the threads and block them. + EnsureAllWorkersCreated(); + ThreadBlocker blocker; + + // Now block all the threads with SKIP_ON_SHUTDOWN. Shutdown() should not + // return until these tasks have completed. + for (size_t i = 0; i < kNumWorkerThreads; i++) { + pool()->PostWorkerTaskWithShutdownBehavior( + FROM_HERE, + base::Bind(&TestTracker::BlockTask, tracker(), i, &blocker), + SequencedWorkerPool::SKIP_ON_SHUTDOWN); + } + tracker()->WaitUntilTasksBlocked(kNumWorkerThreads); + + // Now post an additional task as SKIP_ON_SHUTDOWN, which should not be + // executed once Shutdown() has been called. + pool()->PostWorkerTaskWithShutdownBehavior( + FROM_HERE, + base::Bind(&TestTracker::BlockTask, + tracker(), 0, &blocker), + SequencedWorkerPool::SKIP_ON_SHUTDOWN); + + // This callback will only be invoked if SKIP_ON_SHUTDOWN tasks that have + // been started block shutdown. + SetWillWaitForShutdownCallback( + base::Bind(&EnsureTasksToCompleteCountAndUnblock, + scoped_refptr<TestTracker>(tracker()), 0, + &blocker, kNumWorkerThreads)); + + // No tasks should have completed yet. + EXPECT_EQ(0u, tracker()->WaitUntilTasksComplete(0).size()); + + // This should not block. If this test hangs, it means it failed. + pool()->Shutdown(); + + // Shutdown should not return until all of the tasks have completed. + std::vector<int> result = + tracker()->WaitUntilTasksComplete(kNumWorkerThreads); + + // Only tasks marked SKIP_ON_SHUTDOWN that were already started should be + // allowed to complete. No additional non-blocking tasks should have been + // started. + ASSERT_EQ(kNumWorkerThreads, result.size()); + for (size_t i = 0; i < kNumWorkerThreads; i++) { + EXPECT_TRUE(std::find(result.begin(), result.end(), static_cast<int>(i)) != + result.end()); + } +} + +// Ensure all worker threads are created, and then trigger a spurious +// work signal. This shouldn't cause any other work signals to be +// triggered. This is a regression test for http://crbug.com/117469. +TEST_F(SequencedWorkerPoolTest, SpuriousWorkSignal) { + EnsureAllWorkersCreated(); + int old_has_work_call_count = has_work_call_count(); + pool()->SignalHasWorkForTesting(); + // This is inherently racy, but can only produce false positives. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); + EXPECT_EQ(old_has_work_call_count + 1, has_work_call_count()); +} + +void IsRunningOnCurrentThreadTask( + SequencedWorkerPool::SequenceToken test_positive_token, + SequencedWorkerPool::SequenceToken test_negative_token, + SequencedWorkerPool* pool, + SequencedWorkerPool* unused_pool) { + EXPECT_TRUE(pool->RunsTasksOnCurrentThread()); + EXPECT_TRUE(pool->IsRunningSequenceOnCurrentThread(test_positive_token)); + EXPECT_FALSE(pool->IsRunningSequenceOnCurrentThread(test_negative_token)); + EXPECT_FALSE(unused_pool->RunsTasksOnCurrentThread()); + EXPECT_FALSE( + unused_pool->IsRunningSequenceOnCurrentThread(test_positive_token)); + EXPECT_FALSE( + unused_pool->IsRunningSequenceOnCurrentThread(test_negative_token)); +} + +// Verify correctness of the IsRunningSequenceOnCurrentThread method. +TEST_F(SequencedWorkerPoolTest, IsRunningOnCurrentThread) { + SequencedWorkerPool::SequenceToken token1 = pool()->GetSequenceToken(); + SequencedWorkerPool::SequenceToken token2 = pool()->GetSequenceToken(); + SequencedWorkerPool::SequenceToken unsequenced_token; + + scoped_refptr<SequencedWorkerPool> unused_pool = + new SequencedWorkerPool(2, "unused_pool"); + EXPECT_TRUE(token1.Equals(unused_pool->GetSequenceToken())); + EXPECT_TRUE(token2.Equals(unused_pool->GetSequenceToken())); + + EXPECT_FALSE(pool()->RunsTasksOnCurrentThread()); + EXPECT_FALSE(pool()->IsRunningSequenceOnCurrentThread(token1)); + EXPECT_FALSE(pool()->IsRunningSequenceOnCurrentThread(token2)); + EXPECT_FALSE(pool()->IsRunningSequenceOnCurrentThread(unsequenced_token)); + EXPECT_FALSE(unused_pool->RunsTasksOnCurrentThread()); + EXPECT_FALSE(unused_pool->IsRunningSequenceOnCurrentThread(token1)); + EXPECT_FALSE(unused_pool->IsRunningSequenceOnCurrentThread(token2)); + EXPECT_FALSE( + unused_pool->IsRunningSequenceOnCurrentThread(unsequenced_token)); + + pool()->PostSequencedWorkerTask( + token1, FROM_HERE, + base::Bind(&IsRunningOnCurrentThreadTask, + token1, token2, pool(), unused_pool)); + pool()->PostSequencedWorkerTask( + token2, FROM_HERE, + base::Bind(&IsRunningOnCurrentThreadTask, + token2, unsequenced_token, pool(), unused_pool)); + pool()->PostWorkerTask( + FROM_HERE, + base::Bind(&IsRunningOnCurrentThreadTask, + unsequenced_token, token1, pool(), unused_pool)); + pool()->Shutdown(); + unused_pool->Shutdown(); +} + +class SequencedWorkerPoolTaskRunnerTestDelegate { + public: + SequencedWorkerPoolTaskRunnerTestDelegate() {} + + ~SequencedWorkerPoolTaskRunnerTestDelegate() {} + + void StartTaskRunner() { + pool_owner_.reset( + new SequencedWorkerPoolOwner(10, "SequencedWorkerPoolTaskRunnerTest")); + } + + scoped_refptr<SequencedWorkerPool> GetTaskRunner() { + return pool_owner_->pool(); + } + + void StopTaskRunner() { + // Make sure all tasks (including delayed ones) are run before shutting + // down. + pool_owner_->pool()->FlushForTesting(); + pool_owner_->pool()->Shutdown(); + // Don't reset |pool_owner_| here, as the test may still hold a + // reference to the pool. + } + + bool TaskRunnerHandlesNonZeroDelays() const { + return true; + } + + private: + MessageLoop message_loop_; + scoped_ptr<SequencedWorkerPoolOwner> pool_owner_; +}; + +INSTANTIATE_TYPED_TEST_CASE_P( + SequencedWorkerPool, TaskRunnerTest, + SequencedWorkerPoolTaskRunnerTestDelegate); + +class SequencedWorkerPoolTaskRunnerWithShutdownBehaviorTestDelegate { + public: + SequencedWorkerPoolTaskRunnerWithShutdownBehaviorTestDelegate() {} + + ~SequencedWorkerPoolTaskRunnerWithShutdownBehaviorTestDelegate() { + } + + void StartTaskRunner() { + pool_owner_.reset( + new SequencedWorkerPoolOwner(10, "SequencedWorkerPoolTaskRunnerTest")); + task_runner_ = pool_owner_->pool()->GetTaskRunnerWithShutdownBehavior( + SequencedWorkerPool::BLOCK_SHUTDOWN); + } + + scoped_refptr<TaskRunner> GetTaskRunner() { + return task_runner_; + } + + void StopTaskRunner() { + // Make sure all tasks (including delayed ones) are run before shutting + // down. + pool_owner_->pool()->FlushForTesting(); + pool_owner_->pool()->Shutdown(); + // Don't reset |pool_owner_| here, as the test may still hold a + // reference to the pool. + } + + bool TaskRunnerHandlesNonZeroDelays() const { + return true; + } + + private: + MessageLoop message_loop_; + scoped_ptr<SequencedWorkerPoolOwner> pool_owner_; + scoped_refptr<TaskRunner> task_runner_; +}; + +INSTANTIATE_TYPED_TEST_CASE_P( + SequencedWorkerPoolTaskRunner, TaskRunnerTest, + SequencedWorkerPoolTaskRunnerWithShutdownBehaviorTestDelegate); + +class SequencedWorkerPoolSequencedTaskRunnerTestDelegate { + public: + SequencedWorkerPoolSequencedTaskRunnerTestDelegate() {} + + ~SequencedWorkerPoolSequencedTaskRunnerTestDelegate() { + } + + void StartTaskRunner() { + pool_owner_.reset(new SequencedWorkerPoolOwner( + 10, "SequencedWorkerPoolSequencedTaskRunnerTest")); + task_runner_ = pool_owner_->pool()->GetSequencedTaskRunner( + pool_owner_->pool()->GetSequenceToken()); + } + + scoped_refptr<SequencedTaskRunner> GetTaskRunner() { + return task_runner_; + } + + void StopTaskRunner() { + // Make sure all tasks (including delayed ones) are run before shutting + // down. + pool_owner_->pool()->FlushForTesting(); + pool_owner_->pool()->Shutdown(); + // Don't reset |pool_owner_| here, as the test may still hold a + // reference to the pool. + } + + bool TaskRunnerHandlesNonZeroDelays() const { + return true; + } + + private: + MessageLoop message_loop_; + scoped_ptr<SequencedWorkerPoolOwner> pool_owner_; + scoped_refptr<SequencedTaskRunner> task_runner_; +}; + +INSTANTIATE_TYPED_TEST_CASE_P( + SequencedWorkerPoolSequencedTaskRunner, TaskRunnerTest, + SequencedWorkerPoolSequencedTaskRunnerTestDelegate); + +INSTANTIATE_TYPED_TEST_CASE_P( + SequencedWorkerPoolSequencedTaskRunner, SequencedTaskRunnerTest, + SequencedWorkerPoolSequencedTaskRunnerTestDelegate); + +} // namespace + +} // namespace base
diff --git a/src/base/threading/simple_thread.cc b/src/base/threading/simple_thread.cc new file mode 100644 index 0000000..b8d7713 --- /dev/null +++ b/src/base/threading/simple_thread.cc
@@ -0,0 +1,168 @@ +// Copyright (c) 2010 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/threading/simple_thread.h" + +#include "base/logging.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread_restrictions.h" +#include "base/string_number_conversions.h" + +namespace base { + +SimpleThread::SimpleThread(const std::string& name_prefix) + : name_prefix_(name_prefix), name_(name_prefix), + thread_(), event_(true, false), tid_(0), joined_(false) { +} + +SimpleThread::SimpleThread(const std::string& name_prefix, + const Options& options) + : name_prefix_(name_prefix), name_(name_prefix), options_(options), + thread_(), event_(true, false), tid_(0), joined_(false) { +} + +SimpleThread::~SimpleThread() { + DCHECK(HasBeenStarted()) << "SimpleThread was never started."; + DCHECK(HasBeenJoined()) << "SimpleThread destroyed without being Join()ed."; +} + +void SimpleThread::Start() { + DCHECK(!HasBeenStarted()) << "Tried to Start a thread multiple times."; +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) + PlatformThread::PlatformThreadOptions platform_options; + platform_options.stack_size = options_.stack_size(); + platform_options.priority = options_.priority(); + platform_options.affinity = options_.affinity(); + bool success = PlatformThread::CreateWithOptions(platform_options, + this, &thread_); +#else + bool success = PlatformThread::Create(options_.stack_size(), this, &thread_); +#endif + DCHECK(success); + base::ThreadRestrictions::ScopedAllowWait allow_wait; + event_.Wait(); // Wait for the thread to complete initialization. +} + +void SimpleThread::Join() { + DCHECK(HasBeenStarted()) << "Tried to Join a never-started thread."; + DCHECK(!HasBeenJoined()) << "Tried to Join a thread multiple times."; + PlatformThread::Join(thread_); + joined_ = true; +} + +bool SimpleThread::HasBeenStarted() { + base::ThreadRestrictions::ScopedAllowWait allow_wait; + return event_.IsSignaled(); +} + +void SimpleThread::ThreadMain() { + tid_ = PlatformThread::CurrentId(); + // Construct our full name of the form "name_prefix_/TID". + name_.push_back('/'); + name_.append(IntToString(tid_)); + PlatformThread::SetName(name_.c_str()); + + // We've initialized our new thread, signal that we're done to Start(). + event_.Signal(); + + Run(); +} + +DelegateSimpleThread::DelegateSimpleThread(Delegate* delegate, + const std::string& name_prefix) + : SimpleThread(name_prefix), + delegate_(delegate) { +} + +DelegateSimpleThread::DelegateSimpleThread(Delegate* delegate, + const std::string& name_prefix, + const Options& options) + : SimpleThread(name_prefix, options), + delegate_(delegate) { +} + +DelegateSimpleThread::~DelegateSimpleThread() { +} + +void DelegateSimpleThread::Run() { + DCHECK(delegate_) << "Tried to call Run without a delegate (called twice?)"; + delegate_->Run(); + delegate_ = NULL; +} + +DelegateSimpleThreadPool::DelegateSimpleThreadPool( + const std::string& name_prefix, + int num_threads) + : name_prefix_(name_prefix), + num_threads_(num_threads), + dry_(true, false) { +} + +DelegateSimpleThreadPool::~DelegateSimpleThreadPool() { + DCHECK(threads_.empty()); + DCHECK(delegates_.empty()); + DCHECK(!dry_.IsSignaled()); +} + +void DelegateSimpleThreadPool::Start() { + DCHECK(threads_.empty()) << "Start() called with outstanding threads."; + for (int i = 0; i < num_threads_; ++i) { + DelegateSimpleThread* thread = new DelegateSimpleThread(this, name_prefix_); + thread->Start(); + threads_.push_back(thread); + } +} + +void DelegateSimpleThreadPool::JoinAll() { + DCHECK(!threads_.empty()) << "JoinAll() called with no outstanding threads."; + + // Tell all our threads to quit their worker loop. + AddWork(NULL, num_threads_); + + // Join and destroy all the worker threads. + for (int i = 0; i < num_threads_; ++i) { + threads_[i]->Join(); + delete threads_[i]; + } + threads_.clear(); + DCHECK(delegates_.empty()); +} + +void DelegateSimpleThreadPool::AddWork(Delegate* delegate, int repeat_count) { + AutoLock locked(lock_); + for (int i = 0; i < repeat_count; ++i) + delegates_.push(delegate); + // If we were empty, signal that we have work now. + if (!dry_.IsSignaled()) + dry_.Signal(); +} + +void DelegateSimpleThreadPool::Run() { + Delegate* work = NULL; + + while (true) { + dry_.Wait(); + { + AutoLock locked(lock_); + if (!dry_.IsSignaled()) + continue; + + DCHECK(!delegates_.empty()); + work = delegates_.front(); + delegates_.pop(); + + // Signal to any other threads that we're currently out of work. + if (delegates_.empty()) + dry_.Reset(); + } + + // A NULL delegate pointer signals us to quit. + if (!work) + break; + + work->Run(); + } +} + +} // namespace base
diff --git a/src/base/threading/simple_thread.h b/src/base/threading/simple_thread.h new file mode 100644 index 0000000..2594b18 --- /dev/null +++ b/src/base/threading/simple_thread.h
@@ -0,0 +1,210 @@ +// 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. + +// WARNING: You should probably be using Thread (thread.h) instead. Thread is +// Chrome's message-loop based Thread abstraction, and if you are a +// thread running in the browser, there will likely be assumptions +// that your thread will have an associated message loop. +// +// This is a simple thread interface that backs to a native operating system +// thread. You should use this only when you want a thread that does not have +// an associated MessageLoop. Unittesting is the best example of this. +// +// The simplest interface to use is DelegateSimpleThread, which will create +// a new thread, and execute the Delegate's virtual Run() in this new thread +// until it has completed, exiting the thread. +// +// NOTE: You *MUST* call Join on the thread to clean up the underlying thread +// resources. You are also responsible for destructing the SimpleThread object. +// It is invalid to destroy a SimpleThread while it is running, or without +// Start() having been called (and a thread never created). The Delegate +// object should live as long as a DelegateSimpleThread. +// +// Thread Safety: A SimpleThread is not completely thread safe. It is safe to +// access it from the creating thread or from the newly created thread. This +// implies that the creator thread should be the thread that calls Join. +// +// Example: +// class MyThreadRunner : public DelegateSimpleThread::Delegate { ... }; +// MyThreadRunner runner; +// DelegateSimpleThread thread(&runner, "good_name_here"); +// thread.Start(); +// // Start will return after the Thread has been successfully started and +// // initialized. The newly created thread will invoke runner->Run(), and +// // run until it returns. +// thread.Join(); // Wait until the thread has exited. You *MUST* Join! +// // The SimpleThread object is still valid, however you may not call Join +// // or Start again. + +#ifndef BASE_THREADING_SIMPLE_THREAD_H_ +#define BASE_THREADING_SIMPLE_THREAD_H_ + +#include <string> +#include <queue> +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/threading/platform_thread.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" + +namespace base { + +// This is the base SimpleThread. You can derive from it and implement the +// virtual Run method, or you can use the DelegateSimpleThread interface. +class BASE_EXPORT SimpleThread : public PlatformThread::Delegate { + public: + class BASE_EXPORT Options { + public: + Options() : stack_size_(0), priority_(kThreadPriority_Default), + affinity_(kNoThreadAffinity) { } + Options(size_t stack_size, ThreadPriority priority) + : stack_size_(stack_size), priority_(priority), + affinity_(kNoThreadAffinity) { } + Options(size_t stack_size, ThreadPriority priority, ThreadAffinity affinity) + : stack_size_(stack_size), priority_(priority), affinity_(affinity) { } + ~Options() { } + + // We use the standard compiler-supplied copy constructor. + + // A custom stack size, or 0 for the system default. + void set_stack_size(size_t size) { stack_size_ = size; } + size_t stack_size() const { return stack_size_; } + + // Thread priority + void set_priority(ThreadPriority priority) { priority_ = priority; } + ThreadPriority priority() const { return priority_; } + + // Core affinity + void set_affinity(ThreadAffinity affinity) { affinity_ = affinity; } + ThreadAffinity affinity() const { return affinity_; } + + private: + size_t stack_size_; + ThreadPriority priority_; + ThreadAffinity affinity_; + }; + + // Create a SimpleThread. |options| should be used to manage any specific + // configuration involving the thread creation and management. + // Every thread has a name, in the form of |name_prefix|/TID, for example + // "my_thread/321". The thread will not be created until Start() is called. + explicit SimpleThread(const std::string& name_prefix); + SimpleThread(const std::string& name_prefix, const Options& options); + + virtual ~SimpleThread(); + + virtual void Start(); + virtual void Join(); + + // Subclasses should override the Run method. + virtual void Run() = 0; + + // Return the thread name prefix, or "unnamed" if none was supplied. + std::string name_prefix() { return name_prefix_; } + + // Return the completed name including TID, only valid after Start(). + std::string name() { return name_; } + + // The native thread handle. + PlatformThreadHandle thread_handle() { return thread_; } + + // Return the thread id, only valid after Start(). + PlatformThreadId tid() { return tid_; } + + // Return True if Start() has ever been called. + bool HasBeenStarted(); + + // Return True if Join() has evern been called. + bool HasBeenJoined() { return joined_; } + + // Overridden from PlatformThread::Delegate: + virtual void ThreadMain() OVERRIDE; + + // Only set priorities with a careful understanding of the consequences. + // This is meant for very limited use cases. + void SetThreadPriority(ThreadPriority priority) { + PlatformThread::SetThreadPriority(thread_, priority); + } + + private: + const std::string name_prefix_; + std::string name_; + const Options options_; + PlatformThreadHandle thread_; // PlatformThread handle, invalid after Join! + WaitableEvent event_; // Signaled if Start() was ever called. + PlatformThreadId tid_; // The backing thread's id. + bool joined_; // True if Join has been called. +}; + +class BASE_EXPORT DelegateSimpleThread : public SimpleThread { + public: + class BASE_EXPORT Delegate { + public: + Delegate() { } + virtual ~Delegate() { } + virtual void Run() = 0; + }; + + DelegateSimpleThread(Delegate* delegate, + const std::string& name_prefix); + DelegateSimpleThread(Delegate* delegate, + const std::string& name_prefix, + const Options& options); + + virtual ~DelegateSimpleThread(); + virtual void Run() OVERRIDE; + private: + Delegate* delegate_; +}; + +// DelegateSimpleThreadPool allows you to start up a fixed number of threads, +// and then add jobs which will be dispatched to the threads. This is +// convenient when you have a lot of small work that you want done +// multi-threaded, but don't want to spawn a thread for each small bit of work. +// +// You just call AddWork() to add a delegate to the list of work to be done. +// JoinAll() will make sure that all outstanding work is processed, and wait +// for everything to finish. You can reuse a pool, so you can call Start() +// again after you've called JoinAll(). +class BASE_EXPORT DelegateSimpleThreadPool + : public DelegateSimpleThread::Delegate { + public: + typedef DelegateSimpleThread::Delegate Delegate; + + DelegateSimpleThreadPool(const std::string& name_prefix, int num_threads); + virtual ~DelegateSimpleThreadPool(); + + // Start up all of the underlying threads, and start processing work if we + // have any. + void Start(); + + // Make sure all outstanding work is finished, and wait for and destroy all + // of the underlying threads in the pool. + void JoinAll(); + + // It is safe to AddWork() any time, before or after Start(). + // Delegate* should always be a valid pointer, NULL is reserved internally. + void AddWork(Delegate* work, int repeat_count); + void AddWork(Delegate* work) { + AddWork(work, 1); + } + + // We implement the Delegate interface, for running our internal threads. + virtual void Run() OVERRIDE; + + private: + const std::string name_prefix_; + int num_threads_; + std::vector<DelegateSimpleThread*> threads_; + std::queue<Delegate*> delegates_; + base::Lock lock_; // Locks delegates_ + WaitableEvent dry_; // Not signaled when there is no work to do. +}; + +} // namespace base + +#endif // BASE_THREADING_SIMPLE_THREAD_H_
diff --git a/src/base/threading/simple_thread_unittest.cc b/src/base/threading/simple_thread_unittest.cc new file mode 100644 index 0000000..169e2d7 --- /dev/null +++ b/src/base/threading/simple_thread_unittest.cc
@@ -0,0 +1,170 @@ +// 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/atomic_sequence_num.h" +#include "base/string_number_conversions.h" +#include "base/threading/simple_thread.h" +#include "base/synchronization/waitable_event.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +class SetIntRunner : public DelegateSimpleThread::Delegate { + public: + SetIntRunner(int* ptr, int val) : ptr_(ptr), val_(val) { } + virtual ~SetIntRunner() { } + + virtual void Run() OVERRIDE { + *ptr_ = val_; + } + + private: + int* ptr_; + int val_; +}; + +class WaitEventRunner : public DelegateSimpleThread::Delegate { + public: + explicit WaitEventRunner(WaitableEvent* event) : event_(event) { } + virtual ~WaitEventRunner() { } + + virtual void Run() OVERRIDE { + EXPECT_FALSE(event_->IsSignaled()); + event_->Signal(); + EXPECT_TRUE(event_->IsSignaled()); + } + private: + WaitableEvent* event_; +}; + +class SeqRunner : public DelegateSimpleThread::Delegate { + public: + explicit SeqRunner(AtomicSequenceNumber* seq) : seq_(seq) { } + virtual void Run() OVERRIDE { + seq_->GetNext(); + } + + private: + AtomicSequenceNumber* seq_; +}; + +// We count up on a sequence number, firing on the event when we've hit our +// expected amount, otherwise we wait on the event. This will ensure that we +// have all threads outstanding until we hit our expected thread pool size. +class VerifyPoolRunner : public DelegateSimpleThread::Delegate { + public: + VerifyPoolRunner(AtomicSequenceNumber* seq, + int total, WaitableEvent* event) + : seq_(seq), total_(total), event_(event) { } + + virtual void Run() OVERRIDE { + if (seq_->GetNext() == total_) { + event_->Signal(); + } else { + event_->Wait(); + } + } + + private: + AtomicSequenceNumber* seq_; + int total_; + WaitableEvent* event_; +}; + +} // namespace + +TEST(SimpleThreadTest, CreateAndJoin) { + int stack_int = 0; + + SetIntRunner runner(&stack_int, 7); + EXPECT_EQ(0, stack_int); + + DelegateSimpleThread thread(&runner, "int_setter"); + EXPECT_FALSE(thread.HasBeenStarted()); + EXPECT_FALSE(thread.HasBeenJoined()); + EXPECT_EQ(0, stack_int); + + thread.Start(); + EXPECT_TRUE(thread.HasBeenStarted()); + EXPECT_FALSE(thread.HasBeenJoined()); + + thread.Join(); + EXPECT_TRUE(thread.HasBeenStarted()); + EXPECT_TRUE(thread.HasBeenJoined()); + EXPECT_EQ(7, stack_int); +} + +TEST(SimpleThreadTest, WaitForEvent) { + // Create a thread, and wait for it to signal us. + WaitableEvent event(true, false); + + WaitEventRunner runner(&event); + DelegateSimpleThread thread(&runner, "event_waiter"); + + EXPECT_FALSE(event.IsSignaled()); + thread.Start(); + event.Wait(); + EXPECT_TRUE(event.IsSignaled()); + thread.Join(); +} + +TEST(SimpleThreadTest, NamedWithOptions) { + WaitableEvent event(true, false); + + WaitEventRunner runner(&event); + SimpleThread::Options options; + DelegateSimpleThread thread(&runner, "event_waiter", options); + EXPECT_EQ(thread.name_prefix(), "event_waiter"); + EXPECT_FALSE(event.IsSignaled()); + + thread.Start(); + EXPECT_EQ(thread.name_prefix(), "event_waiter"); + EXPECT_EQ(thread.name(), + std::string("event_waiter/") + IntToString(thread.tid())); + event.Wait(); + + EXPECT_TRUE(event.IsSignaled()); + thread.Join(); + + // We keep the name and tid, even after the thread is gone. + EXPECT_EQ(thread.name_prefix(), "event_waiter"); + EXPECT_EQ(thread.name(), + std::string("event_waiter/") + IntToString(thread.tid())); +} + +TEST(SimpleThreadTest, ThreadPool) { + AtomicSequenceNumber seq; + SeqRunner runner(&seq); + DelegateSimpleThreadPool pool("seq_runner", 10); + + // Add work before we're running. + pool.AddWork(&runner, 300); + + EXPECT_EQ(seq.GetNext(), 0); + pool.Start(); + + // Add work while we're running. + pool.AddWork(&runner, 300); + + pool.JoinAll(); + + EXPECT_EQ(seq.GetNext(), 601); + + // We can reuse our pool. Verify that all 10 threads can actually run in + // parallel, so this test will only pass if there are actually 10 threads. + AtomicSequenceNumber seq2; + WaitableEvent event(true, false); + // Changing 9 to 10, for example, would cause us JoinAll() to never return. + VerifyPoolRunner verifier(&seq2, 9, &event); + pool.Start(); + + pool.AddWork(&verifier, 10); + + pool.JoinAll(); + EXPECT_EQ(seq2.GetNext(), 10); +} + +} // namespace base
diff --git a/src/base/threading/thread.cc b/src/base/threading/thread.cc new file mode 100644 index 0000000..c940775 --- /dev/null +++ b/src/base/threading/thread.cc
@@ -0,0 +1,233 @@ +// 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/threading/thread.h" + +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/thread_local.h" +#include "base/threading/thread_restrictions.h" +#include "base/synchronization/waitable_event.h" + +#if defined(OS_WIN) +#include "base/win/scoped_com_initializer.h" +#endif + +namespace base { + +namespace { + +// We use this thread-local variable to record whether or not a thread exited +// because its Stop method was called. This allows us to catch cases where +// MessageLoop::Quit() is called directly, which is unexpected when using a +// Thread to setup and run a MessageLoop. +base::LazyInstance<base::ThreadLocalBoolean> lazy_tls_bool = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// This is used to trigger the message loop to exit. +void ThreadQuitHelper() { + MessageLoop::current()->Quit(); + Thread::SetThreadWasQuitProperly(true); +} + +// Used to pass data to ThreadMain. This structure is allocated on the stack +// from within StartWithOptions. +struct Thread::StartupData { + // We get away with a const reference here because of how we are allocated. + const Thread::Options& options; + + // Used to synchronize thread startup. + WaitableEvent event; + + explicit StartupData(const Options& opt) + : options(opt), + event(false, false) {} +}; + +Thread::Thread(const char* name) + : +#if defined(OS_WIN) + com_status_(NONE), +#endif + started_(false), + stopping_(false), + running_(false), + startup_data_(NULL), + thread_(0), + message_loop_(NULL), + thread_id_(kInvalidThreadId), + name_(name) { +} + +Thread::~Thread() { + Stop(); +} + +bool Thread::Start() { + Options options; +#if defined(OS_WIN) + if (com_status_ == STA) + options.message_loop_type = MessageLoop::TYPE_UI; +#endif + return StartWithOptions(options); +} + +bool Thread::StartWithOptions(const Options& options) { + DCHECK(!message_loop_); +#if defined(OS_WIN) + DCHECK((com_status_ != STA) || + (options.message_loop_type == MessageLoop::TYPE_UI)); +#endif + +#if !defined(COBALT) + // BUG. This should not be called here. We are currently executing in the + // thread that is creating this thread, so this call incorrectly marks the + // calling thread as not being quit properly. Instead, set it in + // |ThreadMain|. + SetThreadWasQuitProperly(false); +#endif + + StartupData startup_data(options); + startup_data_ = &startup_data; + +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) + PlatformThread::PlatformThreadOptions platform_options; + platform_options.stack_size = options.stack_size; + platform_options.priority = options.priority; + platform_options.affinity = options.affinity; + if (!PlatformThread::CreateWithOptions(platform_options, this, &thread_)) { +#else + if (!PlatformThread::Create(options.stack_size, this, &thread_)) { +#endif + DLOG(ERROR) << "failed to create thread"; + startup_data_ = NULL; + return false; + } + + // Wait for the thread to start and initialize message_loop_ + base::ThreadRestrictions::ScopedAllowWait allow_wait; + startup_data.event.Wait(); + + // set it to NULL so we don't keep a pointer to some object on the stack. + startup_data_ = NULL; + started_ = true; + + DCHECK(message_loop_); + return true; +} + +void Thread::Stop() { + if (!started_) + return; + + StopSoon(); + + // Wait for the thread to exit. + // + // TODO(darin): Unfortunately, we need to keep message_loop_ around until + // the thread exits. Some consumers are abusing the API. Make them stop. + // + PlatformThread::Join(thread_); + + // The thread should NULL message_loop_ on exit. + DCHECK(!message_loop_); + + // The thread no longer needs to be joined. + started_ = false; + + stopping_ = false; +} + +void Thread::StopSoon() { + // We should only be called on the same thread that started us. + + // Reading thread_id_ without a lock can lead to a benign data race + // with ThreadMain, so we annotate it to stay silent under ThreadSanitizer. + DCHECK_NE(ANNOTATE_UNPROTECTED_READ(thread_id_), PlatformThread::CurrentId()); + + if (stopping_ || !message_loop_) + return; + + stopping_ = true; + message_loop_->PostTask(FROM_HERE, base::Bind(&ThreadQuitHelper)); +} + +bool Thread::IsRunning() const { + return running_; +} + +void Thread::Run(MessageLoop* message_loop) { + message_loop->Run(); +} + +void Thread::SetThreadWasQuitProperly(bool flag) { + lazy_tls_bool.Pointer()->Set(flag); +} + +bool Thread::GetThreadWasQuitProperly() { + bool quit_properly = true; +#ifndef NDEBUG + quit_properly = lazy_tls_bool.Pointer()->Get(); +#endif + return quit_properly; +} + +void Thread::ThreadMain() { + { +#if defined(COBALT) + // It is incorrect to call this in |StartWithOptions| - that would mark the + // calling thread as not being quit correctly. Instead, set it here. + SetThreadWasQuitProperly(false); +#endif + + // The message loop for this thread. + MessageLoop message_loop(startup_data_->options.message_loop_type); + + // Complete the initialization of our Thread object. + thread_id_ = PlatformThread::CurrentId(); + PlatformThread::SetName(name_.c_str()); + ANNOTATE_THREAD_NAME(name_.c_str()); // Tell the name to race detector. + message_loop.set_thread_name(name_); + message_loop_ = &message_loop; + +#if defined(OS_WIN) + scoped_ptr<win::ScopedCOMInitializer> com_initializer; + if (com_status_ != NONE) { + com_initializer.reset((com_status_ == STA) ? + new win::ScopedCOMInitializer() : + new win::ScopedCOMInitializer(win::ScopedCOMInitializer::kMTA)); + } +#endif + + // Let the thread do extra initialization. + // Let's do this before signaling we are started. + Init(); + + running_ = true; + startup_data_->event.Signal(); + // startup_data_ can't be touched anymore since the starting thread is now + // unlocked. + + Run(message_loop_); + running_ = false; + + // Let the thread do extra cleanup. + CleanUp(); + +#if defined(OS_WIN) + com_initializer.reset(); +#endif + + // Assert that MessageLoop::Quit was called by ThreadQuitHelper. + DCHECK(GetThreadWasQuitProperly()); + + // We can't receive messages anymore. + message_loop_ = NULL; + } +} + +} // namespace base
diff --git a/src/base/threading/thread.h b/src/base/threading/thread.h new file mode 100644 index 0000000..d0f89af --- /dev/null +++ b/src/base/threading/thread.h
@@ -0,0 +1,228 @@ +// 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_THREADING_THREAD_H_ +#define BASE_THREADING_THREAD_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/threading/platform_thread.h" + +namespace base { + +// 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 +// pending tasks queued on the thread's message loop will run to completion +// before the thread is terminated. +// +// WARNING! SUBCLASSES MUST CALL Stop() IN THEIR DESTRUCTORS! See ~Thread(). +// +// After the thread is stopped, the destruction sequence is: +// +// (1) Thread::CleanUp() +// (2) MessageLoop::~MessageLoop +// (3.b) MessageLoop::DestructionObserver::WillDestroyCurrentMessageLoop +class BASE_EXPORT Thread : PlatformThread::Delegate { + public: + struct Options { + Options() : message_loop_type(MessageLoop::TYPE_DEFAULT), stack_size(0) + , priority(kThreadPriority_Default), affinity(kNoThreadAffinity) {} + Options(MessageLoop::Type type, size_t size) + : message_loop_type(type), stack_size(size) + , priority(kThreadPriority_Default), affinity(kNoThreadAffinity) {} + Options(MessageLoop::Type type, size_t size, ThreadPriority priority) + : message_loop_type(type), stack_size(size), priority(priority) + , affinity(kNoThreadAffinity) {} + Options(MessageLoop::Type type, size_t size, ThreadPriority priority, + ThreadAffinity affinity) + : message_loop_type(type), stack_size(size), priority(priority) + , affinity(affinity) {} + + // Specifies the type of message loop that will be allocated on the thread. + MessageLoop::Type message_loop_type; + + // 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. + size_t stack_size; + + // Specifies the initial thread priority. + ThreadPriority priority; + + // Specifies the initial thread core affinity. + ThreadAffinity affinity; + }; + + // Constructor. + // name is a display string to identify the thread. + explicit Thread(const char* name); + + // Destroys the thread, stopping it if necessary. + // + // NOTE: ALL SUBCLASSES OF Thread MUST CALL Stop() IN THEIR DESTRUCTORS (or + // guarantee Stop() is explicitly called before the subclass is destroyed). + // This is required to avoid a data race between the destructor modifying the + // vtable, and the thread's ThreadMain calling the virtual method Run(). It + // also ensures that the CleanUp() virtual method is called on the subclass + // before it is destructed. + virtual ~Thread(); + +#if defined(OS_WIN) + // Causes the thread to initialize COM. This must be called before calling + // Start() or StartWithOptions(). If |use_mta| is false, the thread is also + // started with a TYPE_UI message loop. It is an error to call + // init_com_with_mta(false) and then StartWithOptions() with any message loop + // type other than TYPE_UI. + void init_com_with_mta(bool use_mta) { + DCHECK(!started_); + com_status_ = use_mta ? MTA : STA; + } +#endif + + // Starts the thread. Returns true if the thread was successfully started; + // otherwise, returns false. Upon successful return, the message_loop() + // getter will return non-null. + // + // Note: This function can't be called on Windows with the loader lock held; + // i.e. during a DllMain, global object construction or destruction, atexit() + // callback. + bool Start(); + + // Starts the thread. Behaves exactly like Start in addition to allow to + // override the default options. + // + // Note: This function can't be called on Windows with the loader lock held; + // i.e. during a DllMain, global object construction or destruction, atexit() + // callback. + bool StartWithOptions(const Options& options); + + // Signals the thread to exit and returns once the thread has exited. After + // this method returns, the Thread object is completely reset and may be used + // as if it were newly constructed (i.e., Start may be called again). + // + // Stop may be called multiple times and is simply ignored if the thread is + // already stopped. + // + // NOTE: If you are a consumer of Thread, it is not necessary to call this + // before deleting your Thread objects, as the destructor will do it. + // IF YOU ARE A SUBCLASS OF Thread, YOU MUST CALL THIS IN YOUR DESTRUCTOR. + void Stop(); + + // Signals the thread to exit in the near future. + // + // WARNING: This function is not meant to be commonly used. Use at your own + // risk. Calling this function will cause message_loop() to become invalid in + // the near future. This function was created to workaround a specific + // deadlock on Windows with printer worker thread. In any other case, Stop() + // should be used. + // + // StopSoon should not be called multiple times as it is risky to do so. It + // could cause a timing issue in message_loop() access. Call Stop() to reset + // the thread object once it is known that the thread has quit. + void StopSoon(); + + // Returns the message loop for this thread. Use the MessageLoop's + // PostTask methods to execute code on the thread. This only returns + // non-null after a successful call to Start. After Stop has been called, + // this will return NULL. + // + // NOTE: You must not call this MessageLoop's Quit method directly. Use + // the Thread's Stop method instead. + // + MessageLoop* message_loop() const { return message_loop_; } + + // Returns a MessageLoopProxy for this thread. Use the MessageLoopProxy's + // PostTask methods to execute code on the thread. This only returns + // non-NULL after a successful call to Start. After Stop has been called, + // this will return NULL. Callers can hold on to this even after the thread + // is gone. + scoped_refptr<MessageLoopProxy> message_loop_proxy() const { + return message_loop_ ? message_loop_->message_loop_proxy() : NULL; + } + + // Returns the name of this thread (for display in debugger too). + const std::string& thread_name() const { return name_; } + + // The native thread handle. + PlatformThreadHandle thread_handle() { return thread_; } + + // The thread ID. + PlatformThreadId thread_id() const { return thread_id_; } + + // Returns true if the thread has been started, and not yet stopped. + bool IsRunning() const; + + protected: + // Called just prior to starting the message loop + virtual void Init() {} + + // Called to start the message loop + virtual void Run(MessageLoop* message_loop); + + // Called just after the message loop ends + virtual void CleanUp() {} + + static void SetThreadWasQuitProperly(bool flag); + static bool GetThreadWasQuitProperly(); + + void set_message_loop(MessageLoop* message_loop) { + message_loop_ = message_loop; + } + + private: +#if defined(OS_WIN) + enum ComStatus { + NONE, + STA, + MTA, + }; +#endif + + // PlatformThread::Delegate methods: + virtual void ThreadMain() OVERRIDE; + +#if defined(OS_WIN) + // Whether this thread needs to initialize COM, and if so, in what mode. + ComStatus com_status_; +#endif + + // Whether we successfully started the thread. + bool started_; + + // If true, we're in the middle of stopping, and shouldn't access + // |message_loop_|. It may non-NULL and invalid. + bool stopping_; + + // True while inside of Run(). + bool running_; + + // Used to pass data to ThreadMain. + struct StartupData; + StartupData* startup_data_; + + // The thread's handle. + PlatformThreadHandle thread_; + + // The thread's message loop. Valid only while the thread is alive. Set + // by the created thread. + MessageLoop* message_loop_; + + // Our thread's ID. + PlatformThreadId thread_id_; + + // The name of the thread. Used for debugging purposes. + std::string name_; + + friend void ThreadQuitHelper(); + + DISALLOW_COPY_AND_ASSIGN(Thread); +}; + +} // namespace base + +#endif // BASE_THREADING_THREAD_H_
diff --git a/src/base/threading/thread_checker.h b/src/base/threading/thread_checker.h new file mode 100644 index 0000000..d4c9b74 --- /dev/null +++ b/src/base/threading/thread_checker.h
@@ -0,0 +1,85 @@ +// 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_THREADING_THREAD_CHECKER_H_ +#define BASE_THREADING_THREAD_CHECKER_H_ + +// Apart from debug builds, we also enable the thread checker in +// builds with DCHECK_ALWAYS_ON so that trybots and waterfall bots +// with this define will get the same level of thread checking as +// debug bots. +// +// Note that this does not perfectly match situations where DCHECK is +// enabled. For example a non-official release build may have +// DCHECK_ALWAYS_ON undefined (and therefore ThreadChecker would be +// disabled) but have DCHECKs enabled at runtime. +#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON) +#define ENABLE_THREAD_CHECKER 1 +#else +#define ENABLE_THREAD_CHECKER 0 +#endif + +#if ENABLE_THREAD_CHECKER +#include "base/threading/thread_checker_impl.h" +#endif + +namespace base { + +// Do nothing implementation, for use in release mode. +// +// Note: You should almost always use the ThreadChecker class to get the +// right version for your build configuration. +class ThreadCheckerDoNothing { + public: + bool CalledOnValidThread() const { + return true; + } + + void DetachFromThread() {} +}; + +// ThreadChecker is a helper class used to help verify that some methods of a +// class are called from the same thread. It provides identical functionality to +// base::NonThreadSafe, but it is meant to be held as a member variable, rather +// than inherited from base::NonThreadSafe. +// +// 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 +// 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 +// having that functionality fully encapsulated in the base class. +// - Derived classes should be able to reassign the base class to another +// thread, via DetachFromThread. +// +// If neither of these are true, then having a ThreadChecker member and calling +// CalledOnValidThread is the preferable solution. +// +// Example: +// class MyClass { +// public: +// void Foo() { +// DCHECK(thread_checker_.CalledOnValidThread()); +// ... (do stuff) ... +// } +// +// private: +// ThreadChecker thread_checker_; +// } +// +// In Release mode, CalledOnValidThread will always return true. +#if ENABLE_THREAD_CHECKER +class ThreadChecker : public ThreadCheckerImpl { +}; +#else +class ThreadChecker : public ThreadCheckerDoNothing { +}; +#endif // ENABLE_THREAD_CHECKER + +#undef ENABLE_THREAD_CHECKER + +} // namespace base + +#endif // BASE_THREADING_THREAD_CHECKER_H_
diff --git a/src/base/threading/thread_checker_impl.cc b/src/base/threading/thread_checker_impl.cc new file mode 100644 index 0000000..985433e --- /dev/null +++ b/src/base/threading/thread_checker_impl.cc
@@ -0,0 +1,34 @@ +// 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/threading/thread_checker_impl.h" + +namespace base { + +ThreadCheckerImpl::ThreadCheckerImpl() + : valid_thread_id_(kInvalidThreadId) { + EnsureThreadIdAssigned(); +} + +ThreadCheckerImpl::~ThreadCheckerImpl() {} + +bool ThreadCheckerImpl::CalledOnValidThread() const { + EnsureThreadIdAssigned(); + AutoLock auto_lock(lock_); + return valid_thread_id_ == PlatformThread::CurrentId(); +} + +void ThreadCheckerImpl::DetachFromThread() { + AutoLock auto_lock(lock_); + valid_thread_id_ = kInvalidThreadId; +} + +void ThreadCheckerImpl::EnsureThreadIdAssigned() const { + AutoLock auto_lock(lock_); + if (valid_thread_id_ != kInvalidThreadId) + return; + valid_thread_id_ = PlatformThread::CurrentId(); +} + +} // namespace base
diff --git a/src/base/threading/thread_checker_impl.h b/src/base/threading/thread_checker_impl.h new file mode 100644 index 0000000..d33e21f --- /dev/null +++ b/src/base/threading/thread_checker_impl.h
@@ -0,0 +1,48 @@ +// 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. + +#ifndef BASE_THREADING_THREAD_CHECKER_IMPL_H_ +#define BASE_THREADING_THREAD_CHECKER_IMPL_H_ + +#include "base/base_export.h" +#include "base/synchronization/lock.h" +#include "base/threading/platform_thread.h" + +namespace base { + +// Real implementation of ThreadChecker, for use in debug mode, or +// for temporary use in release mode (e.g. to CHECK on a threading issue +// seen only in the wild). +// +// Note: You should almost always use the ThreadChecker class to get the +// right version for your build configuration. +class BASE_EXPORT ThreadCheckerImpl { + public: + ThreadCheckerImpl(); + ~ThreadCheckerImpl(); + + bool CalledOnValidThread() const; + + // Changes the thread that is checked for in CalledOnValidThread. This may + // be useful when an object may be created on one thread and then used + // exclusively on another thread. + void DetachFromThread(); + + private: + void EnsureThreadIdAssigned() const; + +#if defined(COBALT) || defined(OS_STARBOARD) + // Don't use a mutex since the number of mutexes is limited on some platforms. + mutable base::subtle::Atomic32 valid_thread_id_; +#else + mutable base::Lock lock_; + // This is mutable so that CalledOnValidThread can set it. + // It's guarded by |lock_|. + mutable PlatformThreadId valid_thread_id_; +#endif +}; + +} // namespace base + +#endif // BASE_THREADING_THREAD_CHECKER_IMPL_H_
diff --git a/src/base/threading/thread_checker_impl_atomic.cc b/src/base/threading/thread_checker_impl_atomic.cc new file mode 100644 index 0000000..534cb08 --- /dev/null +++ b/src/base/threading/thread_checker_impl_atomic.cc
@@ -0,0 +1,61 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/threading/thread_checker_impl.h" + +#include "base/threading/platform_thread.h" + +namespace base { +namespace { + +inline base::subtle::Atomic32 CurrentThreadIdAsAtomic() { + return static_cast<base::subtle::Atomic32>(PlatformThread::CurrentId()); +} + +const base::subtle::Atomic32 kInvalidThreadIdAtomic = + static_cast<base::subtle::Atomic32>(kInvalidThreadId); + +} // namespace + +ThreadCheckerImpl::ThreadCheckerImpl() + : valid_thread_id_(kInvalidThreadIdAtomic) { + EnsureThreadIdAssigned(); +} + +ThreadCheckerImpl::~ThreadCheckerImpl() { +} + +bool ThreadCheckerImpl::CalledOnValidThread() const { + EnsureThreadIdAssigned(); + base::subtle::Atomic32 current_thread = + base::subtle::NoBarrier_Load(&valid_thread_id_); + return current_thread == CurrentThreadIdAsAtomic(); +} + +void ThreadCheckerImpl::DetachFromThread() { + base::subtle::NoBarrier_Store(&valid_thread_id_, kInvalidThreadIdAtomic); +} + +void ThreadCheckerImpl::EnsureThreadIdAssigned() const { + // Set valid_thread_id_ to the current thread id if valid_thread_id_ is + // currently unset (that is, set to kInvalidThreadIdAtomic). + base::subtle::NoBarrier_CompareAndSwap( + &valid_thread_id_, + kInvalidThreadIdAtomic, + CurrentThreadIdAsAtomic()); +} + +} // namespace base
diff --git a/src/base/threading/thread_checker_unittest.cc b/src/base/threading/thread_checker_unittest.cc new file mode 100644 index 0000000..026cfd9 --- /dev/null +++ b/src/base/threading/thread_checker_unittest.cc
@@ -0,0 +1,182 @@ +// 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/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/threading/simple_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Duplicated from base/threading/thread_checker.h so that we can be +// good citizens there and undef the macro. +#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON) +#define ENABLE_THREAD_CHECKER 1 +#else +#define ENABLE_THREAD_CHECKER 0 +#endif + +namespace base { + +namespace { + +// Simple class to exercise the basics of ThreadChecker. +// Both the destructor and DoStuff should verify that they were +// called on the same thread as the constructor. +class ThreadCheckerClass : public ThreadChecker { + public: + ThreadCheckerClass() {} + + // Verifies that it was called on the same thread as the constructor. + void DoStuff() { + DCHECK(CalledOnValidThread()); + } + + void DetachFromThread() { + ThreadChecker::DetachFromThread(); + } + + static void MethodOnDifferentThreadImpl(); + static void DetachThenCallFromDifferentThreadImpl(); + + private: + DISALLOW_COPY_AND_ASSIGN(ThreadCheckerClass); +}; + +// Calls ThreadCheckerClass::DoStuff on another thread. +class CallDoStuffOnThread : public base::SimpleThread { + public: + CallDoStuffOnThread(ThreadCheckerClass* thread_checker_class) + : SimpleThread("call_do_stuff_on_thread"), + thread_checker_class_(thread_checker_class) { + } + + virtual void Run() OVERRIDE { + thread_checker_class_->DoStuff(); + } + + private: + ThreadCheckerClass* thread_checker_class_; + + DISALLOW_COPY_AND_ASSIGN(CallDoStuffOnThread); +}; + +// Deletes ThreadCheckerClass on a different thread. +class DeleteThreadCheckerClassOnThread : public base::SimpleThread { + public: + DeleteThreadCheckerClassOnThread(ThreadCheckerClass* thread_checker_class) + : SimpleThread("delete_thread_checker_class_on_thread"), + thread_checker_class_(thread_checker_class) { + } + + virtual void Run() OVERRIDE { + thread_checker_class_.reset(); + } + + private: + scoped_ptr<ThreadCheckerClass> thread_checker_class_; + + DISALLOW_COPY_AND_ASSIGN(DeleteThreadCheckerClassOnThread); +}; + +} // namespace + +TEST(ThreadCheckerTest, CallsAllowedOnSameThread) { + scoped_ptr<ThreadCheckerClass> thread_checker_class( + new ThreadCheckerClass); + + // Verify that DoStuff doesn't assert. + thread_checker_class->DoStuff(); + + // Verify that the destructor doesn't assert. + thread_checker_class.reset(); +} + +TEST(ThreadCheckerTest, DestructorAllowedOnDifferentThread) { + scoped_ptr<ThreadCheckerClass> thread_checker_class( + new ThreadCheckerClass); + + // Verify that the destructor doesn't assert + // when called on a different thread. + DeleteThreadCheckerClassOnThread delete_on_thread( + thread_checker_class.release()); + + delete_on_thread.Start(); + delete_on_thread.Join(); +} + +TEST(ThreadCheckerTest, DetachFromThread) { + scoped_ptr<ThreadCheckerClass> thread_checker_class( + new ThreadCheckerClass); + + // Verify that DoStuff doesn't assert when called on a different thread after + // a call to DetachFromThread. + thread_checker_class->DetachFromThread(); + CallDoStuffOnThread call_on_thread(thread_checker_class.get()); + + call_on_thread.Start(); + call_on_thread.Join(); +} + +#if GTEST_HAS_DEATH_TEST || !ENABLE_THREAD_CHECKER + +void ThreadCheckerClass::MethodOnDifferentThreadImpl() { + scoped_ptr<ThreadCheckerClass> thread_checker_class( + new ThreadCheckerClass); + + // DoStuff should assert in debug builds only when called on a + // different thread. + CallDoStuffOnThread call_on_thread(thread_checker_class.get()); + + call_on_thread.Start(); + call_on_thread.Join(); +} + +#if ENABLE_THREAD_CHECKER +TEST(ThreadCheckerDeathTest, MethodNotAllowedOnDifferentThreadInDebug) { + ASSERT_DEATH({ + ThreadCheckerClass::MethodOnDifferentThreadImpl(); + }, ""); +} +#else +TEST(ThreadCheckerTest, MethodAllowedOnDifferentThreadInRelease) { + ThreadCheckerClass::MethodOnDifferentThreadImpl(); +} +#endif // ENABLE_THREAD_CHECKER + +void ThreadCheckerClass::DetachThenCallFromDifferentThreadImpl() { + scoped_ptr<ThreadCheckerClass> thread_checker_class( + new ThreadCheckerClass); + + // DoStuff doesn't assert when called on a different thread + // after a call to DetachFromThread. + thread_checker_class->DetachFromThread(); + CallDoStuffOnThread call_on_thread(thread_checker_class.get()); + + call_on_thread.Start(); + call_on_thread.Join(); + + // DoStuff should assert in debug builds only after moving to + // another thread. + thread_checker_class->DoStuff(); +} + +#if ENABLE_THREAD_CHECKER +TEST(ThreadCheckerDeathTest, DetachFromThreadInDebug) { + ASSERT_DEATH({ + ThreadCheckerClass::DetachThenCallFromDifferentThreadImpl(); + }, ""); +} +#else +TEST(ThreadCheckerTest, DetachFromThreadInRelease) { + ThreadCheckerClass::DetachThenCallFromDifferentThreadImpl(); +} +#endif // ENABLE_THREAD_CHECKER + +#endif // GTEST_HAS_DEATH_TEST || !ENABLE_THREAD_CHECKER + +// Just in case we ever get lumped together with other compilation units. +#undef ENABLE_THREAD_CHECKER + +} // namespace base
diff --git a/src/base/threading/thread_collision_warner.cc b/src/base/threading/thread_collision_warner.cc new file mode 100644 index 0000000..547e11c --- /dev/null +++ b/src/base/threading/thread_collision_warner.cc
@@ -0,0 +1,64 @@ +// Copyright (c) 2010 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/threading/thread_collision_warner.h" + +#include "base/logging.h" +#include "base/threading/platform_thread.h" + +namespace base { + +void DCheckAsserter::warn() { + NOTREACHED() << "Thread Collision"; +} + +static subtle::Atomic32 CurrentThread() { + const PlatformThreadId current_thread_id = PlatformThread::CurrentId(); + // We need to get the thread id into an atomic data type. This might be a + // truncating conversion, but any loss-of-information just increases the + // chance of a fault negative, not a false positive. + const subtle::Atomic32 atomic_thread_id = + static_cast<subtle::Atomic32>(current_thread_id); + + return atomic_thread_id; +} + +void ThreadCollisionWarner::EnterSelf() { + // If the active thread is 0 then I'll write the current thread ID + // if two or more threads arrive here only one will succeed to + // write on valid_thread_id_ the current thread ID. + subtle::Atomic32 current_thread_id = CurrentThread(); + + int previous_value = subtle::NoBarrier_CompareAndSwap(&valid_thread_id_, + 0, + current_thread_id); + if (previous_value != 0 && previous_value != current_thread_id) { + // gotcha! a thread is trying to use the same class and that is + // not current thread. + asserter_->warn(); + } + + subtle::NoBarrier_AtomicIncrement(&counter_, 1); +} + +void ThreadCollisionWarner::Enter() { + subtle::Atomic32 current_thread_id = CurrentThread(); + + if (subtle::NoBarrier_CompareAndSwap(&valid_thread_id_, + 0, + current_thread_id) != 0) { + // gotcha! another thread is trying to use the same class. + asserter_->warn(); + } + + subtle::NoBarrier_AtomicIncrement(&counter_, 1); +} + +void ThreadCollisionWarner::Leave() { + if (subtle::Barrier_AtomicIncrement(&counter_, -1) == 0) { + subtle::NoBarrier_Store(&valid_thread_id_, 0); + } +} + +} // namespace base
diff --git a/src/base/threading/thread_collision_warner.h b/src/base/threading/thread_collision_warner.h new file mode 100644 index 0000000..2181cbd --- /dev/null +++ b/src/base/threading/thread_collision_warner.h
@@ -0,0 +1,244 @@ +// 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_THREADING_THREAD_COLLISION_WARNER_H_ +#define BASE_THREADING_THREAD_COLLISION_WARNER_H_ + +#include <memory> + +#include "base/atomicops.h" +#include "base/base_export.h" +#include "base/compiler_specific.h" + +// A helper class alongside macros to be used to verify assumptions about thread +// safety of a class. +// +// Example: Queue implementation non thread-safe but still usable if clients +// are synchronized somehow. +// +// In this case the macro DFAKE_SCOPED_LOCK has to be +// used, it checks that if a thread is inside the push/pop then +// noone else is still inside the pop/push +// +// class NonThreadSafeQueue { +// public: +// ... +// void push(int) { DFAKE_SCOPED_LOCK(push_pop_); ... } +// int pop() { DFAKE_SCOPED_LOCK(push_pop_); ... } +// ... +// private: +// DFAKE_MUTEX(push_pop_); +// }; +// +// +// Example: Queue implementation non thread-safe but still usable if clients +// are synchronized somehow, it calls a method to "protect" from +// a "protected" method +// +// In this case the macro DFAKE_SCOPED_RECURSIVE_LOCK +// has to be used, it checks that if a thread is inside the push/pop +// then noone else is still inside the pop/push +// +// class NonThreadSafeQueue { +// public: +// void push(int) { +// DFAKE_SCOPED_LOCK(push_pop_); +// ... +// } +// int pop() { +// DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); +// bar(); +// ... +// } +// void bar() { DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); ... } +// ... +// private: +// DFAKE_MUTEX(push_pop_); +// }; +// +// +// Example: Queue implementation not usable even if clients are synchronized, +// so only one thread in the class life cycle can use the two members +// push/pop. +// +// In this case the macro DFAKE_SCOPED_LOCK_THREAD_LOCKED pins the +// specified +// critical section the first time a thread enters push or pop, from +// that time on only that thread is allowed to execute push or pop. +// +// class NonThreadSafeQueue { +// public: +// ... +// void push(int) { DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); ... } +// int pop() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); ... } +// ... +// private: +// DFAKE_MUTEX(push_pop_); +// }; +// +// +// Example: Class that has to be contructed/destroyed on same thread, it has +// a "shareable" method (with external synchronization) and a not +// shareable method (even with external synchronization). +// +// In this case 3 Critical sections have to be defined +// +// class ExoticClass { +// public: +// ExoticClass() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(ctor_dtor_); ... } +// ~ExoticClass() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(ctor_dtor_); ... } +// +// void Shareable() { DFAKE_SCOPED_LOCK(shareable_section_); ... } +// void NotShareable() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(ctor_dtor_); ... } +// ... +// private: +// DFAKE_MUTEX(ctor_dtor_); +// DFAKE_MUTEX(shareable_section_); +// }; + + +#if !defined(NDEBUG) + +// Defines a class member that acts like a mutex. It is used only as a +// verification tool. +#define DFAKE_MUTEX(obj) \ + mutable base::ThreadCollisionWarner obj +// Asserts the call is never called simultaneously in two threads. Used at +// member function scope. +#define DFAKE_SCOPED_LOCK(obj) \ + base::ThreadCollisionWarner::ScopedCheck s_check_##obj(&obj) +// Asserts the call is never called simultaneously in two threads. Used at +// member function scope. Same as DFAKE_SCOPED_LOCK but allows recursive locks. +#define DFAKE_SCOPED_RECURSIVE_LOCK(obj) \ + base::ThreadCollisionWarner::ScopedRecursiveCheck sr_check_##obj(&obj) +// Asserts the code is always executed in the same thread. +#define DFAKE_SCOPED_LOCK_THREAD_LOCKED(obj) \ + base::ThreadCollisionWarner::Check check_##obj(&obj) + +#else + +#define DFAKE_MUTEX(obj) typedef void InternalFakeMutexType##obj +#define DFAKE_SCOPED_LOCK(obj) ((void)0) +#define DFAKE_SCOPED_RECURSIVE_LOCK(obj) ((void)0) +#define DFAKE_SCOPED_LOCK_THREAD_LOCKED(obj) ((void)0) + +#endif + +namespace base { + +// The class ThreadCollisionWarner uses an Asserter to notify the collision +// AsserterBase is the interfaces and DCheckAsserter is the default asserter +// used. During the unit tests is used another class that doesn't "DCHECK" +// in case of collision (check thread_collision_warner_unittests.cc) +struct BASE_EXPORT AsserterBase { + virtual ~AsserterBase() {} + virtual void warn() = 0; +}; + +struct BASE_EXPORT DCheckAsserter : public AsserterBase { + virtual ~DCheckAsserter() {} + virtual void warn() OVERRIDE; +}; + +class BASE_EXPORT ThreadCollisionWarner { + public: + // The parameter asserter is there only for test purpose + ThreadCollisionWarner(AsserterBase* asserter = new DCheckAsserter()) + : valid_thread_id_(0), + counter_(0), + asserter_(asserter) {} + + ~ThreadCollisionWarner() { + delete asserter_; + } + + // This class is meant to be used through the macro + // DFAKE_SCOPED_LOCK_THREAD_LOCKED + // it doesn't leave the critical section, as opposed to ScopedCheck, + // because the critical section being pinned is allowed to be used only + // from one thread + class BASE_EXPORT Check { + public: + explicit Check(ThreadCollisionWarner* warner) + : warner_(warner) { + warner_->EnterSelf(); + } + + ~Check() {} + + private: + ThreadCollisionWarner* warner_; + + DISALLOW_COPY_AND_ASSIGN(Check); + }; + + // This class is meant to be used through the macro + // DFAKE_SCOPED_LOCK + class BASE_EXPORT ScopedCheck { + public: + explicit ScopedCheck(ThreadCollisionWarner* warner) + : warner_(warner) { + warner_->Enter(); + } + + ~ScopedCheck() { + warner_->Leave(); + } + + private: + ThreadCollisionWarner* warner_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCheck); + }; + + // This class is meant to be used through the macro + // DFAKE_SCOPED_RECURSIVE_LOCK + class BASE_EXPORT ScopedRecursiveCheck { + public: + explicit ScopedRecursiveCheck(ThreadCollisionWarner* warner) + : warner_(warner) { + warner_->EnterSelf(); + } + + ~ScopedRecursiveCheck() { + warner_->Leave(); + } + + private: + ThreadCollisionWarner* warner_; + + DISALLOW_COPY_AND_ASSIGN(ScopedRecursiveCheck); + }; + + private: + // This method stores the current thread identifier and does a DCHECK + // if a another thread has already done it, it is safe if same thread + // calls this multiple time (recursion allowed). + void EnterSelf(); + + // Same as EnterSelf but recursion is not allowed. + void Enter(); + + // Removes the thread_id stored in order to allow other threads to + // call EnterSelf or Enter. + void Leave(); + + // This stores the thread id that is inside the critical section, if the + // value is 0 then no thread is inside. + volatile subtle::Atomic32 valid_thread_id_; + + // Counter to trace how many time a critical section was "pinned" + // (when allowed) in order to unpin it when counter_ reaches 0. + volatile subtle::Atomic32 counter_; + + // Here only for class unit tests purpose, during the test I need to not + // DCHECK but notify the collision with something else. + AsserterBase* asserter_; + + DISALLOW_COPY_AND_ASSIGN(ThreadCollisionWarner); +}; + +} // namespace base + +#endif // BASE_THREADING_THREAD_COLLISION_WARNER_H_
diff --git a/src/base/threading/thread_collision_warner_unittest.cc b/src/base/threading/thread_collision_warner_unittest.cc new file mode 100644 index 0000000..48710a7 --- /dev/null +++ b/src/base/threading/thread_collision_warner_unittest.cc
@@ -0,0 +1,385 @@ +// 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/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "base/threading/platform_thread.h" +#include "base/threading/simple_thread.h" +#include "base/threading/thread_collision_warner.h" +#include "testing/gtest/include/gtest/gtest.h" + +// '' : local class member function does not have a body +MSVC_PUSH_DISABLE_WARNING(4822) + + +#if defined(NDEBUG) + +// Would cause a memory leak otherwise. +#undef DFAKE_MUTEX +#define DFAKE_MUTEX(obj) scoped_ptr<base::AsserterBase> obj + +// In Release, we expect the AsserterBase::warn() to not happen. +#define EXPECT_NDEBUG_FALSE_DEBUG_TRUE EXPECT_FALSE + +#else + +// In Debug, we expect the AsserterBase::warn() to happen. +#define EXPECT_NDEBUG_FALSE_DEBUG_TRUE EXPECT_TRUE + +#endif + + +namespace { + +// This is the asserter used with ThreadCollisionWarner instead of the default +// DCheckAsserter. The method fail_state is used to know if a collision took +// place. +class AssertReporter : public base::AsserterBase { + public: + AssertReporter() + : failed_(false) {} + + virtual void warn() OVERRIDE { + failed_ = true; + } + + virtual ~AssertReporter() {} + + bool fail_state() const { return failed_; } + void reset() { failed_ = false; } + + private: + bool failed_; +}; + +} // namespace + +TEST(ThreadCollisionTest, BookCriticalSection) { + AssertReporter* local_reporter = new AssertReporter(); + + base::ThreadCollisionWarner warner(local_reporter); + EXPECT_FALSE(local_reporter->fail_state()); + + { // Pin section. + DFAKE_SCOPED_LOCK_THREAD_LOCKED(warner); + EXPECT_FALSE(local_reporter->fail_state()); + { // Pin section. + DFAKE_SCOPED_LOCK_THREAD_LOCKED(warner); + EXPECT_FALSE(local_reporter->fail_state()); + } + } +} + +TEST(ThreadCollisionTest, ScopedRecursiveBookCriticalSection) { + AssertReporter* local_reporter = new AssertReporter(); + + base::ThreadCollisionWarner warner(local_reporter); + EXPECT_FALSE(local_reporter->fail_state()); + + { // Pin section. + DFAKE_SCOPED_RECURSIVE_LOCK(warner); + EXPECT_FALSE(local_reporter->fail_state()); + { // Pin section again (allowed by DFAKE_SCOPED_RECURSIVE_LOCK) + DFAKE_SCOPED_RECURSIVE_LOCK(warner); + EXPECT_FALSE(local_reporter->fail_state()); + } // Unpin section. + } // Unpin section. + + // Check that section is not pinned + { // Pin section. + DFAKE_SCOPED_LOCK(warner); + EXPECT_FALSE(local_reporter->fail_state()); + } // Unpin section. +} + +TEST(ThreadCollisionTest, ScopedBookCriticalSection) { + AssertReporter* local_reporter = new AssertReporter(); + + base::ThreadCollisionWarner warner(local_reporter); + EXPECT_FALSE(local_reporter->fail_state()); + + { // Pin section. + DFAKE_SCOPED_LOCK(warner); + EXPECT_FALSE(local_reporter->fail_state()); + } // Unpin section. + + { // Pin section. + DFAKE_SCOPED_LOCK(warner); + EXPECT_FALSE(local_reporter->fail_state()); + { + // Pin section again (not allowed by DFAKE_SCOPED_LOCK) + DFAKE_SCOPED_LOCK(warner); + EXPECT_NDEBUG_FALSE_DEBUG_TRUE(local_reporter->fail_state()); + // Reset the status of warner for further tests. + local_reporter->reset(); + } // Unpin section. + } // Unpin section. + + { + // Pin section. + DFAKE_SCOPED_LOCK(warner); + EXPECT_FALSE(local_reporter->fail_state()); + } // Unpin section. +} + +TEST(ThreadCollisionTest, MTBookCriticalSectionTest) { + class NonThreadSafeQueue { + public: + explicit NonThreadSafeQueue(base::AsserterBase* asserter) + : push_pop_(asserter) { + } + + void push(int value) { + DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); + } + + int pop() { + DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); + return 0; + } + + private: + DFAKE_MUTEX(push_pop_); + + DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue); + }; + + class QueueUser : public base::DelegateSimpleThread::Delegate { + public: + explicit QueueUser(NonThreadSafeQueue& queue) + : queue_(queue) {} + + virtual void Run() OVERRIDE { + queue_.push(0); + queue_.pop(); + } + + private: + NonThreadSafeQueue& queue_; + }; + + AssertReporter* local_reporter = new AssertReporter(); + + NonThreadSafeQueue queue(local_reporter); + + QueueUser queue_user_a(queue); + QueueUser queue_user_b(queue); + + base::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a"); + base::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b"); + + thread_a.Start(); + thread_b.Start(); + + thread_a.Join(); + thread_b.Join(); + + EXPECT_NDEBUG_FALSE_DEBUG_TRUE(local_reporter->fail_state()); +} + +TEST(ThreadCollisionTest, MTScopedBookCriticalSectionTest) { + // Queue with a 5 seconds push execution time, hopefuly the two used threads + // in the test will enter the push at same time. + class NonThreadSafeQueue { + public: + explicit NonThreadSafeQueue(base::AsserterBase* asserter) + : push_pop_(asserter) { + } + + void push(int value) { + DFAKE_SCOPED_LOCK(push_pop_); + base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(5)); + } + + int pop() { + DFAKE_SCOPED_LOCK(push_pop_); + return 0; + } + + private: + DFAKE_MUTEX(push_pop_); + + DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue); + }; + + class QueueUser : public base::DelegateSimpleThread::Delegate { + public: + explicit QueueUser(NonThreadSafeQueue& queue) + : queue_(queue) {} + + virtual void Run() OVERRIDE { + queue_.push(0); + queue_.pop(); + } + + private: + NonThreadSafeQueue& queue_; + }; + + AssertReporter* local_reporter = new AssertReporter(); + + NonThreadSafeQueue queue(local_reporter); + + QueueUser queue_user_a(queue); + QueueUser queue_user_b(queue); + + base::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a"); + base::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b"); + + thread_a.Start(); + thread_b.Start(); + + thread_a.Join(); + thread_b.Join(); + + EXPECT_NDEBUG_FALSE_DEBUG_TRUE(local_reporter->fail_state()); +} + +TEST(ThreadCollisionTest, MTSynchedScopedBookCriticalSectionTest) { + // Queue with a 2 seconds push execution time, hopefuly the two used threads + // in the test will enter the push at same time. + class NonThreadSafeQueue { + public: + explicit NonThreadSafeQueue(base::AsserterBase* asserter) + : push_pop_(asserter) { + } + + void push(int value) { + DFAKE_SCOPED_LOCK(push_pop_); + base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(2)); + } + + int pop() { + DFAKE_SCOPED_LOCK(push_pop_); + return 0; + } + + private: + DFAKE_MUTEX(push_pop_); + + DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue); + }; + + // This time the QueueUser class protects the non thread safe queue with + // a lock. + class QueueUser : public base::DelegateSimpleThread::Delegate { + public: + QueueUser(NonThreadSafeQueue& queue, base::Lock& lock) + : queue_(queue), + lock_(lock) {} + + virtual void Run() OVERRIDE { + { + base::AutoLock auto_lock(lock_); + queue_.push(0); + } + { + base::AutoLock auto_lock(lock_); + queue_.pop(); + } + } + private: + NonThreadSafeQueue& queue_; + base::Lock& lock_; + }; + + AssertReporter* local_reporter = new AssertReporter(); + + NonThreadSafeQueue queue(local_reporter); + + base::Lock lock; + + QueueUser queue_user_a(queue, lock); + QueueUser queue_user_b(queue, lock); + + base::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a"); + base::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b"); + + thread_a.Start(); + thread_b.Start(); + + thread_a.Join(); + thread_b.Join(); + + EXPECT_FALSE(local_reporter->fail_state()); +} + +TEST(ThreadCollisionTest, MTSynchedScopedRecursiveBookCriticalSectionTest) { + // Queue with a 2 seconds push execution time, hopefuly the two used threads + // in the test will enter the push at same time. + class NonThreadSafeQueue { + public: + explicit NonThreadSafeQueue(base::AsserterBase* asserter) + : push_pop_(asserter) { + } + + void push(int) { + DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); + bar(); + base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(2)); + } + + int pop() { + DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); + return 0; + } + + void bar() { + DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); + } + + private: + DFAKE_MUTEX(push_pop_); + + DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue); + }; + + // This time the QueueUser class protects the non thread safe queue with + // a lock. + class QueueUser : public base::DelegateSimpleThread::Delegate { + public: + QueueUser(NonThreadSafeQueue& queue, base::Lock& lock) + : queue_(queue), + lock_(lock) {} + + virtual void Run() OVERRIDE { + { + base::AutoLock auto_lock(lock_); + queue_.push(0); + } + { + base::AutoLock auto_lock(lock_); + queue_.bar(); + } + { + base::AutoLock auto_lock(lock_); + queue_.pop(); + } + } + private: + NonThreadSafeQueue& queue_; + base::Lock& lock_; + }; + + AssertReporter* local_reporter = new AssertReporter(); + + NonThreadSafeQueue queue(local_reporter); + + base::Lock lock; + + QueueUser queue_user_a(queue, lock); + QueueUser queue_user_b(queue, lock); + + base::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a"); + base::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b"); + + thread_a.Start(); + thread_b.Start(); + + thread_a.Join(); + thread_b.Join(); + + EXPECT_FALSE(local_reporter->fail_state()); +}
diff --git a/src/base/threading/thread_local.h b/src/base/threading/thread_local.h new file mode 100644 index 0000000..762d77b --- /dev/null +++ b/src/base/threading/thread_local.h
@@ -0,0 +1,132 @@ +// 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. + +// WARNING: Thread local storage is a bit tricky to get right. Please make +// sure that this is really the proper solution for what you're trying to +// achieve. Don't prematurely optimize, most likely you can just use a Lock. +// +// These classes implement a wrapper around the platform's TLS storage +// mechanism. On construction, they will allocate a TLS slot, and free the +// TLS slot on destruction. No memory management (creation or destruction) is +// handled. This means for uses of ThreadLocalPointer, you must correctly +// manage the memory yourself, these classes will not destroy the pointer for +// you. There are no at-thread-exit actions taken by these classes. +// +// ThreadLocalPointer<Type> wraps a Type*. It performs no creation or +// destruction, so memory management must be handled elsewhere. The first call +// to Get() on a thread will return NULL. You can update the pointer with a +// call to Set(). +// +// ThreadLocalBoolean wraps a bool. It will default to false if it has never +// been set otherwise with Set(). +// +// Thread Safety: An instance of ThreadLocalStorage is completely thread safe +// once it has been created. If you want to dynamically create an instance, +// you must of course properly deal with safety and race conditions. This +// means a function-level static initializer is generally inappropiate. +// +// Example usage: +// // My class is logically attached to a single thread. We cache a pointer +// // on the thread it was created on, so we can implement current(). +// MyClass::MyClass() { +// DCHECK(Singleton<ThreadLocalPointer<MyClass> >::get()->Get() == NULL); +// Singleton<ThreadLocalPointer<MyClass> >::get()->Set(this); +// } +// +// MyClass::~MyClass() { +// DCHECK(Singleton<ThreadLocalPointer<MyClass> >::get()->Get() != NULL); +// Singleton<ThreadLocalPointer<MyClass> >::get()->Set(NULL); +// } +// +// // Return the current MyClass associated with the calling thread, can be +// // NULL if there isn't a MyClass associated. +// MyClass* MyClass::current() { +// return Singleton<ThreadLocalPointer<MyClass> >::get()->Get(); +// } + +#ifndef BASE_THREADING_THREAD_LOCAL_H_ +#define BASE_THREADING_THREAD_LOCAL_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" + +#if defined(OS_POSIX) +#include <pthread.h> +#elif defined(OS_STARBOARD) +#include "starboard/thread.h" +#endif + +namespace base { + +namespace internal { + +// Helper functions that abstract the cross-platform APIs. Do not use directly. +struct BASE_EXPORT ThreadLocalPlatform { +#if defined(OS_WIN) + typedef unsigned long SlotType; +#elif defined(OS_POSIX) + typedef pthread_key_t SlotType; +#elif defined(OS_STARBOARD) + typedef SbThreadLocalKey SlotType; +#endif + + static void AllocateSlot(SlotType& slot); + static void FreeSlot(SlotType& slot); + static void* GetValueFromSlot(SlotType& slot); + static void SetValueInSlot(SlotType& slot, void* value); +}; + +} // namespace internal + +template <typename Type> +class ThreadLocalPointer { + public: + ThreadLocalPointer() : slot_() { + internal::ThreadLocalPlatform::AllocateSlot(slot_); + } + + ~ThreadLocalPointer() { + internal::ThreadLocalPlatform::FreeSlot(slot_); + } + + Type* Get() { + return static_cast<Type*>( + internal::ThreadLocalPlatform::GetValueFromSlot(slot_)); + } + + void Set(Type* ptr) { + internal::ThreadLocalPlatform::SetValueInSlot( + slot_, const_cast<void*>(static_cast<const void*>(ptr))); + } + + private: + typedef internal::ThreadLocalPlatform::SlotType SlotType; + + SlotType slot_; + + DISALLOW_COPY_AND_ASSIGN(ThreadLocalPointer<Type>); +}; + +class ThreadLocalBoolean { + public: + ThreadLocalBoolean() { } + ~ThreadLocalBoolean() { } + + bool Get() { + return tlp_.Get() != NULL; + } + + void Set(bool val) { + tlp_.Set(val ? this : NULL); + } + + private: + ThreadLocalPointer<void> tlp_; + + DISALLOW_COPY_AND_ASSIGN(ThreadLocalBoolean); +}; + +} // namespace base + +#endif // BASE_THREADING_THREAD_LOCAL_H_
diff --git a/src/base/threading/thread_local_posix.cc b/src/base/threading/thread_local_posix.cc new file mode 100644 index 0000000..4951006 --- /dev/null +++ b/src/base/threading/thread_local_posix.cc
@@ -0,0 +1,40 @@ +// 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/threading/thread_local.h" + +#include <pthread.h> + +#include "base/logging.h" + +namespace base { + +namespace internal { + +// static +void ThreadLocalPlatform::AllocateSlot(SlotType& slot) { + int error = pthread_key_create(&slot, NULL); + CHECK_EQ(error, 0); +} + +// static +void ThreadLocalPlatform::FreeSlot(SlotType& slot) { + int error = pthread_key_delete(slot); + DCHECK_EQ(0, error); +} + +// static +void* ThreadLocalPlatform::GetValueFromSlot(SlotType& slot) { + return pthread_getspecific(slot); +} + +// static +void ThreadLocalPlatform::SetValueInSlot(SlotType& slot, void* value) { + int error = pthread_setspecific(slot, value); + DCHECK_EQ(error, 0); +} + +} // namespace internal + +} // namespace base
diff --git a/src/base/threading/thread_local_shell.cc b/src/base/threading/thread_local_shell.cc new file mode 100644 index 0000000..4d09da5 --- /dev/null +++ b/src/base/threading/thread_local_shell.cc
@@ -0,0 +1,18 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// We use the same code as posix version +#include "thread_local_posix.cc"
diff --git a/src/base/threading/thread_local_starboard.cc b/src/base/threading/thread_local_starboard.cc new file mode 100644 index 0000000..e3a96d6 --- /dev/null +++ b/src/base/threading/thread_local_starboard.cc
@@ -0,0 +1,48 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/threading/thread_local.h" + +#include "base/logging.h" +#include "starboard/thread.h" + +namespace base { + +namespace internal { + +// static +void ThreadLocalPlatform::AllocateSlot(SlotType& slot) { + slot = SbThreadCreateLocalKey(NULL); + CHECK_NE(kSbThreadLocalKeyInvalid, slot); +} + +// static +void ThreadLocalPlatform::FreeSlot(SlotType& slot) { + SbThreadDestroyLocalKey(slot); +} + +// static +void* ThreadLocalPlatform::GetValueFromSlot(SlotType& slot) { + return SbThreadGetLocalValue(slot); +} + +// static +void ThreadLocalPlatform::SetValueInSlot(SlotType& slot, void* value) { + bool result = SbThreadSetLocalValue(slot, value); + DCHECK(result); +} + +} // namespace internal + +} // namespace base
diff --git a/src/base/threading/thread_local_storage.h b/src/base/threading/thread_local_storage.h new file mode 100644 index 0000000..2240e8a --- /dev/null +++ b/src/base/threading/thread_local_storage.h
@@ -0,0 +1,99 @@ +// 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_THREADING_THREAD_LOCAL_STORAGE_H_ +#define BASE_THREADING_THREAD_LOCAL_STORAGE_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" + +#if defined(OS_POSIX) +#include <pthread.h> +#elif defined(OS_STARBOARD) +#include "starboard/thread.h" +#endif + +namespace base { + +// Wrapper for thread local storage. This class doesn't do much except provide +// an API for portability. +class BASE_EXPORT ThreadLocalStorage { + public: + + // Prototype for the TLS destructor function, which can be optionally used to + // cleanup thread local storage on thread exit. 'value' is the data that is + // stored in thread local storage. + typedef void (*TLSDestructorFunc)(void* value); + + // StaticSlot uses its own struct initializer-list style static + // initialization, as base's LINKER_INITIALIZED requires a constructor and on + // some compilers (notably gcc 4.4) this still ends up needing runtime + // initialization. + #define TLS_INITIALIZER {0} + + // A key representing one value stored in TLS. + // Initialize like + // ThreadLocalStorage::StaticSlot my_slot = TLS_INITIALIZER; + // If you're not using a static variable, use the convenience class + // ThreadLocalStorage::Slot (below) instead. + struct BASE_EXPORT StaticSlot { + // Set up the TLS slot. Called by the constructor. + // 'destructor' is a pointer to a function to perform per-thread cleanup of + // this object. If set to NULL, no cleanup is done for this TLS slot. + // Returns false on error. + bool Initialize(TLSDestructorFunc destructor); + + // Free a previously allocated TLS 'slot'. + // If a destructor was set for this slot, removes + // the destructor so that remaining threads exiting + // will not free data. + void Free(); + + // Get the thread-local value stored in slot 'slot'. + // Values are guaranteed to initially be zero. + void* Get() const; + + // Set the thread-local value stored in slot 'slot' to + // value 'value'. + void Set(void* value); + + bool initialized() const { return initialized_; } + + // The internals of this struct should be considered private. + bool initialized_; +#if defined(OS_WIN) + int slot_; +#elif defined(OS_POSIX) + pthread_key_t key_; +#elif defined(OS_STARBOARD) + SbThreadLocalKey key_; +#endif + + }; + + // A convenience wrapper around StaticSlot with a constructor. Can be used + // as a member variable. + class BASE_EXPORT Slot : public StaticSlot { + public: + // Calls StaticSlot::Initialize(). + explicit Slot(TLSDestructorFunc destructor = NULL); + + private: + using StaticSlot::initialized_; +#if defined(OS_WIN) + using StaticSlot::slot_; +#elif defined(OS_POSIX) + using StaticSlot::key_; +#elif defined(OS_STARBOARD) + using StaticSlot::key_; +#endif + DISALLOW_COPY_AND_ASSIGN(Slot); + }; + + DISALLOW_COPY_AND_ASSIGN(ThreadLocalStorage); +}; + +} // namespace base + +#endif // BASE_THREADING_THREAD_LOCAL_STORAGE_H_
diff --git a/src/base/threading/thread_local_storage_posix.cc b/src/base/threading/thread_local_storage_posix.cc new file mode 100644 index 0000000..75da5a7 --- /dev/null +++ b/src/base/threading/thread_local_storage_posix.cc
@@ -0,0 +1,49 @@ +// 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/threading/thread_local_storage.h" + +#include "base/logging.h" + +namespace base { + +ThreadLocalStorage::Slot::Slot(TLSDestructorFunc destructor) { + initialized_ = false; + key_ = 0; + Initialize(destructor); +} + +bool ThreadLocalStorage::StaticSlot::Initialize(TLSDestructorFunc destructor) { + DCHECK(!initialized_); + int error = pthread_key_create(&key_, destructor); + if (error) { + NOTREACHED(); + return false; + } + + initialized_ = true; + return true; +} + +void ThreadLocalStorage::StaticSlot::Free() { + DCHECK(initialized_); + int error = pthread_key_delete(key_); + if (error) + NOTREACHED(); + initialized_ = false; +} + +void* ThreadLocalStorage::StaticSlot::Get() const { + DCHECK(initialized_); + return pthread_getspecific(key_); +} + +void ThreadLocalStorage::StaticSlot::Set(void* value) { + DCHECK(initialized_); + int error = pthread_setspecific(key_, value); + if (error) + NOTREACHED(); +} + +} // namespace base
diff --git a/src/base/threading/thread_local_storage_shell.cc b/src/base/threading/thread_local_storage_shell.cc new file mode 100644 index 0000000..4728401 --- /dev/null +++ b/src/base/threading/thread_local_storage_shell.cc
@@ -0,0 +1,18 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// We use the same code as posix version +#include "thread_local_storage_posix.cc"
diff --git a/src/base/threading/thread_local_storage_starboard.cc b/src/base/threading/thread_local_storage_starboard.cc new file mode 100644 index 0000000..db8e810 --- /dev/null +++ b/src/base/threading/thread_local_storage_starboard.cc
@@ -0,0 +1,57 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/threading/thread_local_storage.h" + +#include "base/logging.h" + +namespace base { + +ThreadLocalStorage::Slot::Slot(TLSDestructorFunc destructor) { + initialized_ = false; + key_ = kSbThreadLocalKeyInvalid; + Initialize(destructor); +} + +bool ThreadLocalStorage::StaticSlot::Initialize(TLSDestructorFunc destructor) { + DCHECK(!initialized_); + key_ = SbThreadCreateLocalKey(destructor); + if (!SbThreadIsValidLocalKey(key_)) { + NOTREACHED(); + return false; + } + + initialized_ = true; + return true; +} + +void ThreadLocalStorage::StaticSlot::Free() { + DCHECK(initialized_); + SbThreadDestroyLocalKey(key_); + initialized_ = false; +} + +void* ThreadLocalStorage::StaticSlot::Get() const { + DCHECK(initialized_); + return SbThreadGetLocalValue(key_); +} + +void ThreadLocalStorage::StaticSlot::Set(void* value) { + DCHECK(initialized_); + if (!SbThreadSetLocalValue(key_, value)) { + NOTREACHED(); + } +} + +} // namespace base
diff --git a/src/base/threading/thread_local_storage_unittest.cc b/src/base/threading/thread_local_storage_unittest.cc new file mode 100644 index 0000000..c613148 --- /dev/null +++ b/src/base/threading/thread_local_storage_unittest.cc
@@ -0,0 +1,125 @@ +// 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. + +#if defined(OS_WIN) +#include <windows.h> +#include <process.h> +#endif + +#include "base/threading/simple_thread.h" +#include "base/threading/thread_local_storage.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +// Ignore warnings about ptr->int conversions that we use when +// storing ints into ThreadLocalStorage. +#pragma warning(disable : 4311 4312) +#endif + +namespace base { + +namespace { + +const int kInitialTlsValue = 0x5555; +const int kFinalTlsValue = 0x7777; +// How many times must a destructor be called before we really are done. +const int kNumberDestructorCallRepetitions = 3; + +static ThreadLocalStorage::StaticSlot tls_slot = TLS_INITIALIZER; + +class ThreadLocalStorageRunner : public DelegateSimpleThread::Delegate { + public: + explicit ThreadLocalStorageRunner(int* tls_value_ptr) + : tls_value_ptr_(tls_value_ptr) {} + + virtual ~ThreadLocalStorageRunner() {} + + virtual void Run() OVERRIDE { + *tls_value_ptr_ = kInitialTlsValue; + tls_slot.Set(tls_value_ptr_); + + int *ptr = static_cast<int*>(tls_slot.Get()); + EXPECT_EQ(ptr, tls_value_ptr_); + EXPECT_EQ(*ptr, kInitialTlsValue); + *tls_value_ptr_ = 0; + + ptr = static_cast<int*>(tls_slot.Get()); + EXPECT_EQ(ptr, tls_value_ptr_); + EXPECT_EQ(*ptr, 0); + + *ptr = kFinalTlsValue + kNumberDestructorCallRepetitions; + } + + private: + int* tls_value_ptr_; + DISALLOW_COPY_AND_ASSIGN(ThreadLocalStorageRunner); +}; + + +void ThreadLocalStorageCleanup(void *value) { + int *ptr = reinterpret_cast<int*>(value); + // Destructors should never be called with a NULL. + ASSERT_NE(reinterpret_cast<int*>(NULL), ptr); + if (*ptr == kFinalTlsValue) + return; // We've been called enough times. + ASSERT_LT(kFinalTlsValue, *ptr); + ASSERT_GE(kFinalTlsValue + kNumberDestructorCallRepetitions, *ptr); + --*ptr; // Move closer to our target. + // Tell tls that we're not done with this thread, and still need destruction. + tls_slot.Set(value); +} + +} // namespace + +TEST(ThreadLocalStorageTest, Basics) { + ThreadLocalStorage::Slot slot; + slot.Set(reinterpret_cast<void*>(123)); + int value = reinterpret_cast<intptr_t>(slot.Get()); + EXPECT_EQ(value, 123); + slot.Free(); +} + +#if defined(THREAD_SANITIZER) +// Do not run the test under ThreadSanitizer. Because this test iterates its +// own TSD destructor for the maximum possible number of times, TSan can't jump +// in after the last destructor invocation, therefore the destructor remains +// unsynchronized with the following users of the same TSD slot. This results +// in race reports between the destructor and functions in other tests. +#define MAYBE_TLSDestructors DISABLED_TLSDestructors +#else +#define MAYBE_TLSDestructors TLSDestructors +#endif +TEST(ThreadLocalStorageTest, MAYBE_TLSDestructors) { + // Create a TLS index with a destructor. Create a set of + // threads that set the TLS, while the destructor cleans it up. + // After the threads finish, verify that the value is cleaned up. + const int kNumThreads = 5; + int values[kNumThreads]; + ThreadLocalStorageRunner* thread_delegates[kNumThreads]; + DelegateSimpleThread* threads[kNumThreads]; + + tls_slot.Initialize(ThreadLocalStorageCleanup); + + // Spawn the threads. + for (int index = 0; index < kNumThreads; index++) { + values[index] = kInitialTlsValue; + thread_delegates[index] = new ThreadLocalStorageRunner(&values[index]); + threads[index] = new DelegateSimpleThread(thread_delegates[index], + "tls thread"); + threads[index]->Start(); + } + + // Wait for the threads to finish. + for (int index = 0; index < kNumThreads; index++) { + threads[index]->Join(); + delete threads[index]; + delete thread_delegates[index]; + + // Verify that the destructor was called and that we reset. + EXPECT_EQ(values[index], kFinalTlsValue); + } + tls_slot.Free(); // Stop doing callbacks to cleanup threads. +} + +} // namespace base
diff --git a/src/base/threading/thread_local_storage_win.cc b/src/base/threading/thread_local_storage_win.cc new file mode 100644 index 0000000..0ae3cb4 --- /dev/null +++ b/src/base/threading/thread_local_storage_win.cc
@@ -0,0 +1,277 @@ +// 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/threading/thread_local_storage.h" + +#include <windows.h> + +#include "base/logging.h" + + +namespace { +// In order to make TLS destructors work, we need to keep function +// pointers to the destructor for each TLS that we allocate. +// We make this work by allocating a single OS-level TLS, which +// contains an array of slots for the application to use. In +// parallel, we also allocate an array of destructors, which we +// keep track of and call when threads terminate. + +// g_native_tls_key is the one native TLS that we use. It stores our table. +long g_native_tls_key = TLS_OUT_OF_INDEXES; + +// g_last_used_tls_key is the high-water-mark of allocated thread local storage. +// Each allocation is an index into our g_tls_destructors[]. Each such index is +// assigned to the instance variable slot_ in a ThreadLocalStorage::Slot +// instance. We reserve the value slot_ == 0 to indicate that the corresponding +// instance of ThreadLocalStorage::Slot has been freed (i.e., destructor called, +// etc.). This reserved use of 0 is then stated as the initial value of +// g_last_used_tls_key, so that the first issued index will be 1. +long g_last_used_tls_key = 0; + +// The maximum number of 'slots' in our thread local storage stack. +const int kThreadLocalStorageSize = 64; + +// The maximum number of times to try to clear slots by calling destructors. +// Use pthread naming convention for clarity. +const int kMaxDestructorIterations = kThreadLocalStorageSize; + +// An array of destructor function pointers for the slots. If a slot has a +// destructor, it will be stored in its corresponding entry in this array. +// The elements are volatile to ensure that when the compiler reads the value +// to potentially call the destructor, it does so once, and that value is tested +// for null-ness and then used. Yes, that would be a weird de-optimization, +// but I can imagine some register machines where it was just as easy to +// re-fetch an array element, and I want to be sure a call to free the key +// (i.e., null out the destructor entry) that happens on a separate thread can't +// hurt the racy calls to the destructors on another thread. +volatile base::ThreadLocalStorage::TLSDestructorFunc + g_tls_destructors[kThreadLocalStorageSize]; + +void** ConstructTlsVector() { + if (g_native_tls_key == TLS_OUT_OF_INDEXES) { + long value = TlsAlloc(); + DCHECK(value != TLS_OUT_OF_INDEXES); + + // Atomically test-and-set the tls_key. If the key is TLS_OUT_OF_INDEXES, + // go ahead and set it. Otherwise, do nothing, as another + // thread already did our dirty work. + if (TLS_OUT_OF_INDEXES != InterlockedCompareExchange( + &g_native_tls_key, value, TLS_OUT_OF_INDEXES)) { + // We've been shortcut. Another thread replaced g_native_tls_key first so + // we need to destroy our index and use the one the other thread got + // first. + TlsFree(value); + } + } + DCHECK(!TlsGetValue(g_native_tls_key)); + + // Some allocators, such as TCMalloc, make use of thread local storage. + // As a result, any attempt to call new (or malloc) will lazily cause such a + // system to initialize, which will include registering for a TLS key. If we + // are not careful here, then that request to create a key will call new back, + // and we'll have an infinite loop. We avoid that as follows: + // Use a stack allocated vector, so that we don't have dependence on our + // allocator until our service is in place. (i.e., don't even call new until + // after we're setup) + void* stack_allocated_tls_data[kThreadLocalStorageSize]; + memset(stack_allocated_tls_data, 0, sizeof(stack_allocated_tls_data)); + // Ensure that any rentrant calls change the temp version. + TlsSetValue(g_native_tls_key, stack_allocated_tls_data); + + // Allocate an array to store our data. + void** tls_data = new void*[kThreadLocalStorageSize]; + memcpy(tls_data, stack_allocated_tls_data, sizeof(stack_allocated_tls_data)); + TlsSetValue(g_native_tls_key, tls_data); + return tls_data; +} + +// Called when we terminate a thread, this function calls any TLS destructors +// that are pending for this thread. +void WinThreadExit() { + if (g_native_tls_key == TLS_OUT_OF_INDEXES) + return; + + void** tls_data = static_cast<void**>(TlsGetValue(g_native_tls_key)); + // Maybe we have never initialized TLS for this thread. + if (!tls_data) + return; + + // Some allocators, such as TCMalloc, use TLS. As a result, when a thread + // terminates, one of the destructor calls we make may be to shut down an + // allocator. We have to be careful that after we've shutdown all of the + // known destructors (perchance including an allocator), that we don't call + // the allocator and cause it to resurrect itself (with no possibly destructor + // call to follow). We handle this problem as follows: + // Switch to using a stack allocated vector, so that we don't have dependence + // on our allocator after we have called all g_tls_destructors. (i.e., don't + // even call delete[] after we're done with destructors.) + void* stack_allocated_tls_data[kThreadLocalStorageSize]; + memcpy(stack_allocated_tls_data, tls_data, sizeof(stack_allocated_tls_data)); + // Ensure that any re-entrant calls change the temp version. + TlsSetValue(g_native_tls_key, stack_allocated_tls_data); + delete[] tls_data; // Our last dependence on an allocator. + + int remaining_attempts = kMaxDestructorIterations; + bool need_to_scan_destructors = true; + while (need_to_scan_destructors) { + need_to_scan_destructors = false; + // Try to destroy the first-created-slot (which is slot 1) in our last + // destructor call. That user was able to function, and define a slot with + // no other services running, so perhaps it is a basic service (like an + // allocator) and should also be destroyed last. If we get the order wrong, + // then we'll itterate several more times, so it is really not that + // critical (but it might help). + for (int slot = g_last_used_tls_key; slot > 0; --slot) { + void* value = stack_allocated_tls_data[slot]; + if (value == NULL) + continue; + base::ThreadLocalStorage::TLSDestructorFunc destructor = + g_tls_destructors[slot]; + if (destructor == NULL) + continue; + stack_allocated_tls_data[slot] = NULL; // pre-clear the slot. + destructor(value); + // Any destructor might have called a different service, which then set + // a different slot to a non-NULL value. Hence we need to check + // the whole vector again. This is a pthread standard. + need_to_scan_destructors = true; + } + if (--remaining_attempts <= 0) { + NOTREACHED(); // Destructors might not have been called. + break; + } + } + + // Remove our stack allocated vector. + TlsSetValue(g_native_tls_key, NULL); +} + +} // namespace + +namespace base { + +ThreadLocalStorage::Slot::Slot(TLSDestructorFunc destructor) { + initialized_ = false; + slot_ = 0; + Initialize(destructor); +} + +bool ThreadLocalStorage::StaticSlot::Initialize(TLSDestructorFunc destructor) { + if (g_native_tls_key == TLS_OUT_OF_INDEXES || !TlsGetValue(g_native_tls_key)) + ConstructTlsVector(); + + // Grab a new slot. + slot_ = InterlockedIncrement(&g_last_used_tls_key); + DCHECK_GT(slot_, 0); + if (slot_ >= kThreadLocalStorageSize) { + NOTREACHED(); + return false; + } + + // Setup our destructor. + g_tls_destructors[slot_] = destructor; + initialized_ = true; + return true; +} + +void ThreadLocalStorage::StaticSlot::Free() { + // At this time, we don't reclaim old indices for TLS slots. + // So all we need to do is wipe the destructor. + DCHECK_GT(slot_, 0); + DCHECK_LT(slot_, kThreadLocalStorageSize); + g_tls_destructors[slot_] = NULL; + slot_ = 0; + initialized_ = false; +} + +void* ThreadLocalStorage::StaticSlot::Get() const { + void** tls_data = static_cast<void**>(TlsGetValue(g_native_tls_key)); + if (!tls_data) + tls_data = ConstructTlsVector(); + DCHECK_GT(slot_, 0); + DCHECK_LT(slot_, kThreadLocalStorageSize); + return tls_data[slot_]; +} + +void ThreadLocalStorage::StaticSlot::Set(void* value) { + void** tls_data = static_cast<void**>(TlsGetValue(g_native_tls_key)); + if (!tls_data) + tls_data = ConstructTlsVector(); + DCHECK_GT(slot_, 0); + DCHECK_LT(slot_, kThreadLocalStorageSize); + tls_data[slot_] = value; +} + +} // namespace base + +// Thread Termination Callbacks. +// Windows doesn't support a per-thread destructor with its +// TLS primitives. So, we build it manually by inserting a +// function to be called on each thread's exit. +// This magic is from http://www.codeproject.com/threads/tls.asp +// and it works for VC++ 7.0 and later. + +// Force a reference to _tls_used to make the linker create the TLS directory +// if it's not already there. (e.g. if __declspec(thread) is not used). +// Force a reference to p_thread_callback_base to prevent whole program +// optimization from discarding the variable. +#ifdef _WIN64 + +#pragma comment(linker, "/INCLUDE:_tls_used") +#pragma comment(linker, "/INCLUDE:p_thread_callback_base") + +#else // _WIN64 + +#pragma comment(linker, "/INCLUDE:__tls_used") +#pragma comment(linker, "/INCLUDE:_p_thread_callback_base") + +#endif // _WIN64 + +// Static callback function to call with each thread termination. +void NTAPI OnThreadExit(PVOID module, DWORD reason, PVOID reserved) { + // On XP SP0 & SP1, the DLL_PROCESS_ATTACH is never seen. It is sent on SP2+ + // and on W2K and W2K3. So don't assume it is sent. + if (DLL_THREAD_DETACH == reason || DLL_PROCESS_DETACH == reason) + WinThreadExit(); +} + +// .CRT$XLA to .CRT$XLZ is an array of PIMAGE_TLS_CALLBACK pointers that are +// called automatically by the OS loader code (not the CRT) when the module is +// loaded and on thread creation. They are NOT called if the module has been +// loaded by a LoadLibrary() call. It must have implicitly been loaded at +// process startup. +// By implicitly loaded, I mean that it is directly referenced by the main EXE +// or by one of its dependent DLLs. Delay-loaded DLL doesn't count as being +// implicitly loaded. +// +// See VC\crt\src\tlssup.c for reference. + +// extern "C" suppresses C++ name mangling so we know the symbol name for the +// linker /INCLUDE:symbol pragma above. +extern "C" { +// The linker must not discard p_thread_callback_base. (We force a reference +// to this variable with a linker /INCLUDE:symbol pragma to ensure that.) If +// this variable is discarded, the OnThreadExit function will never be called. +#ifdef _WIN64 + +// .CRT section is merged with .rdata on x64 so it must be constant data. +#pragma const_seg(".CRT$XLB") +// When defining a const variable, it must have external linkage to be sure the +// linker doesn't discard it. +extern const PIMAGE_TLS_CALLBACK p_thread_callback_base; +const PIMAGE_TLS_CALLBACK p_thread_callback_base = OnThreadExit; + +// Reset the default section. +#pragma const_seg() + +#else // _WIN64 + +#pragma data_seg(".CRT$XLB") +PIMAGE_TLS_CALLBACK p_thread_callback_base = OnThreadExit; + +// Reset the default section. +#pragma data_seg() + +#endif // _WIN64 +} // extern "C"
diff --git a/src/base/threading/thread_local_unittest.cc b/src/base/threading/thread_local_unittest.cc new file mode 100644 index 0000000..b125a48 --- /dev/null +++ b/src/base/threading/thread_local_unittest.cc
@@ -0,0 +1,169 @@ +// 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/logging.h" +#include "base/threading/simple_thread.h" +#include "base/threading/thread_local.h" +#include "base/synchronization/waitable_event.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +class ThreadLocalTesterBase : public base::DelegateSimpleThreadPool::Delegate { + public: + typedef base::ThreadLocalPointer<ThreadLocalTesterBase> TLPType; + + ThreadLocalTesterBase(TLPType* tlp, base::WaitableEvent* done) + : tlp_(tlp), + done_(done) { + } + virtual ~ThreadLocalTesterBase() {} + + protected: + TLPType* tlp_; + base::WaitableEvent* done_; +}; + +class SetThreadLocal : public ThreadLocalTesterBase { + public: + SetThreadLocal(TLPType* tlp, base::WaitableEvent* done) + : ThreadLocalTesterBase(tlp, done), + val_(NULL) { + } + virtual ~SetThreadLocal() {} + + void set_value(ThreadLocalTesterBase* val) { val_ = val; } + + virtual void Run() OVERRIDE { + DCHECK(!done_->IsSignaled()); + tlp_->Set(val_); + done_->Signal(); + } + + private: + ThreadLocalTesterBase* val_; +}; + +class GetThreadLocal : public ThreadLocalTesterBase { + public: + GetThreadLocal(TLPType* tlp, base::WaitableEvent* done) + : ThreadLocalTesterBase(tlp, done), + ptr_(NULL) { + } + virtual ~GetThreadLocal() {} + + void set_ptr(ThreadLocalTesterBase** ptr) { ptr_ = ptr; } + + virtual void Run() OVERRIDE { + DCHECK(!done_->IsSignaled()); + *ptr_ = tlp_->Get(); + done_->Signal(); + } + + private: + ThreadLocalTesterBase** ptr_; +}; + +} // namespace + +// In this test, we start 2 threads which will access a ThreadLocalPointer. We +// make sure the default is NULL, and the pointers are unique to the threads. +TEST(ThreadLocalTest, Pointer) { + base::DelegateSimpleThreadPool tp1("ThreadLocalTest tp1", 1); + base::DelegateSimpleThreadPool tp2("ThreadLocalTest tp1", 1); + tp1.Start(); + tp2.Start(); + + base::ThreadLocalPointer<ThreadLocalTesterBase> tlp; + + static ThreadLocalTesterBase* const kBogusPointer = + reinterpret_cast<ThreadLocalTesterBase*>(0x1234); + + ThreadLocalTesterBase* tls_val; + base::WaitableEvent done(true, false); + + GetThreadLocal getter(&tlp, &done); + getter.set_ptr(&tls_val); + + // Check that both threads defaulted to NULL. + tls_val = kBogusPointer; + done.Reset(); + tp1.AddWork(&getter); + done.Wait(); + EXPECT_EQ(static_cast<ThreadLocalTesterBase*>(NULL), tls_val); + + tls_val = kBogusPointer; + done.Reset(); + tp2.AddWork(&getter); + done.Wait(); + EXPECT_EQ(static_cast<ThreadLocalTesterBase*>(NULL), tls_val); + + + SetThreadLocal setter(&tlp, &done); + setter.set_value(kBogusPointer); + + // Have thread 1 set their pointer value to kBogusPointer. + done.Reset(); + tp1.AddWork(&setter); + done.Wait(); + + tls_val = NULL; + done.Reset(); + tp1.AddWork(&getter); + done.Wait(); + EXPECT_EQ(kBogusPointer, tls_val); + + // Make sure thread 2 is still NULL + tls_val = kBogusPointer; + done.Reset(); + tp2.AddWork(&getter); + done.Wait(); + EXPECT_EQ(static_cast<ThreadLocalTesterBase*>(NULL), tls_val); + + // Set thread 2 to kBogusPointer + 1. + setter.set_value(kBogusPointer + 1); + + done.Reset(); + tp2.AddWork(&setter); + done.Wait(); + + tls_val = NULL; + done.Reset(); + tp2.AddWork(&getter); + done.Wait(); + EXPECT_EQ(kBogusPointer + 1, tls_val); + + // Make sure thread 1 is still kBogusPointer. + tls_val = NULL; + done.Reset(); + tp1.AddWork(&getter); + done.Wait(); + EXPECT_EQ(kBogusPointer, tls_val); + + tp1.JoinAll(); + tp2.JoinAll(); +} + +TEST(ThreadLocalTest, Boolean) { + { + base::ThreadLocalBoolean tlb; + EXPECT_FALSE(tlb.Get()); + + tlb.Set(false); + EXPECT_FALSE(tlb.Get()); + + tlb.Set(true); + EXPECT_TRUE(tlb.Get()); + } + + // Our slot should have been freed, we're all reset. + { + base::ThreadLocalBoolean tlb; + EXPECT_FALSE(tlb.Get()); + } +} + +} // namespace base
diff --git a/src/base/threading/thread_local_win.cc b/src/base/threading/thread_local_win.cc new file mode 100644 index 0000000..56d3a3a --- /dev/null +++ b/src/base/threading/thread_local_win.cc
@@ -0,0 +1,42 @@ +// Copyright (c) 2010 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/threading/thread_local.h" + +#include <windows.h> + +#include "base/logging.h" + +namespace base { + +namespace internal { + +// static +void ThreadLocalPlatform::AllocateSlot(SlotType& slot) { + slot = TlsAlloc(); + CHECK_NE(slot, TLS_OUT_OF_INDEXES); +} + +// static +void ThreadLocalPlatform::FreeSlot(SlotType& slot) { + if (!TlsFree(slot)) { + NOTREACHED() << "Failed to deallocate tls slot with TlsFree()."; + } +} + +// static +void* ThreadLocalPlatform::GetValueFromSlot(SlotType& slot) { + return TlsGetValue(slot); +} + +// static +void ThreadLocalPlatform::SetValueInSlot(SlotType& slot, void* value) { + if (!TlsSetValue(slot, value)) { + LOG(FATAL) << "Failed to TlsSetValue()."; + } +} + +} // namespace internal + +} // namespace base
diff --git a/src/base/threading/thread_restrictions.cc b/src/base/threading/thread_restrictions.cc new file mode 100644 index 0000000..abf5068 --- /dev/null +++ b/src/base/threading/thread_restrictions.cc
@@ -0,0 +1,92 @@ +// 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/threading/thread_restrictions.h" + +#if ENABLE_THREAD_RESTRICTIONS + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/thread_local.h" + +namespace base { + +namespace { + +LazyInstance<ThreadLocalBoolean>::Leaky + g_io_disallowed = LAZY_INSTANCE_INITIALIZER; + +LazyInstance<ThreadLocalBoolean>::Leaky + g_singleton_disallowed = LAZY_INSTANCE_INITIALIZER; + +LazyInstance<ThreadLocalBoolean>::Leaky + g_wait_disallowed = LAZY_INSTANCE_INITIALIZER; + +} // anonymous namespace + +// static +bool ThreadRestrictions::SetIOAllowed(bool allowed) { + bool previous_disallowed = g_io_disallowed.Get().Get(); + g_io_disallowed.Get().Set(!allowed); + return !previous_disallowed; +} + +// static +void ThreadRestrictions::AssertIOAllowed() { + if (g_io_disallowed.Get().Get()) { + LOG(FATAL) << + "Function marked as IO-only was called from a thread that " + "disallows IO! If this thread really should be allowed to " + "make IO calls, adjust the call to " + "base::ThreadRestrictions::SetIOAllowed() in this thread's " + "startup."; + } +} + +#ifdef COBALT +// static +bool ThreadRestrictions::GetSingletonAllowed() { + return !g_singleton_disallowed.Get().Get(); +} +#endif // COBALT + +// static +bool ThreadRestrictions::SetSingletonAllowed(bool allowed) { + bool previous_disallowed = g_singleton_disallowed.Get().Get(); + g_singleton_disallowed.Get().Set(!allowed); + return !previous_disallowed; +} + +// static +void ThreadRestrictions::AssertSingletonAllowed() { + if (g_singleton_disallowed.Get().Get()) { + LOG(FATAL) << "LazyInstance/Singleton is not allowed to be used on this " + << "thread. Most likely it's because this thread is not " + << "joinable, so AtExitManager may have deleted the object " + << "on shutdown, leading to a potential shutdown crash."; + } +} + +// static +void ThreadRestrictions::DisallowWaiting() { + g_wait_disallowed.Get().Set(true); +} + +// static +void ThreadRestrictions::AssertWaitAllowed() { + if (g_wait_disallowed.Get().Get()) { + LOG(FATAL) << "Waiting is not allowed to be used on this thread to prevent" + << "jank and deadlock."; + } +} + +bool ThreadRestrictions::SetWaitAllowed(bool allowed) { + bool previous_disallowed = g_wait_disallowed.Get().Get(); + g_wait_disallowed.Get().Set(!allowed); + return !previous_disallowed; +} + +} // namespace base + +#endif // ENABLE_THREAD_RESTRICTIONS
diff --git a/src/base/threading/thread_restrictions.h b/src/base/threading/thread_restrictions.h new file mode 100644 index 0000000..7f9f655 --- /dev/null +++ b/src/base/threading/thread_restrictions.h
@@ -0,0 +1,245 @@ +// 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_THREADING_THREAD_RESTRICTIONS_H_ +#define BASE_THREADING_THREAD_RESTRICTIONS_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" + +// See comment at top of thread_checker.h +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) +#define ENABLE_THREAD_RESTRICTIONS 1 +#else +#define ENABLE_THREAD_RESTRICTIONS 0 +#endif + +class AcceleratedPresenter; +class BrowserProcessImpl; +class HistogramSynchronizer; +class MetricsService; +class NativeBackendKWallet; +class ScopedAllowWaitForLegacyWebViewApi; +class TestingAutomationProvider; + +namespace browser_sync { +class NonFrontendDataTypeController; +class UIModelWorker; +} +namespace cc { +class CompletionEvent; +} +namespace chromeos { +class AudioMixerAlsa; +class BlockingMethodCaller; +namespace system { +class StatisticsProviderImpl; +} +} +namespace chrome_browser_net { +class Predictor; +} +namespace content { +class BrowserGpuChannelHostFactory; +class GLHelper; +class GpuChannelHost; +class RenderWidgetHelper; +class TextInputClientMac; +} +namespace dbus { +class Bus; +} +namespace disk_cache { +class BackendImpl; +class InFlightIO; +} +namespace media { +class AudioOutputController; +} +namespace net { +class FileStreamPosix; +class FileStreamWin; +namespace internal { +class AddressTrackerLinux; +} +} + +namespace remoting { +class AutoThread; +} + +namespace base { + +class SequencedWorkerPool; +class SimpleThread; +class Thread; +class ThreadTestHelper; + +// Certain behavior is disallowed on certain threads. ThreadRestrictions helps +// enforce these rules. Examples of such rules: +// +// * Do not do blocking IO (makes the thread janky) +// * Do not access Singleton/LazyInstance (may lead to shutdown crashes) +// +// Here's more about how the protection works: +// +// 1) If a thread should not be allowed to make IO calls, mark it: +// base::ThreadRestrictions::SetIOAllowed(false); +// By default, threads *are* allowed to make IO calls. +// In Chrome browser code, IO calls should be proxied to the File thread. +// +// 2) If a function makes a call that will go out to disk, check whether the +// current thread is allowed: +// base::ThreadRestrictions::AssertIOAllowed(); +// +// +// Style tip: where should you put AssertIOAllowed checks? It's best +// if you put them as close to the disk access as possible, at the +// lowest level. This rule is simple to follow and helps catch all +// callers. For example, if your function GoDoSomeBlockingDiskCall() +// only calls other functions in Chrome and not fopen(), you should go +// add the AssertIOAllowed checks in the helper functions. + +class BASE_EXPORT ThreadRestrictions { + public: + // Constructing a ScopedAllowIO temporarily allows IO for the current + // thread. Doing this is almost certainly always incorrect. + class BASE_EXPORT ScopedAllowIO { + public: + ScopedAllowIO() { previous_value_ = SetIOAllowed(true); } + ~ScopedAllowIO() { SetIOAllowed(previous_value_); } + private: + // Whether IO is allowed when the ScopedAllowIO was constructed. + bool previous_value_; + + DISALLOW_COPY_AND_ASSIGN(ScopedAllowIO); + }; + + // Constructing a ScopedAllowSingleton temporarily allows accessing for the + // current thread. Doing this is almost always incorrect. + class BASE_EXPORT ScopedAllowSingleton { + public: + ScopedAllowSingleton() { previous_value_ = SetSingletonAllowed(true); } + ~ScopedAllowSingleton() { SetSingletonAllowed(previous_value_); } + private: + // Whether singleton use is allowed when the ScopedAllowSingleton was + // constructed. + bool previous_value_; + + DISALLOW_COPY_AND_ASSIGN(ScopedAllowSingleton); + }; + +#if ENABLE_THREAD_RESTRICTIONS + // Set whether the current thread to make IO calls. + // Threads start out in the *allowed* state. + // Returns the previous value. + static bool SetIOAllowed(bool allowed); + + // Check whether the current thread is allowed to make IO calls, + // and DCHECK if not. See the block comment above the class for + // a discussion of where to add these checks. + static void AssertIOAllowed(); + +#if defined(COBALT) + // Get whether the current thread can use singletons. + static bool GetSingletonAllowed(); +#endif // COBALT + + // Set whether the current thread can use singletons. Returns the previous + // value. + static bool SetSingletonAllowed(bool allowed); + + // Check whether the current thread is allowed to use singletons (Singleton / + // LazyInstance). DCHECKs if not. + static void AssertSingletonAllowed(); + + // Disable waiting on the current thread. Threads start out in the *allowed* + // state. Returns the previous value. + static void DisallowWaiting(); + + // Check whether the current thread is allowed to wait, and DCHECK if not. + static void AssertWaitAllowed(); +#else + // Inline the empty definitions of these functions so that they can be + // compiled out. + static bool SetIOAllowed(bool /*allowed*/) { return true; } + static void AssertIOAllowed() {} +#if defined(COBALT) + static bool GetSingletonAllowed() { return true; } +#endif // COBALT + static bool SetSingletonAllowed(bool /*allowed*/) { return true; } + static void AssertSingletonAllowed() {} + static void DisallowWaiting() {} + static void AssertWaitAllowed() {} +#endif + + private: + // DO NOT ADD ANY OTHER FRIEND STATEMENTS, talk to jam or brettw first. + // BEGIN ALLOWED USAGE. + friend class content::RenderWidgetHelper; + friend class ::HistogramSynchronizer; + friend class ::ScopedAllowWaitForLegacyWebViewApi; + friend class ::TestingAutomationProvider; + friend class cc::CompletionEvent; + friend class remoting::AutoThread; + friend class MessagePumpDefault; + friend class SequencedWorkerPool; + friend class SimpleThread; + friend class Thread; + friend class ThreadTestHelper; + + // END ALLOWED USAGE. + // BEGIN USAGE THAT NEEDS TO BE FIXED. + friend class ::chromeos::AudioMixerAlsa; // http://crbug.com/125206 + friend class ::chromeos::BlockingMethodCaller; // http://crbug.com/125360 + friend class ::chromeos::system::StatisticsProviderImpl; // http://crbug.com/125385 + friend class browser_sync::NonFrontendDataTypeController; // http://crbug.com/19757 + friend class browser_sync::UIModelWorker; // http://crbug.com/19757 + friend class chrome_browser_net::Predictor; // http://crbug.com/78451 + friend class + content::BrowserGpuChannelHostFactory; // http://crbug.com/125248 + friend class content::GLHelper; // http://crbug.com/125415 + friend class content::GpuChannelHost; // http://crbug.com/125264 + friend class content::TextInputClientMac; // http://crbug.com/121917 + friend class dbus::Bus; // http://crbug.com/125222 + friend class disk_cache::BackendImpl; // http://crbug.com/74623 + friend class disk_cache::InFlightIO; // http://crbug.com/74623 + friend class media::AudioOutputController; // http://crbug.com/120973 + friend class net::FileStreamPosix; // http://crbug.com/115067 + friend class net::FileStreamWin; // http://crbug.com/115067 + friend class net::internal::AddressTrackerLinux; // http://crbug.com/125097 + friend class ::AcceleratedPresenter; // http://crbug.com/125391 + friend class ::BrowserProcessImpl; // http://crbug.com/125207 + friend class ::MetricsService; // http://crbug.com/124954 + friend class ::NativeBackendKWallet; // http://crbug.com/125331 + // END USAGE THAT NEEDS TO BE FIXED. + +#if ENABLE_THREAD_RESTRICTIONS + static bool SetWaitAllowed(bool allowed); +#else + static bool SetWaitAllowed(bool /*allowed*/) { return true; } +#endif + + // Constructing a ScopedAllowWait temporarily allows waiting on the current + // thread. Doing this is almost always incorrect, which is why we limit who + // can use this through friend. If you find yourself needing to use this, find + // another way. Talk to jam or brettw. + class BASE_EXPORT ScopedAllowWait { + public: + ScopedAllowWait() { previous_value_ = SetWaitAllowed(true); } + ~ScopedAllowWait() { SetWaitAllowed(previous_value_); } + private: + // Whether singleton use is allowed when the ScopedAllowWait was + // constructed. + bool previous_value_; + + DISALLOW_COPY_AND_ASSIGN(ScopedAllowWait); + }; + + DISALLOW_IMPLICIT_CONSTRUCTORS(ThreadRestrictions); +}; + +} // namespace base + +#endif // BASE_THREADING_THREAD_RESTRICTIONS_H_
diff --git a/src/base/threading/thread_unittest.cc b/src/base/threading/thread_unittest.cc new file mode 100644 index 0000000..28696d9 --- /dev/null +++ b/src/base/threading/thread_unittest.cc
@@ -0,0 +1,237 @@ +// 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/threading/thread.h" + +#include <vector> + +#include "base/bind.h" +#include "base/message_loop.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +using base::Thread; + +typedef PlatformTest ThreadTest; + +namespace { + +void ToggleValue(bool* value) { + ANNOTATE_BENIGN_RACE(value, "Test-only data race on boolean " + "in base/thread_unittest"); + *value = !*value; +} + +class SleepInsideInitThread : public Thread { + public: + SleepInsideInitThread() : Thread("none") { + init_called_ = false; + ANNOTATE_BENIGN_RACE( + this, "Benign test-only data race on vptr - http://crbug.com/98219"); + } + virtual ~SleepInsideInitThread() { + Stop(); + } + + virtual void Init() OVERRIDE { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(500)); + init_called_ = true; + } + bool InitCalled() { return init_called_; } + private: + bool init_called_; +}; + +enum ThreadEvent { + // Thread::Init() was called. + THREAD_EVENT_INIT = 0, + + // The MessageLoop for the thread was deleted. + THREAD_EVENT_MESSAGE_LOOP_DESTROYED, + + // Thread::CleanUp() was called. + THREAD_EVENT_CLEANUP, + + // Keep at end of list. + THREAD_NUM_EVENTS +}; + +typedef std::vector<ThreadEvent> EventList; + +class CaptureToEventList : public Thread { + public: + // This Thread pushes events into the vector |event_list| to show + // the order they occured in. |event_list| must remain valid for the + // lifetime of this thread. + explicit CaptureToEventList(EventList* event_list) + : Thread("none"), + event_list_(event_list) { + } + + virtual ~CaptureToEventList() { + Stop(); + } + + virtual void Init() OVERRIDE { + event_list_->push_back(THREAD_EVENT_INIT); + } + + virtual void CleanUp() OVERRIDE { + event_list_->push_back(THREAD_EVENT_CLEANUP); + } + + private: + EventList* event_list_; +}; + +// Observer that writes a value into |event_list| when a message loop has been +// destroyed. +class CapturingDestructionObserver : public MessageLoop::DestructionObserver { + public: + // |event_list| must remain valid throughout the observer's lifetime. + explicit CapturingDestructionObserver(EventList* event_list) + : event_list_(event_list) { + } + + // DestructionObserver implementation: + virtual void WillDestroyCurrentMessageLoop() OVERRIDE { + event_list_->push_back(THREAD_EVENT_MESSAGE_LOOP_DESTROYED); + event_list_ = NULL; + } + + private: + EventList* event_list_; +}; + +// Task that adds a destruction observer to the current message loop. +void RegisterDestructionObserver(MessageLoop::DestructionObserver* observer) { + MessageLoop::current()->AddDestructionObserver(observer); +} + +} // namespace + +TEST_F(ThreadTest, Restart) { + Thread a("Restart"); + a.Stop(); + EXPECT_FALSE(a.message_loop()); + EXPECT_FALSE(a.IsRunning()); + EXPECT_TRUE(a.Start()); + EXPECT_TRUE(a.message_loop()); + EXPECT_TRUE(a.IsRunning()); + a.Stop(); + EXPECT_FALSE(a.message_loop()); + EXPECT_FALSE(a.IsRunning()); + EXPECT_TRUE(a.Start()); + EXPECT_TRUE(a.message_loop()); + EXPECT_TRUE(a.IsRunning()); + a.Stop(); + EXPECT_FALSE(a.message_loop()); + EXPECT_FALSE(a.IsRunning()); + a.Stop(); + EXPECT_FALSE(a.message_loop()); + EXPECT_FALSE(a.IsRunning()); +} + +TEST_F(ThreadTest, StartWithOptions_StackSize) { + Thread a("StartWithStackSize"); + // Ensure that the thread can work with only 12 kb and still process a + // message. + Thread::Options options; + options.stack_size = 12*1024; + EXPECT_TRUE(a.StartWithOptions(options)); + EXPECT_TRUE(a.message_loop()); + EXPECT_TRUE(a.IsRunning()); + + bool was_invoked = false; + a.message_loop()->PostTask(FROM_HERE, base::Bind(&ToggleValue, &was_invoked)); + + // wait for the task to run (we could use a kernel event here + // instead to avoid busy waiting, but this is sufficient for + // testing purposes). + for (int i = 100; i >= 0 && !was_invoked; --i) { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); + } + EXPECT_TRUE(was_invoked); +} + +TEST_F(ThreadTest, TwoTasks) { + bool was_invoked = false; + { + Thread a("TwoTasks"); + EXPECT_TRUE(a.Start()); + EXPECT_TRUE(a.message_loop()); + + // Test that all events are dispatched before the Thread object is + // destroyed. We do this by dispatching a sleep event before the + // event that will toggle our sentinel value. + a.message_loop()->PostTask( + FROM_HERE, + base::Bind( + static_cast<void (*)(base::TimeDelta)>( + &base::PlatformThread::Sleep), + base::TimeDelta::FromMilliseconds(20))); + a.message_loop()->PostTask(FROM_HERE, base::Bind(&ToggleValue, + &was_invoked)); + } + EXPECT_TRUE(was_invoked); +} + +TEST_F(ThreadTest, StopSoon) { + Thread a("StopSoon"); + EXPECT_TRUE(a.Start()); + EXPECT_TRUE(a.message_loop()); + EXPECT_TRUE(a.IsRunning()); + a.StopSoon(); + a.StopSoon(); + a.Stop(); + EXPECT_FALSE(a.message_loop()); + EXPECT_FALSE(a.IsRunning()); +} + +TEST_F(ThreadTest, ThreadName) { + Thread a("ThreadName"); + EXPECT_TRUE(a.Start()); + EXPECT_EQ("ThreadName", a.thread_name()); +} + +// Make sure we can't use a thread between Start() and Init(). +TEST_F(ThreadTest, SleepInsideInit) { + SleepInsideInitThread t; + EXPECT_FALSE(t.InitCalled()); + t.Start(); + EXPECT_TRUE(t.InitCalled()); +} + +// Make sure that the destruction sequence is: +// +// (1) Thread::CleanUp() +// (2) MessageLoop::~MessageLoop() +// MessageLoop::DestructionObservers called. +TEST_F(ThreadTest, CleanUp) { + EventList captured_events; + CapturingDestructionObserver loop_destruction_observer(&captured_events); + + { + // Start a thread which writes its event into |captured_events|. + CaptureToEventList t(&captured_events); + EXPECT_TRUE(t.Start()); + EXPECT_TRUE(t.message_loop()); + EXPECT_TRUE(t.IsRunning()); + + // Register an observer that writes into |captured_events| once the + // thread's message loop is destroyed. + t.message_loop()->PostTask( + FROM_HERE, base::Bind(&RegisterDestructionObserver, + base::Unretained(&loop_destruction_observer))); + + // Upon leaving this scope, the thread is deleted. + } + + // Check the order of events during shutdown. + ASSERT_EQ(static_cast<size_t>(THREAD_NUM_EVENTS), captured_events.size()); + EXPECT_EQ(THREAD_EVENT_INIT, captured_events[0]); + EXPECT_EQ(THREAD_EVENT_CLEANUP, captured_events[1]); + EXPECT_EQ(THREAD_EVENT_MESSAGE_LOOP_DESTROYED, captured_events[2]); +}
diff --git a/src/base/threading/watchdog.cc b/src/base/threading/watchdog.cc new file mode 100644 index 0000000..d060655 --- /dev/null +++ b/src/base/threading/watchdog.cc
@@ -0,0 +1,178 @@ +// 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/threading/watchdog.h" + +#include "base/compiler_specific.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/platform_thread.h" + +namespace base { + +namespace { + +// When the debugger breaks (when we alarm), all the other alarms that are +// armed will expire (also alarm). To diminish this effect, we track any +// delay due to debugger breaks, and we *try* to adjust the effective start +// time of other alarms to step past the debugging break. +// 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; + +// When did we last alarm and get stuck (for a while) in a debugger? +TimeTicks g_last_debugged_alarm_time; + +// How long did we sit on a break in the debugger? +TimeDelta g_last_debugged_alarm_delay; + +} // namespace + +// Start thread running in a Disarmed state. +Watchdog::Watchdog(const TimeDelta& duration, + const std::string& thread_watched_name, + bool enabled) + : enabled_(enabled), + lock_(), + condition_variable_(&lock_), + state_(DISARMED), + duration_(duration), + thread_watched_name_(thread_watched_name), + ALLOW_THIS_IN_INITIALIZER_LIST(delegate_(this)) { + if (!enabled_) + return; // Don't start thread, or doing anything really. + enabled_ = PlatformThread::Create(0, // Default stack size. + &delegate_, + &handle_); + DCHECK(enabled_); +} + +// Notify watchdog thread, and wait for it to finish up. +Watchdog::~Watchdog() { + if (!enabled_) + return; + if (!IsJoinable()) + Cleanup(); + condition_variable_.Signal(); + PlatformThread::Join(handle_); +} + +void Watchdog::Cleanup() { + if (!enabled_) + return; + { + AutoLock lock(lock_); + state_ = SHUTDOWN; + } + condition_variable_.Signal(); +} + +bool Watchdog::IsJoinable() { + if (!enabled_) + return true; + AutoLock lock(lock_); + return (state_ == JOINABLE); +} + +void Watchdog::Arm() { + ArmAtStartTime(TimeTicks::Now()); +} + +void Watchdog::ArmSomeTimeDeltaAgo(const TimeDelta& time_delta) { + ArmAtStartTime(TimeTicks::Now() - time_delta); +} + +// Start clock for watchdog. +void Watchdog::ArmAtStartTime(const TimeTicks start_time) { + { + AutoLock lock(lock_); + start_time_ = start_time; + state_ = ARMED; + } + // Force watchdog to wake up, and go to sleep with the timer ticking with the + // proper duration. + condition_variable_.Signal(); +} + +// Disable watchdog so that it won't do anything when time expires. +void Watchdog::Disarm() { + AutoLock lock(lock_); + state_ = DISARMED; + // We don't need to signal, as the watchdog will eventually wake up, and it + // will check its state and time, and act accordingly. +} + +void Watchdog::Alarm() { + DVLOG(1) << "Watchdog alarmed for " << thread_watched_name_; +} + +//------------------------------------------------------------------------------ +// Internal private methods that the watchdog thread uses. + +void Watchdog::ThreadDelegate::ThreadMain() { + SetThreadName(); + TimeDelta remaining_duration; + while (1) { + AutoLock lock(watchdog_->lock_); + while (DISARMED == watchdog_->state_) + watchdog_->condition_variable_.Wait(); + if (SHUTDOWN == watchdog_->state_) { + watchdog_->state_ = JOINABLE; + return; + } + DCHECK(ARMED == watchdog_->state_); + remaining_duration = watchdog_->duration_ - + (TimeTicks::Now() - watchdog_->start_time_); + if (remaining_duration.InMilliseconds() > 0) { + // Spurios wake? Timer drifts? Go back to sleep for remaining time. + watchdog_->condition_variable_.TimedWait(remaining_duration); + continue; + } + // 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_) { + // 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_) + // Too many alarms must have taken place. + watchdog_->state_ = DISARMED; + continue; + } + } + watchdog_->state_ = DISARMED; // Only alarm at most once. + TimeTicks last_alarm_time = TimeTicks::Now(); + { + AutoUnlock lock(watchdog_->lock_); + watchdog_->Alarm(); // Set a break point here to debug on alarms. + } + TimeDelta last_alarm_delay = TimeTicks::Now() - last_alarm_time; + 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()); + // This was a real debugger break. + g_last_debugged_alarm_time = last_alarm_time; + g_last_debugged_alarm_delay = last_alarm_delay; + } +} + +void Watchdog::ThreadDelegate::SetThreadName() const { + std::string name = watchdog_->thread_watched_name_ + " Watchdog"; + PlatformThread::SetName(name.c_str()); + DVLOG(1) << "Watchdog active: " << name; +} + +// static +void Watchdog::ResetStaticData() { + AutoLock lock(*g_static_lock.Pointer()); + g_last_debugged_alarm_time = TimeTicks(); + g_last_debugged_alarm_delay = TimeDelta(); +} + +} // namespace base
diff --git a/src/base/threading/watchdog.h b/src/base/threading/watchdog.h new file mode 100644 index 0000000..5b0b210 --- /dev/null +++ b/src/base/threading/watchdog.h
@@ -0,0 +1,94 @@ +// 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. + +// The Watchdog class creates a second thread that can Alarm if a specific +// duration of time passes without proper attention. The duration of time is +// specified at construction time. The Watchdog may be used many times by +// simply calling Arm() (to start timing) and Disarm() (to reset the timer). +// The Watchdog is typically used under a debugger, where the stack traces on +// other threads can be examined if/when the Watchdog alarms. + +// Some watchdogs will be enabled or disabled via command line switches. To +// facilitate such code, an "enabled" argument for the constuctor can be used +// to permanently disable the watchdog. Disabled watchdogs don't even spawn +// a second thread, and their methods call (Arm() and Disarm()) return very +// quickly. + +#ifndef BASE_THREADING_WATCHDOG_H_ +#define BASE_THREADING_WATCHDOG_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/threading/platform_thread.h" +#include "base/time.h" + +namespace base { + +class BASE_EXPORT Watchdog { + public: + // Constructor specifies how long the Watchdog will wait before alarming. + Watchdog(const TimeDelta& duration, + const std::string& thread_watched_name, + bool enabled); + virtual ~Watchdog(); + + // Notify watchdog thread to finish up. Sets the state_ to SHUTDOWN. + void Cleanup(); + + // Returns true if we state_ is JOINABLE (which indicates that Watchdog has + // exited). + bool IsJoinable(); + + // Start timing, and alarm when time expires (unless we're disarm()ed.) + void Arm(); // Arm starting now. + void ArmSomeTimeDeltaAgo(const TimeDelta& time_delta); + void ArmAtStartTime(const TimeTicks start_time); + + // Reset time, and do not set off the alarm. + void Disarm(); + + // Alarm is called if the time expires after an Arm() without someone calling + // Disarm(). This method can be overridden to create testable classes. + virtual void Alarm(); + + // Reset static data to initial state. Useful for tests, to ensure + // they are independent. + static void ResetStaticData(); + + private: + class ThreadDelegate : public PlatformThread::Delegate { + public: + explicit ThreadDelegate(Watchdog* watchdog) : watchdog_(watchdog) { + } + virtual void ThreadMain() OVERRIDE; + private: + void SetThreadName() const; + + Watchdog* watchdog_; + }; + + enum State {ARMED, DISARMED, SHUTDOWN, JOINABLE }; + + bool enabled_; + + Lock lock_; // Mutex for state_. + ConditionVariable condition_variable_; + State state_; + const TimeDelta duration_; // How long after start_time_ do we alarm? + const std::string thread_watched_name_; + PlatformThreadHandle handle_; + ThreadDelegate delegate_; // Store it, because it must outlive the thread. + + TimeTicks start_time_; // Start of epoch, and alarm after duration_. + + DISALLOW_COPY_AND_ASSIGN(Watchdog); +}; + +} // namespace base + +#endif // BASE_THREADING_WATCHDOG_H_
diff --git a/src/base/threading/watchdog_unittest.cc b/src/base/threading/watchdog_unittest.cc new file mode 100644 index 0000000..92ab02c --- /dev/null +++ b/src/base/threading/watchdog_unittest.cc
@@ -0,0 +1,142 @@ +// 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/threading/watchdog.h" + +#include "base/logging.h" +#include "base/synchronization/spin_wait.h" +#include "base/threading/platform_thread.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +//------------------------------------------------------------------------------ +// Provide a derived class to facilitate testing. + +class WatchdogCounter : public Watchdog { + public: + WatchdogCounter(const TimeDelta& duration, + const std::string& thread_watched_name, + bool enabled) + : Watchdog(duration, thread_watched_name, enabled), + alarm_counter_(0) { + } + + virtual ~WatchdogCounter() {} + + virtual void Alarm() OVERRIDE { + alarm_counter_++; + Watchdog::Alarm(); + } + + int alarm_counter() { return alarm_counter_; } + + private: + int alarm_counter_; + + DISALLOW_COPY_AND_ASSIGN(WatchdogCounter); +}; + +class WatchdogTest : public testing::Test { + public: + virtual void SetUp() OVERRIDE { + Watchdog::ResetStaticData(); + } +}; + +} // namespace + +//------------------------------------------------------------------------------ +// Actual tests + +// Minimal constructor/destructor test. +TEST_F(WatchdogTest, StartupShutdownTest) { + Watchdog watchdog1(TimeDelta::FromMilliseconds(300), "Disabled", false); + Watchdog watchdog2(TimeDelta::FromMilliseconds(300), "Enabled", true); +} + +// Test ability to call Arm and Disarm repeatedly. +TEST_F(WatchdogTest, ArmDisarmTest) { + Watchdog watchdog1(TimeDelta::FromMilliseconds(300), "Disabled", false); + watchdog1.Arm(); + watchdog1.Disarm(); + watchdog1.Arm(); + watchdog1.Disarm(); + + Watchdog watchdog2(TimeDelta::FromMilliseconds(300), "Enabled", true); + watchdog2.Arm(); + watchdog2.Disarm(); + watchdog2.Arm(); + watchdog2.Disarm(); +} + +// Make sure a basic alarm fires when the time has expired. +TEST_F(WatchdogTest, AlarmTest) { + WatchdogCounter watchdog(TimeDelta::FromMilliseconds(10), "Enabled", true); + watchdog.Arm(); + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(5), + watchdog.alarm_counter() > 0); + EXPECT_EQ(1, watchdog.alarm_counter()); +} + +// Make sure a basic alarm fires when the time has expired. +TEST_F(WatchdogTest, AlarmPriorTimeTest) { + WatchdogCounter watchdog(TimeDelta(), "Enabled2", true); + // Set a time in the past. + watchdog.ArmSomeTimeDeltaAgo(TimeDelta::FromSeconds(2)); + // It should instantly go off, but certainly in less than 5 minutes. + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(5), + watchdog.alarm_counter() > 0); + + EXPECT_EQ(1, watchdog.alarm_counter()); +} + +// Make sure a disable alarm does nothing, even if we arm it. +TEST_F(WatchdogTest, ConstructorDisabledTest) { + WatchdogCounter watchdog(TimeDelta::FromMilliseconds(10), "Disabled", false); + watchdog.Arm(); + // Alarm should not fire, as it was disabled. + PlatformThread::Sleep(TimeDelta::FromMilliseconds(500)); + EXPECT_EQ(0, watchdog.alarm_counter()); +} + +// Make sure Disarming will prevent firing, even after Arming. +TEST_F(WatchdogTest, DisarmTest) { + WatchdogCounter watchdog(TimeDelta::FromSeconds(1), "Enabled3", true); + + TimeTicks start = TimeTicks::Now(); + watchdog.Arm(); + // Sleep a bit, but not past the alarm point. + PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); + watchdog.Disarm(); + TimeTicks end = TimeTicks::Now(); + + if (end - start > TimeDelta::FromMilliseconds(500)) { + LOG(WARNING) << "100ms sleep took over 500ms, making the results of this " + << "timing-sensitive test suspicious. Aborting now."; + return; + } + + // Alarm should not have fired before it was disarmed. + EXPECT_EQ(0, watchdog.alarm_counter()); + + // Sleep past the point where it would have fired if it wasn't disarmed, + // and verify that it didn't fire. + PlatformThread::Sleep(TimeDelta::FromSeconds(1)); + EXPECT_EQ(0, watchdog.alarm_counter()); + + // ...but even after disarming, we can still use the alarm... + // Set a time greater than the timeout into the past. + watchdog.ArmSomeTimeDeltaAgo(TimeDelta::FromSeconds(10)); + // It should almost instantly go off, but certainly in less than 5 minutes. + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(5), + watchdog.alarm_counter() > 0); + + EXPECT_EQ(1, watchdog.alarm_counter()); +} + +} // namespace base
diff --git a/src/base/threading/worker_pool.cc b/src/base/threading/worker_pool.cc new file mode 100644 index 0000000..16cc061 --- /dev/null +++ b/src/base/threading/worker_pool.cc
@@ -0,0 +1,116 @@ +// 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/threading/worker_pool.h" + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/lazy_instance.h" +#include "base/task_runner.h" +#include "base/threading/post_task_and_reply_impl.h" +#include "base/tracked_objects.h" + +namespace base { + +namespace { + +class PostTaskAndReplyWorkerPool : public internal::PostTaskAndReplyImpl { + public: + PostTaskAndReplyWorkerPool(bool task_is_slow) : task_is_slow_(task_is_slow) { + } + + private: + virtual bool PostTask(const tracked_objects::Location& from_here, + const Closure& task) OVERRIDE { + return WorkerPool::PostTask(from_here, task, task_is_slow_); + } + + bool task_is_slow_; +}; + +// WorkerPoolTaskRunner --------------------------------------------- +// A TaskRunner which posts tasks to a WorkerPool with a +// fixed ShutdownBehavior. +// +// Note that this class is RefCountedThreadSafe (inherited from TaskRunner). +class WorkerPoolTaskRunner : public TaskRunner { + public: + WorkerPoolTaskRunner(bool tasks_are_slow); + + // TaskRunner implementation + virtual bool PostDelayedTask(const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) OVERRIDE; + virtual bool RunsTasksOnCurrentThread() const OVERRIDE; + + private: + virtual ~WorkerPoolTaskRunner(); + + // Helper function for posting a delayed task. Asserts that the delay is + // zero because non-zero delays are not supported. + bool PostDelayedTaskAssertZeroDelay( + const tracked_objects::Location& from_here, + const Closure& task, + base::TimeDelta delay); + + const bool tasks_are_slow_; + + DISALLOW_COPY_AND_ASSIGN(WorkerPoolTaskRunner); +}; + +WorkerPoolTaskRunner::WorkerPoolTaskRunner(bool tasks_are_slow) + : tasks_are_slow_(tasks_are_slow) { +} + +WorkerPoolTaskRunner::~WorkerPoolTaskRunner() { +} + +bool WorkerPoolTaskRunner::PostDelayedTask( + const tracked_objects::Location& from_here, + const Closure& task, + TimeDelta delay) { + return PostDelayedTaskAssertZeroDelay(from_here, task, delay); +} + +bool WorkerPoolTaskRunner::RunsTasksOnCurrentThread() const { + return WorkerPool::RunsTasksOnCurrentThread(); +} + +bool WorkerPoolTaskRunner::PostDelayedTaskAssertZeroDelay( + const tracked_objects::Location& from_here, + const Closure& task, + base::TimeDelta delay) { + DCHECK_EQ(delay.InMillisecondsRoundedUp(), 0) + << "WorkerPoolTaskRunner does not support non-zero delays"; + return WorkerPool::PostTask(from_here, task, tasks_are_slow_); +} + +struct TaskRunnerHolder { + TaskRunnerHolder() { + taskrunners_[0] = new WorkerPoolTaskRunner(false); + taskrunners_[1] = new WorkerPoolTaskRunner(true); + } + scoped_refptr<TaskRunner> taskrunners_[2]; +}; + +base::LazyInstance<TaskRunnerHolder>::Leaky + g_taskrunners = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +bool WorkerPool::PostTaskAndReply(const tracked_objects::Location& from_here, + const Closure& task, + const Closure& reply, + bool task_is_slow) { + return PostTaskAndReplyWorkerPool(task_is_slow).PostTaskAndReply( + from_here, task, reply); +} + +// static +const scoped_refptr<TaskRunner>& +WorkerPool::GetTaskRunner(bool tasks_are_slow) { + return g_taskrunners.Get().taskrunners_[tasks_are_slow]; +} + +} // namespace base
diff --git a/src/base/threading/worker_pool.h b/src/base/threading/worker_pool.h new file mode 100644 index 0000000..333b495 --- /dev/null +++ b/src/base/threading/worker_pool.h
@@ -0,0 +1,60 @@ +// 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_THREADING_WORKER_POOL_H_ +#define BASE_THREADING_WORKER_POOL_H_ + +#include "base/base_export.h" +#include "base/callback_forward.h" +#include "base/memory/ref_counted.h" + +class Task; + +namespace tracked_objects { +class Location; +} // namespace tracked_objects + +namespace base { + +class TaskRunner; + +// This is a facility that runs tasks that don't require a specific thread or +// a message loop. +// +// WARNING: This shouldn't be used unless absolutely necessary. We don't wait +// for the worker pool threads to finish on shutdown, so the tasks running +// inside the pool must be extremely careful about other objects they access +// (MessageLoops, Singletons, etc). During shutdown these object may no longer +// exist. +class BASE_EXPORT WorkerPool { + public: + // This function posts |task| to run on a worker thread. |task_is_slow| + // should be used for tasks that will take a long time to execute. Returns + // false if |task| could not be posted to a worker thread. Regardless of + // return value, ownership of |task| is transferred to the worker pool. + static bool PostTask(const tracked_objects::Location& from_here, + const base::Closure& task, bool task_is_slow); + + // Just like MessageLoopProxy::PostTaskAndReply, except the destination + // for |task| is a worker thread and you can specify |task_is_slow| just + // like you can for PostTask above. + static bool PostTaskAndReply(const tracked_objects::Location& from_here, + const Closure& task, + const Closure& reply, + bool task_is_slow); + + // Return true if the current thread is one that this WorkerPool runs tasks + // on. (Note that if the Windows worker pool is used without going through + // this WorkerPool interface, RunsTasksOnCurrentThread would return false on + // those threads.) + static bool RunsTasksOnCurrentThread(); + + // Get a TaskRunner wrapper which posts to the WorkerPool using the given + // |task_is_slow| behavior. + static const scoped_refptr<TaskRunner>& GetTaskRunner(bool task_is_slow); +}; + +} // namespace base + +#endif // BASE_THREADING_WORKER_POOL_H_
diff --git a/src/base/threading/worker_pool_posix.cc b/src/base/threading/worker_pool_posix.cc new file mode 100644 index 0000000..2ad3925 --- /dev/null +++ b/src/base/threading/worker_pool_posix.cc
@@ -0,0 +1,204 @@ +// 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/threading/worker_pool_posix.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/debug/trace_event.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/stringprintf.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread_local.h" +#include "base/threading/worker_pool.h" +#include "base/tracked_objects.h" + +using tracked_objects::TrackedTime; + +namespace base { + +namespace { + +base::LazyInstance<ThreadLocalBoolean>::Leaky + g_worker_pool_running_on_this_thread = LAZY_INSTANCE_INITIALIZER; + +const int kIdleSecondsBeforeExit = 10 * 60; + +#ifdef ADDRESS_SANITIZER +const int kWorkerThreadStackSize = 256 * 1024; +#else +// A stack size of 64 KB is too small for the CERT_PKIXVerifyCert +// function of NSS because of NSS bug 439169. +const int kWorkerThreadStackSize = 128 * 1024; +#endif + +class WorkerPoolImpl { + public: + WorkerPoolImpl(); + ~WorkerPoolImpl(); + + void PostTask(const tracked_objects::Location& from_here, + const base::Closure& task, bool task_is_slow); + + private: + scoped_refptr<base::PosixDynamicThreadPool> pool_; +}; + +WorkerPoolImpl::WorkerPoolImpl() + : pool_(new base::PosixDynamicThreadPool("WorkerPool", + kIdleSecondsBeforeExit)) { +} + +WorkerPoolImpl::~WorkerPoolImpl() { + pool_->Terminate(); +} + +void WorkerPoolImpl::PostTask(const tracked_objects::Location& from_here, + const base::Closure& task, bool task_is_slow) { + pool_->PostTask(from_here, task); +} + +base::LazyInstance<WorkerPoolImpl> g_lazy_worker_pool = + LAZY_INSTANCE_INITIALIZER; + +class WorkerThread : public PlatformThread::Delegate { + public: + WorkerThread(const std::string& name_prefix, + base::PosixDynamicThreadPool* pool) + : name_prefix_(name_prefix), + pool_(pool) {} + + virtual void ThreadMain() OVERRIDE; + + private: + const std::string name_prefix_; + scoped_refptr<base::PosixDynamicThreadPool> pool_; + + DISALLOW_COPY_AND_ASSIGN(WorkerThread); +}; + +void WorkerThread::ThreadMain() { + g_worker_pool_running_on_this_thread.Get().Set(true); + const std::string name = base::StringPrintf( + "%s/%d", name_prefix_.c_str(), PlatformThread::CurrentId()); + // Note |name.c_str()| must remain valid for for the whole life of the thread. + PlatformThread::SetName(name.c_str()); + + for (;;) { + PendingTask pending_task = pool_->WaitForTask(); + if (pending_task.task.is_null()) + break; + TRACE_EVENT2("task", "WorkerThread::ThreadMain::Run", + "src_file", pending_task.posted_from.file_name(), + "src_func", pending_task.posted_from.function_name()); + + TrackedTime start_time = + tracked_objects::ThreadData::NowForStartOfRun(pending_task.birth_tally); + + pending_task.task.Run(); + + tracked_objects::ThreadData::TallyRunOnWorkerThreadIfTracking( + pending_task.birth_tally, TrackedTime(pending_task.time_posted), + start_time, tracked_objects::ThreadData::NowForEndOfRun()); + } + + // The WorkerThread is non-joinable, so it deletes itself. + delete this; +} + +} // namespace + +// static +bool WorkerPool::PostTask(const tracked_objects::Location& from_here, + const base::Closure& task, bool task_is_slow) { + g_lazy_worker_pool.Pointer()->PostTask(from_here, task, task_is_slow); + return true; +} + +// static +bool WorkerPool::RunsTasksOnCurrentThread() { + return g_worker_pool_running_on_this_thread.Get().Get(); +} + +PosixDynamicThreadPool::PosixDynamicThreadPool( + const std::string& name_prefix, + int idle_seconds_before_exit) + : name_prefix_(name_prefix), + idle_seconds_before_exit_(idle_seconds_before_exit), + pending_tasks_available_cv_(&lock_), + num_idle_threads_(0), + terminated_(false), + num_idle_threads_cv_(NULL) {} + +PosixDynamicThreadPool::~PosixDynamicThreadPool() { + while (!pending_tasks_.empty()) + pending_tasks_.pop(); +} + +void PosixDynamicThreadPool::Terminate() { + { + AutoLock locked(lock_); + DCHECK(!terminated_) << "Thread pool is already terminated."; + terminated_ = true; + } + pending_tasks_available_cv_.Broadcast(); +} + +void PosixDynamicThreadPool::PostTask( + const tracked_objects::Location& from_here, + const base::Closure& task) { + PendingTask pending_task(from_here, task); + AddTask(&pending_task); +} + +void PosixDynamicThreadPool::AddTask(PendingTask* pending_task) { + AutoLock locked(lock_); + DCHECK(!terminated_) << + "This thread pool is already terminated. Do not post new tasks."; + + pending_tasks_.push(*pending_task); + pending_task->task.Reset(); + + // We have enough worker threads. + if (static_cast<size_t>(num_idle_threads_) >= pending_tasks_.size()) { + pending_tasks_available_cv_.Signal(); + } else { + // The new PlatformThread will take ownership of the WorkerThread object, + // which will delete itself on exit. + WorkerThread* worker = + new WorkerThread(name_prefix_, this); + PlatformThread::CreateNonJoinable(kWorkerThreadStackSize, worker); + } +} + +PendingTask PosixDynamicThreadPool::WaitForTask() { + AutoLock locked(lock_); + + if (terminated_) + return PendingTask(FROM_HERE, base::Closure()); + + if (pending_tasks_.empty()) { // No work available, wait for work. + num_idle_threads_++; + if (num_idle_threads_cv_.get()) + num_idle_threads_cv_->Signal(); + pending_tasks_available_cv_.TimedWait( + TimeDelta::FromSeconds(idle_seconds_before_exit_)); + num_idle_threads_--; + if (num_idle_threads_cv_.get()) + num_idle_threads_cv_->Signal(); + if (pending_tasks_.empty()) { + // We waited for work, but there's still no work. Return NULL to signal + // the thread to terminate. + return PendingTask(FROM_HERE, base::Closure()); + } + } + + PendingTask pending_task = pending_tasks_.front(); + pending_tasks_.pop(); + return pending_task; +} + +} // namespace base
diff --git a/src/base/threading/worker_pool_posix.h b/src/base/threading/worker_pool_posix.h new file mode 100644 index 0000000..dd0ffb6 --- /dev/null +++ b/src/base/threading/worker_pool_posix.h
@@ -0,0 +1,98 @@ +// 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. +// +// The thread pool used in the POSIX implementation of WorkerPool dynamically +// adds threads as necessary to handle all tasks. It keeps old threads around +// for a period of time to allow them to be reused. After this waiting period, +// the threads exit. This thread pool uses non-joinable threads, therefore +// worker threads are not joined during process shutdown. This means that +// potentially long running tasks (such as DNS lookup) do not block process +// shutdown, but also means that process shutdown may "leak" objects. Note that +// although PosixDynamicThreadPool spawns the worker threads and manages the +// task queue, it does not own the worker threads. The worker threads ask the +// PosixDynamicThreadPool for work and eventually clean themselves up. The +// worker threads all maintain scoped_refptrs to the PosixDynamicThreadPool +// instance, which prevents PosixDynamicThreadPool from disappearing before all +// worker threads exit. The owner of PosixDynamicThreadPool should likewise +// maintain a scoped_refptr to the PosixDynamicThreadPool instance. +// +// NOTE: The classes defined in this file are only meant for use by the POSIX +// implementation of WorkerPool. No one else should be using these classes. +// These symbols are exported in a header purely for testing purposes. + +#ifndef BASE_THREADING_WORKER_POOL_POSIX_H_ +#define BASE_THREADING_WORKER_POOL_POSIX_H_ + +#include <queue> +#include <string> + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/location.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/pending_task.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/threading/platform_thread.h" +#include "base/tracked_objects.h" + +class Task; + +namespace base { + +class BASE_EXPORT PosixDynamicThreadPool + : public RefCountedThreadSafe<PosixDynamicThreadPool> { + public: + class PosixDynamicThreadPoolPeer; + + // All worker threads will share the same |name_prefix|. They will exit after + // |idle_seconds_before_exit|. + PosixDynamicThreadPool(const std::string& name_prefix, + int idle_seconds_before_exit); + + // Indicates that the thread pool is going away. Stops handing out tasks to + // worker threads. Wakes up all the idle threads to let them exit. + void Terminate(); + + // Adds |task| to the thread pool. + void PostTask(const tracked_objects::Location& from_here, + const Closure& task); + + // Worker thread method to wait for up to |idle_seconds_before_exit| for more + // work from the thread pool. Returns NULL if no work is available. + PendingTask WaitForTask(); + + private: + friend class RefCountedThreadSafe<PosixDynamicThreadPool>; + friend class PosixDynamicThreadPoolPeer; + + ~PosixDynamicThreadPool(); + + // Adds pending_task to the thread pool. This function will clear + // |pending_task->task|. + void AddTask(PendingTask* pending_task); + + const std::string name_prefix_; + const int idle_seconds_before_exit_; + + Lock lock_; // Protects all the variables below. + + // Signal()s worker threads to let them know more tasks are available. + // Also used for Broadcast()'ing to worker threads to let them know the pool + // is being deleted and they can exit. + ConditionVariable pending_tasks_available_cv_; + int num_idle_threads_; + TaskQueue pending_tasks_; + bool terminated_; + // Only used for tests to ensure correct thread ordering. It will always be + // NULL in non-test code. + scoped_ptr<ConditionVariable> num_idle_threads_cv_; + + DISALLOW_COPY_AND_ASSIGN(PosixDynamicThreadPool); +}; + +} // namespace base + +#endif // BASE_THREADING_WORKER_POOL_POSIX_H_
diff --git a/src/base/threading/worker_pool_posix_unittest.cc b/src/base/threading/worker_pool_posix_unittest.cc new file mode 100644 index 0000000..49f6570 --- /dev/null +++ b/src/base/threading/worker_pool_posix_unittest.cc
@@ -0,0 +1,254 @@ +// 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/threading/worker_pool_posix.h" + +#include <set> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/threading/platform_thread.h" +#include "base/synchronization/waitable_event.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +// Peer class to provide passthrough access to PosixDynamicThreadPool internals. +class PosixDynamicThreadPool::PosixDynamicThreadPoolPeer { + public: + explicit PosixDynamicThreadPoolPeer(PosixDynamicThreadPool* pool) + : pool_(pool) {} + + Lock* lock() { return &pool_->lock_; } + ConditionVariable* pending_tasks_available_cv() { + return &pool_->pending_tasks_available_cv_; + } + const std::queue<PendingTask>& pending_tasks() const { + return pool_->pending_tasks_; + } + int num_idle_threads() const { return pool_->num_idle_threads_; } + ConditionVariable* num_idle_threads_cv() { + return pool_->num_idle_threads_cv_.get(); + } + void set_num_idle_threads_cv(ConditionVariable* cv) { + pool_->num_idle_threads_cv_.reset(cv); + } + + private: + PosixDynamicThreadPool* pool_; + + DISALLOW_COPY_AND_ASSIGN(PosixDynamicThreadPoolPeer); +}; + +namespace { + +// IncrementingTask's main purpose is to increment a counter. It also updates a +// set of unique thread ids, and signals a ConditionVariable on completion. +// Note that since it does not block, there is no way to control the number of +// threads used if more than one IncrementingTask is consecutively posted to the +// thread pool, since the first one might finish executing before the subsequent +// PostTask() calls get invoked. +void IncrementingTask(Lock* counter_lock, + int* counter, + Lock* unique_threads_lock, + std::set<PlatformThreadId>* unique_threads) { + { + base::AutoLock locked(*unique_threads_lock); + unique_threads->insert(PlatformThread::CurrentId()); + } + base::AutoLock locked(*counter_lock); + (*counter)++; +} + +// BlockingIncrementingTask is a simple wrapper around IncrementingTask that +// allows for waiting at the start of Run() for a WaitableEvent to be signalled. +struct BlockingIncrementingTaskArgs { + Lock* counter_lock; + int* counter; + Lock* unique_threads_lock; + std::set<PlatformThreadId>* unique_threads; + Lock* num_waiting_to_start_lock; + int* num_waiting_to_start; + ConditionVariable* num_waiting_to_start_cv; + base::WaitableEvent* start; +}; + +void BlockingIncrementingTask(const BlockingIncrementingTaskArgs& args) { + { + base::AutoLock num_waiting_to_start_locked(*args.num_waiting_to_start_lock); + (*args.num_waiting_to_start)++; + } + args.num_waiting_to_start_cv->Signal(); + args.start->Wait(); + IncrementingTask(args.counter_lock, args.counter, args.unique_threads_lock, + args.unique_threads); +} + +class PosixDynamicThreadPoolTest : public testing::Test { + protected: + PosixDynamicThreadPoolTest() + : pool_(new base::PosixDynamicThreadPool("dynamic_pool", 60*60)), + peer_(pool_.get()), + counter_(0), + num_waiting_to_start_(0), + num_waiting_to_start_cv_(&num_waiting_to_start_lock_), + start_(true, false) {} + + virtual void SetUp() OVERRIDE { + peer_.set_num_idle_threads_cv(new ConditionVariable(peer_.lock())); + } + + virtual void TearDown() OVERRIDE { + // Wake up the idle threads so they can terminate. + if (pool_.get()) pool_->Terminate(); + } + + void WaitForTasksToStart(int num_tasks) { + base::AutoLock num_waiting_to_start_locked(num_waiting_to_start_lock_); + while (num_waiting_to_start_ < num_tasks) { + num_waiting_to_start_cv_.Wait(); + } + } + + void WaitForIdleThreads(int num_idle_threads) { + base::AutoLock pool_locked(*peer_.lock()); + while (peer_.num_idle_threads() < num_idle_threads) { + peer_.num_idle_threads_cv()->Wait(); + } + } + + base::Closure CreateNewIncrementingTaskCallback() { + return base::Bind(&IncrementingTask, &counter_lock_, &counter_, + &unique_threads_lock_, &unique_threads_); + } + + base::Closure CreateNewBlockingIncrementingTaskCallback() { + BlockingIncrementingTaskArgs args = { + &counter_lock_, &counter_, &unique_threads_lock_, &unique_threads_, + &num_waiting_to_start_lock_, &num_waiting_to_start_, + &num_waiting_to_start_cv_, &start_ + }; + return base::Bind(&BlockingIncrementingTask, args); + } + + scoped_refptr<base::PosixDynamicThreadPool> pool_; + base::PosixDynamicThreadPool::PosixDynamicThreadPoolPeer peer_; + Lock counter_lock_; + int counter_; + Lock unique_threads_lock_; + std::set<PlatformThreadId> unique_threads_; + Lock num_waiting_to_start_lock_; + int num_waiting_to_start_; + ConditionVariable num_waiting_to_start_cv_; + base::WaitableEvent start_; +}; + +} // namespace + +TEST_F(PosixDynamicThreadPoolTest, Basic) { + EXPECT_EQ(0, peer_.num_idle_threads()); + EXPECT_EQ(0U, unique_threads_.size()); + EXPECT_EQ(0U, peer_.pending_tasks().size()); + + // Add one task and wait for it to be completed. + pool_->PostTask(FROM_HERE, CreateNewIncrementingTaskCallback()); + + WaitForIdleThreads(1); + + 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_); +} + +TEST_F(PosixDynamicThreadPoolTest, ReuseIdle) { + // Add one task and wait for it to be completed. + pool_->PostTask(FROM_HERE, CreateNewIncrementingTaskCallback()); + + WaitForIdleThreads(1); + + // Add another 2 tasks. One should reuse the existing worker thread. + pool_->PostTask(FROM_HERE, CreateNewBlockingIncrementingTaskCallback()); + pool_->PostTask(FROM_HERE, CreateNewBlockingIncrementingTaskCallback()); + + WaitForTasksToStart(2); + start_.Signal(); + WaitForIdleThreads(2); + + EXPECT_EQ(2U, unique_threads_.size()); + EXPECT_EQ(2, peer_.num_idle_threads()); + EXPECT_EQ(3, counter_); +} + +TEST_F(PosixDynamicThreadPoolTest, TwoActiveTasks) { + // Add two blocking tasks. + pool_->PostTask(FROM_HERE, CreateNewBlockingIncrementingTaskCallback()); + pool_->PostTask(FROM_HERE, CreateNewBlockingIncrementingTaskCallback()); + + EXPECT_EQ(0, counter_) << "Blocking tasks should not have started yet."; + + WaitForTasksToStart(2); + start_.Signal(); + WaitForIdleThreads(2); + + EXPECT_EQ(2U, unique_threads_.size()); + EXPECT_EQ(2, peer_.num_idle_threads()) << "Existing threads are now idle."; + EXPECT_EQ(2, counter_); +} + +TEST_F(PosixDynamicThreadPoolTest, Complex) { + // Add two non blocking tasks and wait for them to finish. + pool_->PostTask(FROM_HERE, CreateNewIncrementingTaskCallback()); + + WaitForIdleThreads(1); + + // Add two blocking tasks, start them simultaneously, and wait for them to + // finish. + pool_->PostTask(FROM_HERE, CreateNewBlockingIncrementingTaskCallback()); + pool_->PostTask(FROM_HERE, CreateNewBlockingIncrementingTaskCallback()); + + WaitForTasksToStart(2); + start_.Signal(); + WaitForIdleThreads(2); + + EXPECT_EQ(3, counter_); + EXPECT_EQ(2, peer_.num_idle_threads()); + EXPECT_EQ(2U, unique_threads_.size()); + + // Wake up all idle threads so they can exit. + { + base::AutoLock locked(*peer_.lock()); + while (peer_.num_idle_threads() > 0) { + peer_.pending_tasks_available_cv()->Signal(); + peer_.num_idle_threads_cv()->Wait(); + } + } + + // Add another non blocking task. There are no threads to reuse. + pool_->PostTask(FROM_HERE, CreateNewIncrementingTaskCallback()); + WaitForIdleThreads(1); + + // The POSIX implementation of PlatformThread::CurrentId() uses pthread_self() + // which is not guaranteed to be unique after a thread joins. The OS X + // implemntation of pthread_self() returns the address of the pthread_t, which + // is merely a malloc()ed pointer stored in the first TLS slot. When a thread + // joins and that structure is freed, the block of memory can be put on the + // OS free list, meaning the same address could be reused in a subsequent + // allocation. This in fact happens when allocating in a loop as this test + // does. + // + // Because there are two concurrent threads, there's at least the guarantee + // of having two unique thread IDs in the set. But after those two threads are + // joined, the next-created thread can get a re-used ID if the allocation of + // the pthread_t structure is taken from the free list. Therefore, there can + // be either 2 or 3 unique thread IDs in the set at this stage in the test. + EXPECT_TRUE(unique_threads_.size() >= 2 && unique_threads_.size() <= 3) + << "unique_threads_.size() = " << unique_threads_.size(); + EXPECT_EQ(1, peer_.num_idle_threads()); + EXPECT_EQ(4, counter_); +} + +} // namespace base
diff --git a/src/base/threading/worker_pool_shell.cc b/src/base/threading/worker_pool_shell.cc new file mode 100644 index 0000000..303675c --- /dev/null +++ b/src/base/threading/worker_pool_shell.cc
@@ -0,0 +1,18 @@ +/* + * Copyright 2012 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "worker_pool_posix.cc" +
diff --git a/src/base/threading/worker_pool_starboard.cc b/src/base/threading/worker_pool_starboard.cc new file mode 100644 index 0000000..47a4e6c --- /dev/null +++ b/src/base/threading/worker_pool_starboard.cc
@@ -0,0 +1,17 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/threading/worker_pool_posix.cc"
diff --git a/src/base/threading/worker_pool_unittest.cc b/src/base/threading/worker_pool_unittest.cc new file mode 100644 index 0000000..107588b --- /dev/null +++ b/src/base/threading/worker_pool_unittest.cc
@@ -0,0 +1,111 @@ +// 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/threading/worker_pool.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/message_loop.h" +#include "base/test/test_timeouts.h" +#include "base/time.h" +#include "base/threading/thread_checker_impl.h" +#include "base/synchronization/waitable_event.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +typedef PlatformTest WorkerPoolTest; + +namespace base { + +namespace { + +class PostTaskAndReplyTester + : public base::RefCountedThreadSafe<PostTaskAndReplyTester> { + public: + PostTaskAndReplyTester() : finished_(false), test_event_(false, false) {} + + void RunTest() { + ASSERT_TRUE(thread_checker_.CalledOnValidThread()); + WorkerPool::PostTaskAndReply( + FROM_HERE, + base::Bind(&PostTaskAndReplyTester::OnWorkerThread, this), + base::Bind(&PostTaskAndReplyTester::OnOriginalThread, this), + false); + + test_event_.Wait(); + } + + void OnWorkerThread() { + // We're not on the original thread. + EXPECT_FALSE(thread_checker_.CalledOnValidThread()); + + test_event_.Signal(); + } + + void OnOriginalThread() { + EXPECT_TRUE(thread_checker_.CalledOnValidThread()); + finished_ = true; + } + + bool finished() const { + return finished_; + } + + private: + friend class base::RefCountedThreadSafe<PostTaskAndReplyTester>; + ~PostTaskAndReplyTester() {} + + bool finished_; + WaitableEvent test_event_; + + // The Impl version performs its checks even in release builds. + ThreadCheckerImpl thread_checker_; +}; + +} // namespace + +TEST_F(WorkerPoolTest, PostTask) { + WaitableEvent test_event(false, false); + WaitableEvent long_test_event(false, false); + + WorkerPool::PostTask(FROM_HERE, + base::Bind(&WaitableEvent::Signal, + base::Unretained(&test_event)), + false); + WorkerPool::PostTask(FROM_HERE, + base::Bind(&WaitableEvent::Signal, + base::Unretained(&long_test_event)), + true); + + test_event.Wait(); + long_test_event.Wait(); +} + +#if defined(OS_WIN) || defined(OS_LINUX) +// Flaky on Windows and Linux (http://crbug.com/130337) +#define MAYBE_PostTaskAndReply DISABLED_PostTaskAndReply +#else +#define MAYBE_PostTaskAndReply PostTaskAndReply +#endif + +TEST_F(WorkerPoolTest, MAYBE_PostTaskAndReply) { + MessageLoop message_loop; + scoped_refptr<PostTaskAndReplyTester> tester(new PostTaskAndReplyTester()); + tester->RunTest(); + + const TimeDelta kMaxDuration = TestTimeouts::tiny_timeout(); + TimeTicks start = TimeTicks::Now(); + while (!tester->finished() && TimeTicks::Now() - start < kMaxDuration) { +#if defined(OS_IOS) + // Ensure that the other thread has a chance to run even on a single-core + // device. + pthread_yield_np(); +#endif + MessageLoop::current()->RunUntilIdle(); + } + EXPECT_TRUE(tester->finished()); +} + +} // namespace base
diff --git a/src/base/threading/worker_pool_win.cc b/src/base/threading/worker_pool_win.cc new file mode 100644 index 0000000..1e8be26 --- /dev/null +++ b/src/base/threading/worker_pool_win.cc
@@ -0,0 +1,73 @@ +// 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/threading/worker_pool.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/debug/trace_event.h" +#include "base/logging.h" +#include "base/pending_task.h" +#include "base/threading/thread_local.h" +#include "base/tracked_objects.h" + +namespace base { + +namespace { + +base::LazyInstance<ThreadLocalBoolean>::Leaky + g_worker_pool_running_on_this_thread = LAZY_INSTANCE_INITIALIZER; + +DWORD CALLBACK WorkItemCallback(void* param) { + PendingTask* pending_task = static_cast<PendingTask*>(param); + TRACE_EVENT2("task", "WorkItemCallback::Run", + "src_file", pending_task->posted_from.file_name(), + "src_func", pending_task->posted_from.function_name()); + + tracked_objects::TrackedTime start_time = + tracked_objects::ThreadData::NowForStartOfRun(pending_task->birth_tally); + + g_worker_pool_running_on_this_thread.Get().Set(true); + pending_task->task.Run(); + g_worker_pool_running_on_this_thread.Get().Set(false); + + tracked_objects::ThreadData::TallyRunOnWorkerThreadIfTracking( + pending_task->birth_tally, + tracked_objects::TrackedTime(pending_task->time_posted), start_time, + tracked_objects::ThreadData::NowForEndOfRun()); + + delete pending_task; + return 0; +} + +// Takes ownership of |pending_task| +bool PostTaskInternal(PendingTask* pending_task, bool task_is_slow) { + ULONG flags = 0; + if (task_is_slow) + flags |= WT_EXECUTELONGFUNCTION; + + if (!QueueUserWorkItem(WorkItemCallback, pending_task, flags)) { + DLOG(ERROR) << "QueueUserWorkItem failed: " << GetLastError(); + delete pending_task; + return false; + } + + return true; +} + +} // namespace + +// static +bool WorkerPool::PostTask(const tracked_objects::Location& from_here, + const base::Closure& task, bool task_is_slow) { + PendingTask* pending_task = new PendingTask(from_here, task); + return PostTaskInternal(pending_task, task_is_slow); +} + +// static +bool WorkerPool::RunsTasksOnCurrentThread() { + return g_worker_pool_running_on_this_thread.Get().Get(); +} + +} // namespace base
diff --git a/src/base/time.cc b/src/base/time.cc new file mode 100644 index 0000000..6d20667 --- /dev/null +++ b/src/base/time.cc
@@ -0,0 +1,237 @@ +// 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/time.h" + +#include <math.h> +#if defined(OS_WIN) +#include <float.h> +#endif + +#include <limits> + +#include "base/sys_string_conversions.h" +#include "base/third_party/nspr/prtime.h" + +#include "base/logging.h" + +namespace base { + +namespace { +#if defined(OS_WIN) +inline bool isnan(double num) { return !!_isnan(num); } +#endif +} + +// TimeDelta ------------------------------------------------------------------ + +// static +TimeDelta TimeDelta::Max() { + return TimeDelta(std::numeric_limits<int64>::max()); +} + +int TimeDelta::InDays() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits<int>::max(); + } + return static_cast<int>(delta_ / Time::kMicrosecondsPerDay); +} + +int TimeDelta::InHours() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits<int>::max(); + } + return static_cast<int>(delta_ / Time::kMicrosecondsPerHour); +} + +int TimeDelta::InMinutes() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits<int>::max(); + } + return static_cast<int>(delta_ / Time::kMicrosecondsPerMinute); +} + +double TimeDelta::InSecondsF() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits<double>::infinity(); + } + return static_cast<double>(delta_) / Time::kMicrosecondsPerSecond; +} + +int64 TimeDelta::InSeconds() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits<int64>::max(); + } + return delta_ / Time::kMicrosecondsPerSecond; +} + +double TimeDelta::InMillisecondsF() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits<double>::infinity(); + } + return static_cast<double>(delta_) / Time::kMicrosecondsPerMillisecond; +} + +int64 TimeDelta::InMilliseconds() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits<int64>::max(); + } + return delta_ / Time::kMicrosecondsPerMillisecond; +} + +int64 TimeDelta::InMillisecondsRoundedUp() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits<int64>::max(); + } + return (delta_ + Time::kMicrosecondsPerMillisecond - 1) / + Time::kMicrosecondsPerMillisecond; +} + +int64 TimeDelta::InMicroseconds() const { + if (is_max()) { + // Preserve max to prevent overflow. + return std::numeric_limits<int64>::max(); + } + return delta_; +} + +// Time ----------------------------------------------------------------------- + +// static +Time Time::Max() { + return Time(std::numeric_limits<int64>::max()); +} + +// static +Time Time::FromTimeT(time_t tt) { + if (tt == 0) + return Time(); // Preserve 0 so we can tell it doesn't exist. + if (tt == std::numeric_limits<time_t>::max()) + return Max(); + return Time((tt * kMicrosecondsPerSecond) + kTimeTToMicrosecondsOffset); +} + +time_t Time::ToTimeT() const { + if (is_null()) + return 0; // Preserve 0 so we can tell it doesn't exist. + if (is_max()) { + // Preserve max without offset to prevent overflow. + return std::numeric_limits<time_t>::max(); + } + if (std::numeric_limits<int64>::max() - kTimeTToMicrosecondsOffset <= us_) { + DLOG(WARNING) << "Overflow when converting base::Time with internal " << + "value " << us_ << " to time_t."; + return std::numeric_limits<time_t>::max(); + } + return (us_ - kTimeTToMicrosecondsOffset) / kMicrosecondsPerSecond; +} + +// static +Time Time::FromDoubleT(double dt) { + if (dt == 0 || isnan(dt)) + return Time(); // Preserve 0 so we can tell it doesn't exist. + if (dt == std::numeric_limits<double>::max()) + return Max(); + return Time(static_cast<int64>((dt * + static_cast<double>(kMicrosecondsPerSecond)) + + kTimeTToMicrosecondsOffset)); +} + +double Time::ToDoubleT() const { + if (is_null()) + return 0; // Preserve 0 so we can tell it doesn't exist. + if (is_max()) { + // Preserve max without offset to prevent overflow. + return std::numeric_limits<double>::max(); + } + return (static_cast<double>(us_ - kTimeTToMicrosecondsOffset) / + static_cast<double>(kMicrosecondsPerSecond)); +} + +// static +Time Time::FromJsTime(double ms_since_epoch) { + // The epoch is a valid time, so this constructor doesn't interpret + // 0 as the null time. + if (ms_since_epoch == std::numeric_limits<double>::max()) + return Max(); + return Time(static_cast<int64>(ms_since_epoch * kMicrosecondsPerMillisecond) + + kTimeTToMicrosecondsOffset); +} + +double Time::ToJsTime() const { + if (is_null()) { + // Preserve 0 so the invalid result doesn't depend on the platform. + return 0; + } + if (is_max()) { + // Preserve max without offset to prevent overflow. + return std::numeric_limits<double>::max(); + } + return (static_cast<double>(us_ - kTimeTToMicrosecondsOffset) / + kMicrosecondsPerMillisecond); +} + +// static +Time Time::UnixEpoch() { + Time time; + time.us_ = kTimeTToMicrosecondsOffset; + return time; +} + +Time Time::LocalMidnight() const { + Exploded exploded; + LocalExplode(&exploded); + exploded.hour = 0; + exploded.minute = 0; + exploded.second = 0; + exploded.millisecond = 0; + return FromLocalExploded(exploded); +} + +// static +bool Time::FromStringInternal(const char* time_string, + bool is_local, + Time* parsed_time) { + DCHECK((time_string != NULL) && (parsed_time != NULL)); + + if (time_string[0] == '\0') + return false; + + PRTime result_time = 0; + PRStatus result = PR_ParseTimeString(time_string, + is_local ? PR_FALSE : PR_TRUE, + &result_time); + if (PR_SUCCESS != result) + return false; + + result_time += kTimeTToMicrosecondsOffset; + *parsed_time = Time(result_time); + return true; +} + +// Time::Exploded ------------------------------------------------------------- + +inline bool is_in_range(int value, int lo, int hi) { + return lo <= value && value <= hi; +} + +bool Time::Exploded::HasValidValues() const { + return is_in_range(month, 1, 12) && + is_in_range(day_of_week, 0, 6) && + is_in_range(day_of_month, 1, 31) && + is_in_range(hour, 0, 23) && + is_in_range(minute, 0, 59) && + is_in_range(second, 0, 60) && + is_in_range(millisecond, 0, 999); +} + +} // namespace base
diff --git a/src/base/time.h b/src/base/time.h new file mode 100644 index 0000000..38bc53a --- /dev/null +++ b/src/base/time.h
@@ -0,0 +1,704 @@ +// 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. + +// Time represents an absolute point in time, internally represented as +// microseconds (s/1,000,000) since the Windows epoch (1601-01-01 00:00:00 UTC) +// (See http://crbug.com/14734). System-dependent clock interface routines are +// defined in time_PLATFORM.cc. +// +// TimeDelta represents a duration of time, internally represented in +// microseconds. +// +// TimeTicks represents an abstract time that is most of the time incrementing +// for use in measuring time durations. It is internally represented in +// microseconds. It can not be converted to a human-readable time, but is +// guaranteed not to decrease (if the user changes the computer clock, +// Time::Now() may actually decrease or jump). But note that TimeTicks may +// "stand still", for example if the computer suspended. +// +// These classes are represented as only a 64-bit value, so they can be +// efficiently passed by value. + +#ifndef BASE_TIME_H_ +#define BASE_TIME_H_ + +#include <time.h> + +#include "base/atomicops.h" +#include "base/base_export.h" +#include "base/basictypes.h" + +#if defined(OS_MACOSX) +#include <CoreFoundation/CoreFoundation.h> +// Avoid Mac system header macro leak. +#undef TYPE_BOOL +#endif + +#if defined(OS_POSIX) +// For struct timeval. +#include <sys/time.h> +#endif + +#if defined(OS_STARBOARD) +#include "starboard/time.h" +#endif + +#if defined(OS_WIN) +// For FILETIME in FromFileTime, until it moves to a new converter class. +// See TODO(iyengar) below. +#include <windows.h> +#endif + +#include <limits> + +namespace base { + +class Time; +class TimeTicks; + +// TimeDelta ------------------------------------------------------------------ + +class BASE_EXPORT TimeDelta { + public: + TimeDelta() : delta_(0) { + } + + // Converts units of time to TimeDeltas. + static TimeDelta FromDays(int days); + static TimeDelta FromHours(int hours); + static TimeDelta FromMinutes(int minutes); + static TimeDelta FromSeconds(int64 secs); + static TimeDelta FromMilliseconds(int64 ms); + static TimeDelta FromSecondsD(double secs); + static TimeDelta FromMillisecondsD(double ms); + static TimeDelta FromMicroseconds(int64 us); +#if defined(OS_WIN) + static TimeDelta FromQPCValue(LONGLONG qpc_value); +#endif + + // Converts an integer value representing TimeDelta to a class. This is used + // when deserializing a |TimeDelta| structure, using a value known to be + // compatible. It is not provided as a constructor because the integer type + // may be unclear from the perspective of a caller. + static TimeDelta FromInternalValue(int64 delta) { + return TimeDelta(delta); + } + + // Returns the maximum time delta, which should be greater than any reasonable + // time delta we might compare it to. Adding or subtracting the maximum time + // delta to a time or another time delta has an undefined result. + static TimeDelta Max(); + + // Returns the internal numeric value of the TimeDelta object. Please don't + // use this and do arithmetic on it, as it is more error prone than using the + // provided operators. + // For serializing, use FromInternalValue to reconstitute. + int64 ToInternalValue() const { + return delta_; + } + + // Returns true if the time delta is the maximum time delta. + bool is_max() const { + return delta_ == std::numeric_limits<int64>::max(); + } + +#if defined(OS_STARBOARD) + SbTime ToSbTime() const; +#endif + +#if defined(OS_POSIX) + struct timespec ToTimeSpec() const; +#endif + + // Returns the time delta in some unit. The F versions return a floating + // point value, the "regular" versions return a rounded-down value. + // + // InMillisecondsRoundedUp() instead returns an integer that is rounded up + // to the next full millisecond. + int InDays() const; + int InHours() const; + int InMinutes() const; + double InSecondsF() const; + int64 InSeconds() const; + double InMillisecondsF() const; + int64 InMilliseconds() const; + int64 InMillisecondsRoundedUp() const; + int64 InMicroseconds() const; + + TimeDelta& operator=(TimeDelta other) { + delta_ = other.delta_; + return *this; + } + + // Computations with other deltas. + TimeDelta operator+(TimeDelta other) const { + return TimeDelta(delta_ + other.delta_); + } + TimeDelta operator-(TimeDelta other) const { + return TimeDelta(delta_ - other.delta_); + } + + TimeDelta& operator+=(TimeDelta other) { + delta_ += other.delta_; + return *this; + } + TimeDelta& operator-=(TimeDelta other) { + delta_ -= other.delta_; + return *this; + } + TimeDelta operator-() const { + return TimeDelta(-delta_); + } + + // Computations with ints, note that we only allow multiplicative operations + // with ints, and additive operations with other deltas. + TimeDelta operator*(int64 a) const { + return TimeDelta(delta_ * a); + } + TimeDelta operator/(int64 a) const { + return TimeDelta(delta_ / a); + } + TimeDelta& operator*=(int64 a) { + delta_ *= a; + return *this; + } + TimeDelta& operator/=(int64 a) { + delta_ /= a; + return *this; + } + int64 operator/(TimeDelta a) const { + return delta_ / a.delta_; + } + + // Defined below because it depends on the definition of the other classes. + Time operator+(Time t) const; + TimeTicks operator+(TimeTicks t) const; + + // Comparison operators. + bool operator==(TimeDelta other) const { + return delta_ == other.delta_; + } + bool operator!=(TimeDelta other) const { + return delta_ != other.delta_; + } + bool operator<(TimeDelta other) const { + return delta_ < other.delta_; + } + bool operator<=(TimeDelta other) const { + return delta_ <= other.delta_; + } + bool operator>(TimeDelta other) const { + return delta_ > other.delta_; + } + bool operator>=(TimeDelta other) const { + return delta_ >= other.delta_; + } + + private: + friend class Time; + friend class TimeTicks; + friend TimeDelta operator*(int64 a, TimeDelta td); + + // Constructs a delta given the duration in microseconds. This is private + // to avoid confusion by callers with an integer constructor. Use + // FromSeconds, FromMilliseconds, etc. instead. + explicit TimeDelta(int64 delta_us) : delta_(delta_us) { + } + + // Delta in microseconds. + int64 delta_; +}; + +inline TimeDelta operator*(int64 a, TimeDelta td) { + return TimeDelta(a * td.delta_); +} + +// Time ----------------------------------------------------------------------- + +// Represents a wall clock time. +class BASE_EXPORT Time { + public: + static const int64 kMillisecondsPerSecond = 1000; + static const int64 kMicrosecondsPerMillisecond = 1000; + static const int64 kMicrosecondsPerSecond = kMicrosecondsPerMillisecond * + kMillisecondsPerSecond; + static const int64 kMicrosecondsPerMinute = kMicrosecondsPerSecond * 60; + static const int64 kMicrosecondsPerHour = kMicrosecondsPerMinute * 60; + static const int64 kMicrosecondsPerDay = kMicrosecondsPerHour * 24; + static const int64 kMicrosecondsPerWeek = kMicrosecondsPerDay * 7; + static const int64 kNanosecondsPerMicrosecond = 1000; + static const int64 kNanosecondsPerSecond = kNanosecondsPerMicrosecond * + kMicrosecondsPerSecond; + +#if !defined(OS_WIN) && !defined(OS_STARBOARD) + // On Mac & Linux, this value is the delta from the Windows epoch of 1601 to + // the Posix delta of 1970. This is used for migrating between the old + // 1970-based epochs to the new 1601-based ones. It should be removed from + // this global header and put in the platform-specific ones when we remove the + // migration code. + static const int64 kWindowsEpochDeltaMicroseconds; +#endif + + // Represents an exploded time that can be formatted nicely. This is kind of + // like the Win32 SYSTEMTIME structure or the Unix "struct tm" with a few + // additions and changes to prevent errors. + struct BASE_EXPORT Exploded { + int year; // Four digit year "2007" + int month; // 1-based month (values 1 = January, etc.) + int day_of_week; // 0-based day of week (0 = Sunday, etc.) + int day_of_month; // 1-based day of month (1-31) + int hour; // Hour within the current day (0-23) + int minute; // Minute within the current hour (0-59) + int second; // Second within the current minute (0-59 plus leap + // seconds which may take it up to 60). + int millisecond; // Milliseconds within the current second (0-999) + + // A cursory test for whether the data members are within their + // respective ranges. A 'true' return value does not guarantee the + // Exploded value can be successfully converted to a Time value. + bool HasValidValues() const; + }; + + // Contains the NULL time. Use Time::Now() to get the current time. + Time() : us_(0) { + } + + // Returns true if the time object has not been initialized. + bool is_null() const { + return us_ == 0; + } + + // Returns true if the time object is the maximum time. + bool is_max() const { + return us_ == std::numeric_limits<int64>::max(); + } + + // Returns the time for epoch in Unix-like system (Jan 1, 1970). + static Time UnixEpoch(); + + // Returns the current time. Watch out, the system might adjust its clock + // in which case time will actually go backwards. We don't guarantee that + // times are increasing, or that two calls to Now() won't be the same. + static Time Now(); + + // Returns the maximum time, which should be greater than any reasonable time + // with which we might compare it. + static Time Max(); + + // Returns the current time. Same as Now() except that this function always + // uses system time so that there are no discrepancies between the returned + // time and system time even on virtual environments including our test bot. + // For timing sensitive unittests, this function should be used. + static Time NowFromSystemTime(); + + // Converts to/from time_t in UTC and a Time class. + // TODO(brettw) this should be removed once everybody starts using the |Time| + // class. + static Time FromTimeT(time_t tt); + time_t ToTimeT() const; + + // Converts time to/from a double which is the number of seconds since epoch + // (Jan 1, 1970). Webkit uses this format to represent time. + // Because WebKit initializes double time value to 0 to indicate "not + // initialized", we map it to empty Time object that also means "not + // initialized". + static Time FromDoubleT(double dt); + double ToDoubleT() const; + + // Converts to/from the Javascript convention for times, a number of + // milliseconds since the epoch: + // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/getTime. + static Time FromJsTime(double ms_since_epoch); + double ToJsTime() const; + +#if defined(OS_STARBOARD) + static Time FromSbTime(SbTime t); + SbTime ToSbTime() const; +#endif + +#if defined(OS_POSIX) + static Time FromTimeVal(struct timeval t); + struct timeval ToTimeVal() const; +#endif + +#if defined(OS_MACOSX) + static Time FromCFAbsoluteTime(CFAbsoluteTime t); + CFAbsoluteTime ToCFAbsoluteTime() const; +#endif + +#if defined(OS_WIN) + static Time FromFileTime(FILETIME ft); + FILETIME ToFileTime() const; + + // The minimum time of a low resolution timer. This is basically a windows + // constant of ~15.6ms. While it does vary on some older OS versions, we'll + // treat it as static across all windows versions. + static const int kMinLowResolutionThresholdMs = 16; + + // Enable or disable Windows high resolution timer. If the high resolution + // timer is not enabled, calls to ActivateHighResolutionTimer will fail. + // When disabling the high resolution timer, this function will not cause + // the high resolution timer to be deactivated, but will prevent future + // activations. + // Must be called from the main thread. + // For more details see comments in time_win.cc. + static void EnableHighResolutionTimer(bool enable); + + // Activates or deactivates the high resolution timer based on the |activate| + // flag. If the HighResolutionTimer is not Enabled (see + // EnableHighResolutionTimer), this function will return false. Otherwise + // returns true. Each successful activate call must be paired with a + // subsequent deactivate call. + // All callers to activate the high resolution timer must eventually call + // this function to deactivate the high resolution timer. + static bool ActivateHighResolutionTimer(bool activate); + + // Returns true if the high resolution timer is both enabled and activated. + // This is provided for testing only, and is not tracked in a thread-safe + // way. + static bool IsHighResolutionTimerInUse(); +#endif + + // Converts an exploded structure representing either the local time or UTC + // into a Time class. + static Time FromUTCExploded(const Exploded& exploded) { + return FromExploded(false, exploded); + } + static Time FromLocalExploded(const Exploded& exploded) { + return FromExploded(true, exploded); + } + + // Converts an integer value representing Time to a class. This is used + // when deserializing a |Time| structure, using a value known to be + // compatible. It is not provided as a constructor because the integer type + // may be unclear from the perspective of a caller. + static Time FromInternalValue(int64 us) { + return Time(us); + } + + // Converts a string representation of time to a Time object. + // An example of a time string which is converted is as below:- + // "Tue, 15 Nov 1994 12:45:26 GMT". If the timezone is not specified + // in the input string, FromString assumes local time and FromUTCString + // assumes UTC. A timezone that cannot be parsed (e.g. "UTC" which is not + // specified in RFC822) is treated as if the timezone is not specified. + // TODO(iyengar) Move the FromString/FromTimeT/ToTimeT/FromFileTime to + // a new time converter class. + static bool FromString(const char* time_string, Time* parsed_time) { + return FromStringInternal(time_string, true, parsed_time); + } + static bool FromUTCString(const char* time_string, Time* parsed_time) { + return FromStringInternal(time_string, false, parsed_time); + } + + // For serializing, use FromInternalValue to reconstitute. Please don't use + // this and do arithmetic on it, as it is more error prone than using the + // provided operators. + int64 ToInternalValue() const { + return us_; + } + + // Fills the given exploded structure with either the local time or UTC from + // this time structure (containing UTC). + void UTCExplode(Exploded* exploded) const { + return Explode(false, exploded); + } + void LocalExplode(Exploded* exploded) const { + return Explode(true, exploded); + } + + // Rounds this time down to the nearest day in local time. It will represent + // midnight on that day. + Time LocalMidnight() const; + + Time& operator=(Time other) { + us_ = other.us_; + return *this; + } + + // Compute the difference between two times. + TimeDelta operator-(Time other) const { + return TimeDelta(us_ - other.us_); + } + + // Modify by some time delta. + Time& operator+=(TimeDelta delta) { + us_ += delta.delta_; + return *this; + } + Time& operator-=(TimeDelta delta) { + us_ -= delta.delta_; + return *this; + } + + // Return a new time modified by some delta. + Time operator+(TimeDelta delta) const { + return Time(us_ + delta.delta_); + } + Time operator-(TimeDelta delta) const { + return Time(us_ - delta.delta_); + } + + // Comparison operators + bool operator==(Time other) const { + return us_ == other.us_; + } + bool operator!=(Time other) const { + return us_ != other.us_; + } + bool operator<(Time other) const { + return us_ < other.us_; + } + bool operator<=(Time other) const { + return us_ <= other.us_; + } + bool operator>(Time other) const { + return us_ > other.us_; + } + bool operator>=(Time other) const { + return us_ >= other.us_; + } + + private: + friend class TimeDelta; + + explicit Time(int64 us) : us_(us) { + } + + // Explodes the given time to either local time |is_local = true| or UTC + // |is_local = false|. + void Explode(bool is_local, Exploded* exploded) const; + + // Unexplodes a given time assuming the source is either local time + // |is_local = true| or UTC |is_local = false|. + static Time FromExploded(bool is_local, const Exploded& exploded); + + // Converts a string representation of time to a Time object. + // An example of a time string which is converted is as below:- + // "Tue, 15 Nov 1994 12:45:26 GMT". If the timezone is not specified + // in the input string, local time |is_local = true| or + // UTC |is_local = false| is assumed. A timezone that cannot be parsed + // (e.g. "UTC" which is not specified in RFC822) is treated as if the + // timezone is not specified. + static bool FromStringInternal(const char* time_string, + bool is_local, + Time* parsed_time); + + // The representation of Jan 1, 1970 UTC in microseconds since the + // platform-dependent epoch. + static const int64 kTimeTToMicrosecondsOffset; + +#if defined(OS_WIN) + // Indicates whether fast timers are usable right now. For instance, + // when using battery power, we might elect to prevent high speed timers + // which would draw more power. + static bool high_resolution_timer_enabled_; + // Count of activations on the high resolution timer. Only use in tests + // which are single threaded. + static int high_resolution_timer_activated_; +#endif + + // Time in microseconds in UTC. + int64 us_; +}; + +// Inline the TimeDelta factory methods, for fast TimeDelta construction. + +// static +inline TimeDelta TimeDelta::FromDays(int days) { + // Preserve max to prevent overflow. + if (days == std::numeric_limits<int>::max()) + return Max(); + return TimeDelta(days * Time::kMicrosecondsPerDay); +} + +// static +inline TimeDelta TimeDelta::FromHours(int hours) { + // Preserve max to prevent overflow. + if (hours == std::numeric_limits<int>::max()) + return Max(); + return TimeDelta(hours * Time::kMicrosecondsPerHour); +} + +// static +inline TimeDelta TimeDelta::FromMinutes(int minutes) { + // Preserve max to prevent overflow. + if (minutes == std::numeric_limits<int>::max()) + return Max(); + return TimeDelta(minutes * Time::kMicrosecondsPerMinute); +} + +// static +inline TimeDelta TimeDelta::FromSeconds(int64 secs) { + // Preserve max to prevent overflow. + if (secs == std::numeric_limits<int64>::max()) + return Max(); + return TimeDelta(secs * Time::kMicrosecondsPerSecond); +} + +// static +inline TimeDelta TimeDelta::FromMilliseconds(int64 ms) { + // Preserve max to prevent overflow. + if (ms == std::numeric_limits<int64>::max()) + return Max(); + return TimeDelta(ms * Time::kMicrosecondsPerMillisecond); +} + +// static +inline TimeDelta TimeDelta::FromSecondsD(double secs) { + // Preserve max to prevent overflow. + if (secs == std::numeric_limits<double>::infinity()) + return Max(); + return TimeDelta( + static_cast<int64>(secs * Time::kMicrosecondsPerSecond)); +} + +// static +inline TimeDelta TimeDelta::FromMillisecondsD(double ms) { + // Preserve max to prevent overflow. + if (ms == std::numeric_limits<double>::infinity()) + return Max(); + return TimeDelta( + static_cast<int64>(ms * Time::kMicrosecondsPerMillisecond)); +} + +// static +inline TimeDelta TimeDelta::FromMicroseconds(int64 us) { + // Preserve max to prevent overflow. + if (us == std::numeric_limits<int64>::max()) + return Max(); + return TimeDelta(us); +} + +inline Time TimeDelta::operator+(Time t) const { + return Time(t.us_ + delta_); +} + +// TimeTicks ------------------------------------------------------------------ + +class BASE_EXPORT TimeTicks { + public: + TimeTicks() : ticks_(0) { + } + + // Platform-dependent tick count representing "right now." + // The resolution of this clock is ~1-15ms. Resolution varies depending + // on hardware/operating system configuration. + static TimeTicks Now(); + + // Returns a platform-dependent high-resolution tick count. Implementation + // is hardware dependent and may or may not return sub-millisecond + // resolution. THIS CALL IS GENERALLY MUCH MORE EXPENSIVE THAN Now() AND + // SHOULD ONLY BE USED WHEN IT IS REALLY NEEDED. + static TimeTicks HighResNow(); + + // Returns the current system trace time or, if none is defined, the current + // high-res time (i.e. HighResNow()). On systems where a global trace clock + // is defined, timestamping TraceEvents's with this value guarantees + // synchronization between events collected inside chrome and events + // collected outside (e.g. kernel, X server). + static TimeTicks NowFromSystemTraceTime(); + +#if defined(OS_WIN) + // Get the absolute value of QPC time drift. For testing. + static int64 GetQPCDriftMicroseconds(); + + static TimeTicks FromQPCValue(LONGLONG qpc_value); + + // Returns true if the high resolution clock is working on this system. + // This is only for testing. + static bool IsHighResClockWorking(); +#endif + + // Returns true if this object has not been initialized. + bool is_null() const { + return ticks_ == 0; + } + + // Converts an integer value representing TimeTicks to a class. This is used + // when deserializing a |TimeTicks| structure, using a value known to be + // compatible. It is not provided as a constructor because the integer type + // may be unclear from the perspective of a caller. + static TimeTicks FromInternalValue(int64 ticks) { + return TimeTicks(ticks); + } + + // Returns the internal numeric value of the TimeTicks object. + // For serializing, use FromInternalValue to reconstitute. + int64 ToInternalValue() const { + return ticks_; + } + + TimeTicks& operator=(TimeTicks other) { + ticks_ = other.ticks_; + return *this; + } + + // Compute the difference between two times. + TimeDelta operator-(TimeTicks other) const { + return TimeDelta(ticks_ - other.ticks_); + } + + // Modify by some time delta. + TimeTicks& operator+=(TimeDelta delta) { + ticks_ += delta.delta_; + return *this; + } + TimeTicks& operator-=(TimeDelta delta) { + ticks_ -= delta.delta_; + return *this; + } + + // Return a new TimeTicks modified by some delta. + TimeTicks operator+(TimeDelta delta) const { + return TimeTicks(ticks_ + delta.delta_); + } + TimeTicks operator-(TimeDelta delta) const { + return TimeTicks(ticks_ - delta.delta_); + } + + // Comparison operators + bool operator==(TimeTicks other) const { + return ticks_ == other.ticks_; + } + bool operator!=(TimeTicks other) const { + return ticks_ != other.ticks_; + } + bool operator<(TimeTicks other) const { + return ticks_ < other.ticks_; + } + bool operator<=(TimeTicks other) const { + return ticks_ <= other.ticks_; + } + bool operator>(TimeTicks other) const { + return ticks_ > other.ticks_; + } + bool operator>=(TimeTicks other) const { + return ticks_ >= other.ticks_; + } + + protected: + friend class TimeDelta; + + // Please use Now() to create a new object. This is for internal use + // and testing. Ticks is in microseconds. + explicit TimeTicks(int64 ticks) : ticks_(ticks) { + } + + // Tick count in microseconds. + int64 ticks_; + +#if defined(OS_WIN) + typedef DWORD (*TickFunctionType)(void); + static TickFunctionType SetMockTickFunction(TickFunctionType ticker); +#endif +}; + +inline TimeTicks TimeDelta::operator+(TimeTicks t) const { + return TimeTicks(t.ticks_ + delta_); +} + +} // namespace base + +#endif // BASE_TIME_H_
diff --git a/src/base/time_mac.cc b/src/base/time_mac.cc new file mode 100644 index 0000000..883a35b --- /dev/null +++ b/src/base/time_mac.cc
@@ -0,0 +1,199 @@ +// 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/time.h" + +#include <CoreFoundation/CFDate.h> +#include <CoreFoundation/CFTimeZone.h> +#include <mach/mach_time.h> +#include <sys/sysctl.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/mac/scoped_cftyperef.h" + +namespace { + +uint64_t ComputeCurrentTicks() { +#if defined(OS_IOS) + // On iOS mach_absolute_time stops while the device is sleeping. Instead use + // now - KERN_BOOTTIME to get a time difference that is not impacted by clock + // changes. KERN_BOOTTIME will be updated by the system whenever the system + // clock change. + struct timeval boottime; + int mib[2] = {CTL_KERN, KERN_BOOTTIME}; + size_t size = sizeof(boottime); + int kr = sysctl(mib, arraysize(mib), &boottime, &size, NULL, 0); + DCHECK_EQ(KERN_SUCCESS, kr); + base::TimeDelta time_difference = base::Time::Now() - + (base::Time::FromTimeT(boottime.tv_sec) + + base::TimeDelta::FromMicroseconds(boottime.tv_usec)); + return time_difference.InMicroseconds(); +#else + uint64_t absolute_micro; + + static mach_timebase_info_data_t timebase_info; + if (timebase_info.denom == 0) { + // Zero-initialization of statics guarantees that denom will be 0 before + // calling mach_timebase_info. mach_timebase_info will never set denom to + // 0 as that would be invalid, so the zero-check can be used to determine + // whether mach_timebase_info has already been called. This is + // recommended by Apple's QA1398. + kern_return_t kr = mach_timebase_info(&timebase_info); + DCHECK_EQ(KERN_SUCCESS, kr); + } + + // mach_absolute_time is it when it comes to ticks on the Mac. Other calls + // with less precision (such as TickCount) just call through to + // mach_absolute_time. + + // timebase_info converts absolute time tick units into nanoseconds. Convert + // to microseconds up front to stave off overflows. + absolute_micro = + mach_absolute_time() / base::Time::kNanosecondsPerMicrosecond * + timebase_info.numer / timebase_info.denom; + + // Don't bother with the rollover handling that the Windows version does. + // With numer and denom = 1 (the expected case), the 64-bit absolute time + // reported in nanoseconds is enough to last nearly 585 years. + return absolute_micro; +#endif // defined(OS_IOS) +} + +} // namespace + +namespace base { + +// The Time routines in this file use Mach and CoreFoundation APIs, since the +// POSIX definition of time_t in Mac OS X wraps around after 2038--and +// there are already cookie expiration dates, etc., past that time out in +// the field. Using CFDate prevents that problem, and using mach_absolute_time +// for TimeTicks gives us nice high-resolution interval timing. + +// Time ----------------------------------------------------------------------- + +// Core Foundation uses a double second count since 2001-01-01 00:00:00 UTC. +// The UNIX epoch is 1970-01-01 00:00:00 UTC. +// Windows uses a Gregorian epoch of 1601. We need to match this internally +// so that our time representations match across all platforms. See bug 14734. +// irb(main):010:0> Time.at(0).getutc() +// => Thu Jan 01 00:00:00 UTC 1970 +// 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 = + kWindowsEpochDeltaSeconds * Time::kMicrosecondsPerSecond; + +// Some functions in time.cc use time_t directly, so we provide an offset +// to convert from time_t (Unix epoch) and internal (Windows epoch). +// static +const int64 Time::kTimeTToMicrosecondsOffset = kWindowsEpochDeltaMicroseconds; + +// static +Time Time::Now() { + return FromCFAbsoluteTime(CFAbsoluteTimeGetCurrent()); +} + +// static +Time Time::FromCFAbsoluteTime(CFAbsoluteTime t) { + if (t == 0) + return Time(); // Consider 0 as a null Time. + if (t == std::numeric_limits<CFAbsoluteTime>::max()) + return Max(); + return Time(static_cast<int64>( + (t + kCFAbsoluteTimeIntervalSince1970) * kMicrosecondsPerSecond) + + kWindowsEpochDeltaMicroseconds); +} + +CFAbsoluteTime Time::ToCFAbsoluteTime() const { + if (is_null()) + return 0; // Consider 0 as a null Time. + if (is_max()) + return std::numeric_limits<CFAbsoluteTime>::max(); + return (static_cast<CFAbsoluteTime>(us_ - kWindowsEpochDeltaMicroseconds) / + kMicrosecondsPerSecond) - kCFAbsoluteTimeIntervalSince1970; +} + +// static +Time Time::NowFromSystemTime() { + // Just use Now() because Now() returns the system time. + return Now(); +} + +// static +Time Time::FromExploded(bool is_local, const Exploded& exploded) { + CFGregorianDate date; + date.second = exploded.second + + exploded.millisecond / static_cast<double>(kMillisecondsPerSecond); + date.minute = exploded.minute; + date.hour = exploded.hour; + date.day = exploded.day_of_month; + date.month = exploded.month; + date.year = exploded.year; + + base::mac::ScopedCFTypeRef<CFTimeZoneRef> + time_zone(is_local ? CFTimeZoneCopySystem() : NULL); + CFAbsoluteTime seconds = CFGregorianDateGetAbsoluteTime(date, time_zone) + + kCFAbsoluteTimeIntervalSince1970; + return Time(static_cast<int64>(seconds * kMicrosecondsPerSecond) + + kWindowsEpochDeltaMicroseconds); +} + +void Time::Explode(bool is_local, Exploded* exploded) const { + // Avoid rounding issues, by only putting the integral number of seconds + // (rounded towards -infinity) into a |CFAbsoluteTime| (which is a |double|). + int64 microsecond = us_ % kMicrosecondsPerSecond; + if (microsecond < 0) + microsecond += kMicrosecondsPerSecond; + CFAbsoluteTime seconds = ((us_ - microsecond) / kMicrosecondsPerSecond) - + kWindowsEpochDeltaSeconds - + kCFAbsoluteTimeIntervalSince1970; + + base::mac::ScopedCFTypeRef<CFTimeZoneRef> + time_zone(is_local ? CFTimeZoneCopySystem() : NULL); + CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(seconds, time_zone); + // 1 = Monday, ..., 7 = Sunday. + int cf_day_of_week = CFAbsoluteTimeGetDayOfWeek(seconds, time_zone); + + exploded->year = date.year; + exploded->month = date.month; + exploded->day_of_week = cf_day_of_week % 7; + exploded->day_of_month = date.day; + exploded->hour = date.hour; + exploded->minute = date.minute; + // Make sure seconds are rounded down towards -infinity. + exploded->second = floor(date.second); + // Calculate milliseconds ourselves, since we rounded the |seconds|, making + // sure to round towards -infinity. + exploded->millisecond = + (microsecond >= 0) ? microsecond / kMicrosecondsPerMillisecond : + (microsecond - kMicrosecondsPerMillisecond + 1) / + kMicrosecondsPerMillisecond; +} + +// TimeTicks ------------------------------------------------------------------ + +// static +TimeTicks TimeTicks::Now() { + return TimeTicks(ComputeCurrentTicks()); +} + +// static +TimeTicks TimeTicks::HighResNow() { + return Now(); +} + +// static +TimeTicks TimeTicks::NowFromSystemTraceTime() { + return HighResNow(); +} + +} // namespace base
diff --git a/src/base/time_posix.cc b/src/base/time_posix.cc new file mode 100644 index 0000000..80b1c4e --- /dev/null +++ b/src/base/time_posix.cc
@@ -0,0 +1,337 @@ +// 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/time.h" + +#include <sys/time.h> +#include <time.h> +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) +#include <time64.h> +#endif +#include <unistd.h> + +#include <limits> + +#include "base/basictypes.h" +#include "base/logging.h" + +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) +#include "base/os_compat_android.h" +#elif defined(OS_NACL) +#include "base/os_compat_nacl.h" +#endif + +namespace { + +// 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 +#if defined(OS_ANDROID) || defined(__LB_ANDROID__) +typedef time64_t SysTime; + +SysTime SysTimeFromTimeStruct(struct tm* timestruct, bool is_local) { + if (is_local) + return mktime64(timestruct); + else + return timegm64(timestruct); +} + +void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) { + if (is_local) + localtime64_r(&t, timestruct); + else + gmtime64_r(&t, timestruct); +} + +#else // OS_ANDROID +typedef time_t SysTime; + +SysTime SysTimeFromTimeStruct(struct tm* timestruct, bool is_local) { + if (is_local) + return mktime(timestruct); + else + return timegm(timestruct); +} + +void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) { + if (is_local) + localtime_r(&t, timestruct); + else + gmtime_r(&t, timestruct); +} +#endif // OS_ANDROID + +} // namespace + +namespace base { + +#if defined(OS_ANDROID) +#define _POSIX_MONOTONIC_CLOCK 1 +#endif + +struct timespec TimeDelta::ToTimeSpec() const { + int64 microseconds = InMicroseconds(); + time_t seconds = 0; + if (microseconds >= Time::kMicrosecondsPerSecond) { + seconds = InSeconds(); + microseconds -= seconds * Time::kMicrosecondsPerSecond; + } + struct timespec result = + {seconds, + static_cast<long>(microseconds * Time::kNanosecondsPerMicrosecond)}; + return result; +} + +#if !defined(OS_MACOSX) +// The Time routines in this file use standard POSIX routines, or almost- +// standard routines in the case of timegm. We need to use a Mach-specific +// function for TimeTicks::Now() on Mac OS X. + +// Time ----------------------------------------------------------------------- + +// Windows uses a Gregorian epoch of 1601. We need to match this internally +// so that our time representations match across all platforms. See bug 14734. +// irb(main):010:0> Time.at(0).getutc() +// => Thu Jan 01 00:00:00 UTC 1970 +// 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 = + kWindowsEpochDeltaSeconds * Time::kMicrosecondsPerSecond; + +// Some functions in time.cc use time_t directly, so we provide an offset +// to convert from time_t (Unix epoch) and internal (Windows epoch). +// static +const int64 Time::kTimeTToMicrosecondsOffset = kWindowsEpochDeltaMicroseconds; + +// static +Time Time::Now() { + struct timeval tv; + struct timezone tz = { 0, 0 }; // UTC + if (gettimeofday(&tv, &tz) != 0) { + DCHECK(0) << "Could not determine time of day"; + LOG_ERRNO(ERROR) << "Call to gettimeofday failed."; + // Return null instead of uninitialized |tv| value, which contains random + // garbage data. This may result in the crash seen in crbug.com/147570. + return Time(); + } + // Combine seconds and microseconds in a 64-bit field containing microseconds + // since the epoch. That's enough for nearly 600 centuries. Adjust from + // Unix (1970) to Windows (1601) epoch. + return Time((tv.tv_sec * kMicrosecondsPerSecond + tv.tv_usec) + + kWindowsEpochDeltaMicroseconds); +} + +// static +Time Time::NowFromSystemTime() { + // Just use Now() because Now() returns the system time. + return Now(); +} + +void Time::Explode(bool is_local, Exploded* exploded) const { + // Time stores times with microsecond resolution, but Exploded only carries + // millisecond resolution, so begin by being lossy. Adjust from Windows + // epoch (1601) to Unix epoch (1970); + int64 microseconds = us_ - kWindowsEpochDeltaMicroseconds; + // The following values are all rounded towards -infinity. + int64 milliseconds; // Milliseconds since epoch. + SysTime seconds; // Seconds since epoch. + int millisecond; // Exploded millisecond value (0-999). + if (microseconds >= 0) { + // Rounding towards -infinity <=> rounding towards 0, in this case. + milliseconds = microseconds / kMicrosecondsPerMillisecond; + seconds = milliseconds / kMillisecondsPerSecond; + millisecond = milliseconds % kMillisecondsPerSecond; + } else { + // Round these *down* (towards -infinity). + milliseconds = (microseconds - kMicrosecondsPerMillisecond + 1) / + kMicrosecondsPerMillisecond; + seconds = (milliseconds - kMillisecondsPerSecond + 1) / + kMillisecondsPerSecond; + // Make this nonnegative (and between 0 and 999 inclusive). + millisecond = milliseconds % kMillisecondsPerSecond; + if (millisecond < 0) + millisecond += kMillisecondsPerSecond; + } + + struct tm timestruct; + SysTimeToTimeStruct(seconds, ×truct, is_local); + + exploded->year = timestruct.tm_year + 1900; + exploded->month = timestruct.tm_mon + 1; + exploded->day_of_week = timestruct.tm_wday; + exploded->day_of_month = timestruct.tm_mday; + exploded->hour = timestruct.tm_hour; + exploded->minute = timestruct.tm_min; + exploded->second = timestruct.tm_sec; + exploded->millisecond = millisecond; +} + +// static +Time Time::FromExploded(bool is_local, const Exploded& exploded) { + struct tm timestruct; + timestruct.tm_sec = exploded.second; + timestruct.tm_min = exploded.minute; + timestruct.tm_hour = exploded.hour; + timestruct.tm_mday = exploded.day_of_month; + timestruct.tm_mon = exploded.month - 1; + timestruct.tm_year = exploded.year - 1900; + timestruct.tm_wday = exploded.day_of_week; // mktime/timegm ignore this + timestruct.tm_yday = 0; // mktime/timegm ignore this + timestruct.tm_isdst = -1; // attempt to figure it out +#if !defined(OS_NACL) && !defined(OS_SOLARIS) && !defined(__LB_WIIU__) && \ + !defined(__LB_PS4__) + timestruct.tm_gmtoff = 0; // not a POSIX field, so mktime/timegm ignore + timestruct.tm_zone = NULL; // not a POSIX field, so mktime/timegm ignore +#endif + + SysTime seconds = SysTimeFromTimeStruct(×truct, is_local); + + int64 milliseconds; + // Handle overflow. Clamping the range to what mktime and timegm might + // return is the best that can be done here. It's not ideal, but it's better + // than failing here or ignoring the overflow case and treating each time + // overflow as one second prior to the epoch. + if (seconds == -1 && + (exploded.year < 1969 || exploded.year > 1970)) { + // If exploded.year is 1969 or 1970, take -1 as correct, with the + // time indicating 1 second prior to the epoch. (1970 is allowed to handle + // time zone and DST offsets.) Otherwise, return the most future or past + // time representable. Assumes the time_t epoch is 1970-01-01 00:00:00 UTC. + // + // The minimum and maximum representible times that mktime and timegm could + // return are used here instead of values outside that range to allow for + // proper round-tripping between exploded and counter-type time + // representations in the presence of possible truncation to time_t by + // division and use with other functions that accept time_t. + // + // When representing the most distant time in the future, add in an extra + // 999ms to avoid the time being less than any other possible value that + // this function can return. + + // numeric_limits<int32_t>::min() and max() represent times well outside + // the range of results we expect from mktime(). + + if (exploded.year < 1969) { + milliseconds = std::numeric_limits<int32_t>::min() * + kMillisecondsPerSecond; + } else { + milliseconds = std::numeric_limits<int32_t>::max() * + kMillisecondsPerSecond; + milliseconds += (kMillisecondsPerSecond - 1); + } + } else { + milliseconds = seconds * kMillisecondsPerSecond + exploded.millisecond; + } + + // Adjust from Unix (1970) to Windows (1601) epoch. + return Time((milliseconds * kMicrosecondsPerMillisecond) + + kWindowsEpochDeltaMicroseconds); +} + +// TimeTicks ------------------------------------------------------------------ +// FreeBSD 6 has CLOCK_MONOLITHIC but defines _POSIX_MONOTONIC_CLOCK to -1. +#if (defined(OS_POSIX) && \ + defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0) || \ + defined(OS_BSD) || defined(OS_ANDROID) + +// static +TimeTicks TimeTicks::Now() { + uint64_t absolute_micro; + + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { + NOTREACHED() << "clock_gettime(CLOCK_MONOTONIC) failed."; + return TimeTicks(); + } + + absolute_micro = + (static_cast<int64>(ts.tv_sec) * Time::kMicrosecondsPerSecond) + + (static_cast<int64>(ts.tv_nsec) / Time::kNanosecondsPerMicrosecond); + + return TimeTicks(absolute_micro); +} +#elif defined(__LB_WIIU__) + // Defined in platform-specific code +#else // _POSIX_MONOTONIC_CLOCK +#error No usable tick clock function on this platform. +#endif // _POSIX_MONOTONIC_CLOCK + +// static +TimeTicks TimeTicks::HighResNow() { + return Now(); +} + +#if defined(OS_CHROMEOS) +// Force definition of the system trace clock; it is a chromeos-only api +// at the moment and surfacing it in the right place requires mucking +// with glibc et al. +#define CLOCK_SYSTEM_TRACE 11 + +// static +TimeTicks TimeTicks::NowFromSystemTraceTime() { + uint64_t absolute_micro; + + struct timespec ts; + if (clock_gettime(CLOCK_SYSTEM_TRACE, &ts) != 0) { + // NB: fall-back for a chrome os build running on linux + return HighResNow(); + } + + absolute_micro = + (static_cast<int64>(ts.tv_sec) * Time::kMicrosecondsPerSecond) + + (static_cast<int64>(ts.tv_nsec) / Time::kNanosecondsPerMicrosecond); + + return TimeTicks(absolute_micro); +} + +#else // !defined(OS_CHROMEOS) + +// static +TimeTicks TimeTicks::NowFromSystemTraceTime() { + return HighResNow(); +} + +#endif // defined(OS_CHROMEOS) + +#endif // !OS_MACOSX + +// static +Time Time::FromTimeVal(struct timeval t) { + DCHECK_LT(t.tv_usec, static_cast<int>(Time::kMicrosecondsPerSecond)); + DCHECK_GE(t.tv_usec, 0); + if (t.tv_usec == 0 && t.tv_sec == 0) + return Time(); + if (t.tv_usec == static_cast<suseconds_t>(Time::kMicrosecondsPerSecond) - 1 && + t.tv_sec == std::numeric_limits<time_t>::max()) + return Max(); + return Time( + (static_cast<int64>(t.tv_sec) * Time::kMicrosecondsPerSecond) + + t.tv_usec + + kTimeTToMicrosecondsOffset); +} + +struct timeval Time::ToTimeVal() const { + struct timeval result; + if (is_null()) { + result.tv_sec = 0; + result.tv_usec = 0; + return result; + } + if (is_max()) { + result.tv_sec = std::numeric_limits<time_t>::max(); + result.tv_usec = static_cast<suseconds_t>(Time::kMicrosecondsPerSecond) - 1; + return result; + } + int64 us = us_ - kTimeTToMicrosecondsOffset; + result.tv_sec = us / Time::kMicrosecondsPerSecond; + result.tv_usec = us % Time::kMicrosecondsPerSecond; + return result; +} + +} // namespace base
diff --git a/src/base/time_starboard.cc b/src/base/time_starboard.cc new file mode 100644 index 0000000..2e7785e --- /dev/null +++ b/src/base/time_starboard.cc
@@ -0,0 +1,104 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/time.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "starboard/client_porting/eztime/eztime.h" +#include "starboard/time.h" + +namespace base { + +namespace { +EzTimeZone GetTz(bool is_local) { + return is_local ? kEzTimeZoneLocal : kEzTimeZoneUTC; +} +} // namespace + +SbTime TimeDelta::ToSbTime() const { + return InMicroseconds(); +} + +// static +const int64 Time::kTimeTToMicrosecondsOffset = -kSbTimeToPosixDelta; + +// static +Time Time::Now() { + return Time(SbTimeGetNow()); +} + +// static +Time Time::NowFromSystemTime() { + return Now(); +} + +void Time::Explode(bool is_local, Exploded *exploded) const { + EzTimeValue value = EzTimeValueFromSbTime(ToSbTime()); + EzTimeExploded ez_exploded; + int millisecond; + bool result = EzTimeValueExplode(&value, GetTz(is_local), &ez_exploded, + &millisecond); + DCHECK(result); + + exploded->year = ez_exploded.tm_year + 1900; + exploded->month = ez_exploded.tm_mon + 1; + exploded->day_of_week = ez_exploded.tm_wday; + exploded->day_of_month = ez_exploded.tm_mday; + exploded->hour = ez_exploded.tm_hour; + exploded->minute = ez_exploded.tm_min; + exploded->second = ez_exploded.tm_sec; + exploded->millisecond = millisecond; +} + +// static +Time Time::FromExploded(bool is_local, const Exploded &exploded) { + EzTimeExploded ez_exploded; + ez_exploded.tm_sec = exploded.second; + ez_exploded.tm_min = exploded.minute; + ez_exploded.tm_hour = exploded.hour; + ez_exploded.tm_mday = exploded.day_of_month; + ez_exploded.tm_mon = exploded.month - 1; + ez_exploded.tm_year = exploded.year - 1900; + ez_exploded.tm_isdst = -1; + EzTimeValue value = EzTimeValueImplode(&ez_exploded, exploded.millisecond, + GetTz(is_local)); + return Time::FromSbTime(EzTimeValueToSbTime(&value)); +} + +// static +Time Time::FromSbTime(SbTime t) { + return Time(t); +} + +SbTime Time::ToSbTime() const { + return us_; +} + +// static +TimeTicks TimeTicks::Now() { + return TimeTicks(SbTimeGetMonotonicNow()); +} + +// static +TimeTicks TimeTicks::HighResNow() { + return Now(); +} + +// static +TimeTicks TimeTicks::NowFromSystemTraceTime() { + return HighResNow(); +} + +} // namespace base
diff --git a/src/base/time_unittest.cc b/src/base/time_unittest.cc new file mode 100644 index 0000000..f1c0d54 --- /dev/null +++ b/src/base/time_unittest.cc
@@ -0,0 +1,778 @@ +// 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/time.h" + +#if !defined(OS_STARBOARD) +#include <time.h> +#endif + +#include <string> + +#include "base/compiler_specific.h" +#include "base/test/time_helpers.h" +#include "base/threading/platform_thread.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::Time; +using base::TimeDelta; +using base::TimeTicks; + +// Specialized test fixture allowing time strings without timezones to be +// tested by comparing them to a known time in the local zone. +// See also pr_time_unittests.cc +class TimeTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { +#if defined(OS_STARBOARD) + // Since we don't have access to mktime, let's use time_helpers to do the + // same thing in a portable way. + comparison_time_local_ = base::test::time_helpers::TestDateToTime( + base::test::time_helpers::kTimeZoneLocal); + comparison_time_pdt_ = base::test::time_helpers::TestDateToTime( + base::test::time_helpers::kTimeZonePacific); +#else // defined(OS_STARBOARD) + // Use mktime to get a time_t, and turn it into a PRTime by converting + // seconds to microseconds. Use 15th Oct 2007 12:45:00 local. This + // must be a time guaranteed to be outside of a DST fallback hour in + // any timezone. + struct tm local_comparison_tm = { + 0, // second + 45, // minute + 12, // hour + 15, // day of month + 10 - 1, // month + 2007 - 1900, // year + 0, // day of week (ignored, output only) + 0, // day of year (ignored, output only) + -1 // DST in effect, -1 tells mktime to figure it out + }; + + time_t converted_time = mktime(&local_comparison_tm); + ASSERT_GT(converted_time, 0); + comparison_time_local_ = Time::FromTimeT(converted_time); + // time_t representation of 15th Oct 2007 12:45:00 PDT + comparison_time_pdt_ = Time::FromTimeT(1192477500); +#endif // defined(OS_STARBOARD) + } + + Time comparison_time_local_; + Time comparison_time_pdt_; +}; + +#if !defined(OS_STARBOARD) +// Test conversions to/from time_t and exploding/unexploding. +TEST_F(TimeTest, TimeT) { + // C library time and exploded time. + time_t now_t_1 = time(NULL); + struct tm tms; +#if defined(OS_WIN) + localtime_s(&tms, &now_t_1); +#elif defined(OS_POSIX) + localtime_r(&now_t_1, &tms); +#endif + + // Convert to ours. + Time our_time_1 = Time::FromTimeT(now_t_1); + Time::Exploded exploded; + our_time_1.LocalExplode(&exploded); + + // This will test both our exploding and our time_t -> Time conversion. + EXPECT_EQ(tms.tm_year + 1900, exploded.year); + EXPECT_EQ(tms.tm_mon + 1, exploded.month); + EXPECT_EQ(tms.tm_mday, exploded.day_of_month); + EXPECT_EQ(tms.tm_hour, exploded.hour); + EXPECT_EQ(tms.tm_min, exploded.minute); + EXPECT_EQ(tms.tm_sec, exploded.second); + + // Convert exploded back to the time struct. + Time our_time_2 = Time::FromLocalExploded(exploded); + EXPECT_TRUE(our_time_1 == our_time_2); + + time_t now_t_2 = our_time_2.ToTimeT(); + EXPECT_EQ(now_t_1, now_t_2); + + EXPECT_EQ(10, Time().FromTimeT(10).ToTimeT()); + EXPECT_EQ(10.0, Time().FromTimeT(10).ToDoubleT()); + + // Conversions of 0 should stay 0. + EXPECT_EQ(0, Time().ToTimeT()); + EXPECT_EQ(0, Time::FromTimeT(0).ToInternalValue()); +} +#endif // !defined(OS_STARBOARD) + +// Test conversions to/from javascript time. +TEST_F(TimeTest, JsTime) { + Time epoch = Time::FromJsTime(0.0); + EXPECT_EQ(epoch, Time::UnixEpoch()); + Time t = Time::FromJsTime(700000.3); + EXPECT_EQ(700.0003, t.ToDoubleT()); + t = Time::FromDoubleT(800.73); + EXPECT_EQ(800730.0, t.ToJsTime()); +} + +#if defined(OS_POSIX) && !defined(__LB_SHELL__) +// FromTimeVal is only used on Macs, and ToTimeVal is only used in conjunction +// with TouchPlatformFile(), which is not well supported in lb_shell. + +TEST_F(TimeTest, FromTimeVal) { + Time now = Time::Now(); + Time also_now = Time::FromTimeVal(now.ToTimeVal()); + EXPECT_EQ(now, also_now); +} +#endif // OS_POSIX + +TEST_F(TimeTest, FromExplodedWithMilliseconds) { + // Some platform implementations of FromExploded are liable to drop + // milliseconds if we aren't careful. + Time now = Time::NowFromSystemTime(); + Time::Exploded exploded1 = {0}; + now.UTCExplode(&exploded1); + exploded1.millisecond = 500; + Time time = Time::FromUTCExploded(exploded1); + Time::Exploded exploded2 = {0}; + time.UTCExplode(&exploded2); + EXPECT_EQ(exploded1.millisecond, exploded2.millisecond); +} + +TEST_F(TimeTest, ZeroIsSymmetric) { + Time zero_time(Time::FromTimeT(0)); + EXPECT_EQ(0, zero_time.ToTimeT()); + + EXPECT_EQ(0.0, zero_time.ToDoubleT()); +} + +TEST_F(TimeTest, LocalExplode) { + Time a = Time::Now(); + Time::Exploded exploded; + a.LocalExplode(&exploded); + + Time b = Time::FromLocalExploded(exploded); + + // The exploded structure doesn't have microseconds, and on Mac & Linux, the + // internal OS conversion uses seconds, which will cause truncation. So we + // can only make sure that the delta is within one second. + EXPECT_TRUE((a - b) < TimeDelta::FromSeconds(1)); +} + +TEST_F(TimeTest, UTCExplode) { + Time a = Time::Now(); + Time::Exploded exploded; + a.UTCExplode(&exploded); + + Time b = Time::FromUTCExploded(exploded); + EXPECT_TRUE((a - b) < TimeDelta::FromSeconds(1)); +} + +TEST_F(TimeTest, LocalMidnight) { + Time::Exploded exploded; + Time::Now().LocalMidnight().LocalExplode(&exploded); + EXPECT_EQ(0, exploded.hour); + EXPECT_EQ(0, exploded.minute); + EXPECT_EQ(0, exploded.second); + EXPECT_EQ(0, exploded.millisecond); +} + +#if defined(OS_STARBOARD) +TEST_F(TimeTest, ParseTimeTest1) { + Time now = Time::Now(); + + Time parsed_time; + std::string formatted = base::test::time_helpers::TimeFormatUTC(now); + EXPECT_TRUE(Time::FromUTCString(formatted.c_str(), &parsed_time)); + EXPECT_GE(1, (now - parsed_time).InSecondsF()); + EXPECT_GE(1, (parsed_time - now).InSecondsF()); + + formatted = base::test::time_helpers::TimeFormatLocal(now); + EXPECT_TRUE(Time::FromString(formatted.c_str(), &parsed_time)); + EXPECT_GE(1, (now - parsed_time).InSecondsF()); + EXPECT_GE(1, (parsed_time - now).InSecondsF()); +} +#else // !defined(OS_STARBOARD) +TEST_F(TimeTest, ParseTimeTest1) { + time_t current_time = 0; + time(¤t_time); + + const int BUFFER_SIZE = 64; + struct tm local_time = {0}; + char time_buf[BUFFER_SIZE] = {0}; +#if defined(OS_WIN) + localtime_s(&local_time, ¤t_time); + asctime_s(time_buf, arraysize(time_buf), &local_time); +#elif defined(__STDC_LIB_EXT1__) + localtime_s(¤t_time, &local_time); + asctime_s(time_buf, arraysize(time_buf), &local_time); +#elif defined(OS_POSIX) + localtime_r(¤t_time, &local_time); + asctime_r(&local_time, time_buf); +#endif + + Time parsed_time; + EXPECT_TRUE(Time::FromString(time_buf, &parsed_time)); + EXPECT_EQ(current_time, parsed_time.ToTimeT()); +} +#endif // !defined(OS_STARBOARD) + +TEST_F(TimeTest, DayOfWeekSunday) { + Time time; + EXPECT_TRUE(Time::FromString("Sun, 06 May 2012 12:00:00 GMT", &time)); + Time::Exploded exploded; + time.UTCExplode(&exploded); + EXPECT_EQ(0, exploded.day_of_week); +} + +TEST_F(TimeTest, DayOfWeekWednesday) { + Time time; + EXPECT_TRUE(Time::FromString("Wed, 09 May 2012 12:00:00 GMT", &time)); + Time::Exploded exploded; + time.UTCExplode(&exploded); + EXPECT_EQ(3, exploded.day_of_week); +} + +TEST_F(TimeTest, DayOfWeekSaturday) { + Time time; + EXPECT_TRUE(Time::FromString("Sat, 12 May 2012 12:00:00 GMT", &time)); + Time::Exploded exploded; + time.UTCExplode(&exploded); + EXPECT_EQ(6, exploded.day_of_week); +} + +TEST_F(TimeTest, ParseTimeTest2) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("Mon, 15 Oct 2007 19:45:00 GMT", &parsed_time)); + EXPECT_EQ(comparison_time_pdt_, parsed_time); +} + +TEST_F(TimeTest, ParseTimeTest3) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("15 Oct 07 12:45:00", &parsed_time)); + EXPECT_EQ(comparison_time_local_, parsed_time); +} + +TEST_F(TimeTest, ParseTimeTest4) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("15 Oct 07 19:45 GMT", &parsed_time)); + EXPECT_EQ(comparison_time_pdt_, parsed_time); +} + +TEST_F(TimeTest, ParseTimeTest5) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("Mon Oct 15 12:45 PDT 2007", &parsed_time)); + EXPECT_EQ(comparison_time_pdt_, parsed_time); +} + +TEST_F(TimeTest, ParseTimeTest6) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("Monday, Oct 15, 2007 12:45 PM", &parsed_time)); + EXPECT_EQ(comparison_time_local_, parsed_time); +} + +TEST_F(TimeTest, ParseTimeTest7) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("10/15/07 12:45:00 PM", &parsed_time)); + EXPECT_EQ(comparison_time_local_, parsed_time); +} + +TEST_F(TimeTest, ParseTimeTest8) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("15-OCT-2007 12:45pm", &parsed_time)); + EXPECT_EQ(comparison_time_local_, parsed_time); +} + +TEST_F(TimeTest, ParseTimeTest9) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("16 Oct 2007 4:45-JST (Tuesday)", &parsed_time)); + EXPECT_EQ(comparison_time_pdt_, parsed_time); +} + +TEST_F(TimeTest, ParseTimeTest10) { + Time parsed_time; + EXPECT_TRUE(Time::FromString("15/10/07 12:45", &parsed_time)); + EXPECT_EQ(parsed_time, comparison_time_local_); +} + +// Test some of edge cases around epoch, etc. +TEST_F(TimeTest, ParseTimeTestEpoch0) { + Time parsed_time; + + // time_t == epoch == 0 + EXPECT_TRUE(Time::FromString("Thu Jan 01 01:00:00 +0100 1970", + &parsed_time)); + EXPECT_EQ(0, parsed_time.ToTimeT()); + EXPECT_TRUE(Time::FromString("Thu Jan 01 00:00:00 GMT 1970", + &parsed_time)); + EXPECT_EQ(0, parsed_time.ToTimeT()); +} + +TEST_F(TimeTest, ParseTimeTestEpoch1) { + Time parsed_time; + + // time_t == 1 second after epoch == 1 + EXPECT_TRUE(Time::FromString("Thu Jan 01 01:00:01 +0100 1970", + &parsed_time)); + EXPECT_EQ(1, parsed_time.ToTimeT()); + EXPECT_TRUE(Time::FromString("Thu Jan 01 00:00:01 GMT 1970", + &parsed_time)); + EXPECT_EQ(1, parsed_time.ToTimeT()); +} + +TEST_F(TimeTest, ParseTimeTestEpoch2) { + Time parsed_time; + + // time_t == 2 seconds after epoch == 2 + EXPECT_TRUE(Time::FromString("Thu Jan 01 01:00:02 +0100 1970", + &parsed_time)); + EXPECT_EQ(2, parsed_time.ToTimeT()); + EXPECT_TRUE(Time::FromString("Thu Jan 01 00:00:02 GMT 1970", + &parsed_time)); + EXPECT_EQ(2, parsed_time.ToTimeT()); +} + +TEST_F(TimeTest, ParseTimeTestEpochNeg1) { + Time parsed_time; + + // time_t == 1 second before epoch == -1 + EXPECT_TRUE(Time::FromString("Thu Jan 01 00:59:59 +0100 1970", + &parsed_time)); + EXPECT_EQ(-1, parsed_time.ToTimeT()); + EXPECT_TRUE(Time::FromString("Wed Dec 31 23:59:59 GMT 1969", + &parsed_time)); + EXPECT_EQ(-1, parsed_time.ToTimeT()); +} + +// If time_t is 32 bits, a date after year 2038 will overflow time_t and +// cause timegm() to return -1. The parsed time should not be 1 second +// before epoch. +TEST_F(TimeTest, ParseTimeTestEpochNotNeg1) { + Time parsed_time; + + EXPECT_TRUE(Time::FromString("Wed Dec 31 23:59:59 GMT 2100", + &parsed_time)); + EXPECT_NE(-1, parsed_time.ToTimeT()); +} + +TEST_F(TimeTest, ParseTimeTestEpochNeg2) { + Time parsed_time; + + // time_t == 2 seconds before epoch == -2 + EXPECT_TRUE(Time::FromString("Thu Jan 01 00:59:58 +0100 1970", + &parsed_time)); + EXPECT_EQ(-2, parsed_time.ToTimeT()); + EXPECT_TRUE(Time::FromString("Wed Dec 31 23:59:58 GMT 1969", + &parsed_time)); + EXPECT_EQ(-2, parsed_time.ToTimeT()); +} + +TEST_F(TimeTest, ParseTimeTestEpoch1960) { + Time parsed_time; + + // time_t before Epoch, in 1960 + EXPECT_TRUE(Time::FromString("Wed Jun 29 19:40:01 +0100 1960", + &parsed_time)); + EXPECT_EQ(-299999999, parsed_time.ToTimeT()); + EXPECT_TRUE(Time::FromString("Wed Jun 29 18:40:01 GMT 1960", + &parsed_time)); + EXPECT_EQ(-299999999, parsed_time.ToTimeT()); + EXPECT_TRUE(Time::FromString("Wed Jun 29 17:40:01 GMT 1960", + &parsed_time)); + EXPECT_EQ(-300003599, parsed_time.ToTimeT()); +} + +TEST_F(TimeTest, ParseTimeTestEmpty) { + Time parsed_time; + EXPECT_FALSE(Time::FromString("", &parsed_time)); +} + +TEST_F(TimeTest, ParseTimeTestInvalidString) { + Time parsed_time; + EXPECT_FALSE(Time::FromString("Monday morning 2000", &parsed_time)); +} + +TEST_F(TimeTest, ExplodeBeforeUnixEpoch) { + static const int kUnixEpochYear = 1970; // In case this changes (ha!). + Time t; + Time::Exploded exploded; + + t = Time::UnixEpoch() - TimeDelta::FromMicroseconds(1); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1969-12-31 23:59:59 999 milliseconds (and 999 microseconds). + EXPECT_EQ(kUnixEpochYear - 1, exploded.year); + EXPECT_EQ(12, exploded.month); + EXPECT_EQ(31, exploded.day_of_month); + EXPECT_EQ(23, exploded.hour); + EXPECT_EQ(59, exploded.minute); + EXPECT_EQ(59, exploded.second); + EXPECT_EQ(999, exploded.millisecond); + + t = Time::UnixEpoch() - TimeDelta::FromMicroseconds(1000); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1969-12-31 23:59:59 999 milliseconds. + EXPECT_EQ(kUnixEpochYear - 1, exploded.year); + EXPECT_EQ(12, exploded.month); + EXPECT_EQ(31, exploded.day_of_month); + EXPECT_EQ(23, exploded.hour); + EXPECT_EQ(59, exploded.minute); + EXPECT_EQ(59, exploded.second); + EXPECT_EQ(999, exploded.millisecond); + + t = Time::UnixEpoch() - TimeDelta::FromMicroseconds(1001); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1969-12-31 23:59:59 998 milliseconds (and 999 microseconds). + EXPECT_EQ(kUnixEpochYear - 1, exploded.year); + EXPECT_EQ(12, exploded.month); + EXPECT_EQ(31, exploded.day_of_month); + EXPECT_EQ(23, exploded.hour); + EXPECT_EQ(59, exploded.minute); + EXPECT_EQ(59, exploded.second); + EXPECT_EQ(998, exploded.millisecond); + + t = Time::UnixEpoch() - TimeDelta::FromMilliseconds(1000); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1969-12-31 23:59:59. + EXPECT_EQ(kUnixEpochYear - 1, exploded.year); + EXPECT_EQ(12, exploded.month); + EXPECT_EQ(31, exploded.day_of_month); + EXPECT_EQ(23, exploded.hour); + EXPECT_EQ(59, exploded.minute); + EXPECT_EQ(59, exploded.second); + EXPECT_EQ(0, exploded.millisecond); + + t = Time::UnixEpoch() - TimeDelta::FromMilliseconds(1001); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1969-12-31 23:59:58 999 milliseconds. + EXPECT_EQ(kUnixEpochYear - 1, exploded.year); + EXPECT_EQ(12, exploded.month); + EXPECT_EQ(31, exploded.day_of_month); + EXPECT_EQ(23, exploded.hour); + EXPECT_EQ(59, exploded.minute); + EXPECT_EQ(58, exploded.second); + EXPECT_EQ(999, exploded.millisecond); + + // Make sure we still handle at/after Unix epoch correctly. + t = Time::UnixEpoch(); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1970-12-31 00:00:00 0 milliseconds. + EXPECT_EQ(kUnixEpochYear, exploded.year); + EXPECT_EQ(1, exploded.month); + EXPECT_EQ(1, exploded.day_of_month); + EXPECT_EQ(0, exploded.hour); + EXPECT_EQ(0, exploded.minute); + EXPECT_EQ(0, exploded.second); + EXPECT_EQ(0, exploded.millisecond); + + t = Time::UnixEpoch() + TimeDelta::FromMicroseconds(1); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1970-01-01 00:00:00 0 milliseconds (and 1 microsecond). + EXPECT_EQ(kUnixEpochYear, exploded.year); + EXPECT_EQ(1, exploded.month); + EXPECT_EQ(1, exploded.day_of_month); + EXPECT_EQ(0, exploded.hour); + EXPECT_EQ(0, exploded.minute); + EXPECT_EQ(0, exploded.second); + EXPECT_EQ(0, exploded.millisecond); + + t = Time::UnixEpoch() + TimeDelta::FromMicroseconds(1000); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1970-01-01 00:00:00 1 millisecond. + EXPECT_EQ(kUnixEpochYear, exploded.year); + EXPECT_EQ(1, exploded.month); + EXPECT_EQ(1, exploded.day_of_month); + EXPECT_EQ(0, exploded.hour); + EXPECT_EQ(0, exploded.minute); + EXPECT_EQ(0, exploded.second); + EXPECT_EQ(1, exploded.millisecond); + + t = Time::UnixEpoch() + TimeDelta::FromMilliseconds(1000); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1970-01-01 00:00:01. + EXPECT_EQ(kUnixEpochYear, exploded.year); + EXPECT_EQ(1, exploded.month); + EXPECT_EQ(1, exploded.day_of_month); + EXPECT_EQ(0, exploded.hour); + EXPECT_EQ(0, exploded.minute); + EXPECT_EQ(1, exploded.second); + EXPECT_EQ(0, exploded.millisecond); + + t = Time::UnixEpoch() + TimeDelta::FromMilliseconds(1001); + t.UTCExplode(&exploded); + EXPECT_TRUE(exploded.HasValidValues()); + // Should be 1970-01-01 00:00:01 1 millisecond. + EXPECT_EQ(kUnixEpochYear, exploded.year); + EXPECT_EQ(1, exploded.month); + EXPECT_EQ(1, exploded.day_of_month); + EXPECT_EQ(0, exploded.hour); + EXPECT_EQ(0, exploded.minute); + EXPECT_EQ(1, exploded.second); + EXPECT_EQ(1, exploded.millisecond); +} + +TEST_F(TimeTest, TimeDeltaMax) { + TimeDelta max = TimeDelta::Max(); + EXPECT_TRUE(max.is_max()); + EXPECT_EQ(max, TimeDelta::Max()); + EXPECT_GT(max, TimeDelta::FromDays(100 * 365)); + EXPECT_GT(max, TimeDelta()); +} + +TEST_F(TimeTest, TimeDeltaMaxConversions) { + TimeDelta t = TimeDelta::Max(); + EXPECT_EQ(std::numeric_limits<int64>::max(), t.ToInternalValue()); + + EXPECT_EQ(std::numeric_limits<int>::max(), t.InDays()); + EXPECT_EQ(std::numeric_limits<int>::max(), t.InHours()); + EXPECT_EQ(std::numeric_limits<int>::max(), t.InMinutes()); + EXPECT_EQ(std::numeric_limits<double>::infinity(), t.InSecondsF()); + EXPECT_EQ(std::numeric_limits<int64>::max(), t.InSeconds()); + EXPECT_EQ(std::numeric_limits<double>::infinity(), t.InMillisecondsF()); + EXPECT_EQ(std::numeric_limits<int64>::max(), t.InMilliseconds()); + EXPECT_EQ(std::numeric_limits<int64>::max(), t.InMillisecondsRoundedUp()); + + t = TimeDelta::FromDays(std::numeric_limits<int>::max()); + EXPECT_TRUE(t.is_max()); + + t = TimeDelta::FromHours(std::numeric_limits<int>::max()); + EXPECT_TRUE(t.is_max()); + + t = TimeDelta::FromMinutes(std::numeric_limits<int>::max()); + EXPECT_TRUE(t.is_max()); + + t = TimeDelta::FromSeconds(std::numeric_limits<int64>::max()); + EXPECT_TRUE(t.is_max()); + + t = TimeDelta::FromMilliseconds(std::numeric_limits<int64>::max()); + EXPECT_TRUE(t.is_max()); + + t = TimeDelta::FromSecondsD(std::numeric_limits<double>::infinity()); + EXPECT_TRUE(t.is_max()); + + t = TimeDelta::FromMillisecondsD(std::numeric_limits<double>::infinity()); + EXPECT_TRUE(t.is_max()); + + t = TimeDelta::FromMicroseconds(std::numeric_limits<int64>::max()); + EXPECT_TRUE(t.is_max()); +} + +TEST_F(TimeTest, Max) { + Time max = Time::Max(); + EXPECT_TRUE(max.is_max()); + EXPECT_EQ(max, Time::Max()); + EXPECT_GT(max, Time::Now()); + EXPECT_GT(max, Time()); +} + +TEST_F(TimeTest, MaxConversions) { + Time t = Time::Max(); + EXPECT_EQ(std::numeric_limits<int64>::max(), t.ToInternalValue()); + + t = Time::FromDoubleT(std::numeric_limits<double>::max()); + EXPECT_TRUE(t.is_max()); + EXPECT_EQ(std::numeric_limits<double>::max(), t.ToDoubleT()); + + t = Time::FromJsTime(std::numeric_limits<double>::max()); + EXPECT_TRUE(t.is_max()); + EXPECT_EQ(std::numeric_limits<double>::max(), t.ToJsTime()); + + t = Time::FromTimeT(std::numeric_limits<time_t>::max()); + EXPECT_TRUE(t.is_max()); + EXPECT_EQ(std::numeric_limits<time_t>::max(), t.ToTimeT()); + +#if defined(OS_POSIX) && !defined(__LB_SHELL__) + struct timeval tval; + tval.tv_sec = std::numeric_limits<time_t>::max(); + tval.tv_usec = static_cast<suseconds_t>(Time::kMicrosecondsPerSecond) - 1; + t = Time::FromTimeVal(tval); + EXPECT_TRUE(t.is_max()); + tval = t.ToTimeVal(); + EXPECT_EQ(std::numeric_limits<time_t>::max(), tval.tv_sec); + EXPECT_EQ(static_cast<suseconds_t>(Time::kMicrosecondsPerSecond) - 1, + tval.tv_usec); +#endif + +#if defined(OS_MACOSX) + t = Time::FromCFAbsoluteTime(std::numeric_limits<CFAbsoluteTime>::max()); + EXPECT_TRUE(t.is_max()); + EXPECT_EQ(std::numeric_limits<CFAbsoluteTime>::max(), t.ToCFAbsoluteTime()); +#endif + +#if defined(OS_WIN) + FILETIME ftime; + ftime.dwHighDateTime = std::numeric_limits<DWORD>::max(); + ftime.dwLowDateTime = std::numeric_limits<DWORD>::max(); + t = Time::FromFileTime(ftime); + EXPECT_TRUE(t.is_max()); + ftime = t.ToFileTime(); + EXPECT_EQ(std::numeric_limits<DWORD>::max(), ftime.dwHighDateTime); + EXPECT_EQ(std::numeric_limits<DWORD>::max(), ftime.dwLowDateTime); +#endif +} + +#if defined(OS_MACOSX) +TEST_F(TimeTest, TimeTOverflow) { + Time t = Time::FromInternalValue(std::numeric_limits<int64>::max() - 1); + EXPECT_FALSE(t.is_max()); + EXPECT_EQ(std::numeric_limits<time_t>::max(), t.ToTimeT()); +} +#endif + +TEST(TimeTicks, Deltas) { + for (int index = 0; index < 50; index++) { + TimeTicks ticks_start = TimeTicks::Now(); + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); + TimeTicks ticks_stop = TimeTicks::Now(); + TimeDelta delta = ticks_stop - ticks_start; + // Note: Although we asked for a 10ms sleep, if the + // time clock has a finer granularity than the Sleep() + // clock, it is quite possible to wakeup early. Here + // is how that works: + // Time(ms timer) Time(us timer) + // 5 5010 + // 6 6010 + // 7 7010 + // 8 8010 + // 9 9000 + // Elapsed 4ms 3990us + // + // Unfortunately, our InMilliseconds() function truncates + // rather than rounds. We should consider fixing this + // so that our averages come out better. + EXPECT_GE(delta.InMilliseconds(), 9); + EXPECT_GE(delta.InMicroseconds(), 9000); + EXPECT_EQ(delta.InSeconds(), 0); + } +} + +static void HighResClockTest(TimeTicks (*GetTicks)()) { +#if defined(OS_WIN) + // HighResNow doesn't work on some systems. Since the product still works + // even if it doesn't work, it makes this entire test questionable. + if (!TimeTicks::IsHighResClockWorking()) + return; +#endif + + // Why do we loop here? + // We're trying to measure that intervals increment in a VERY small amount + // of time -- less than 15ms. Unfortunately, if we happen to have a + // context switch in the middle of our test, the context switch could easily + // exceed our limit. So, we iterate on this several times. As long as we're + // able to detect the fine-granularity timers at least once, then the test + // has succeeded. + + const int kTargetGranularityUs = 15000; // 15ms + + bool success = false; + int retries = 100; // Arbitrary. + TimeDelta delta; + while (!success && retries--) { + TimeTicks ticks_start = GetTicks(); + // Loop until we can detect that the clock has changed. Non-HighRes timers + // will increment in chunks, e.g. 15ms. By spinning until we see a clock + // change, we detect the minimum time between measurements. + do { + delta = GetTicks() - ticks_start; + } while (delta.InMilliseconds() == 0); + + if (delta.InMicroseconds() <= kTargetGranularityUs) + success = true; + } + + // In high resolution mode, we expect to see the clock increment + // in intervals less than 15ms. + EXPECT_TRUE(success); +} + +TEST(TimeTicks, HighResNow) { + HighResClockTest(&TimeTicks::HighResNow); +} + +TEST(TimeTicks, NowFromSystemTraceTime) { + // Re-use HighResNow test for now since clock properties are identical. + HighResClockTest(&TimeTicks::NowFromSystemTraceTime); +} + +TEST(TimeDelta, FromAndIn) { + EXPECT_TRUE(TimeDelta::FromDays(2) == TimeDelta::FromHours(48)); + EXPECT_TRUE(TimeDelta::FromHours(3) == TimeDelta::FromMinutes(180)); + EXPECT_TRUE(TimeDelta::FromMinutes(2) == TimeDelta::FromSeconds(120)); + EXPECT_TRUE(TimeDelta::FromSeconds(2) == TimeDelta::FromMilliseconds(2000)); + EXPECT_TRUE(TimeDelta::FromMilliseconds(2) == + TimeDelta::FromMicroseconds(2000)); + EXPECT_TRUE(TimeDelta::FromSecondsD(2.3) == + TimeDelta::FromMilliseconds(2300)); + EXPECT_TRUE(TimeDelta::FromMillisecondsD(2.5) == + TimeDelta::FromMicroseconds(2500)); + EXPECT_EQ(13, TimeDelta::FromDays(13).InDays()); + EXPECT_EQ(13, TimeDelta::FromHours(13).InHours()); + EXPECT_EQ(13, TimeDelta::FromMinutes(13).InMinutes()); + EXPECT_EQ(13, TimeDelta::FromSeconds(13).InSeconds()); + EXPECT_EQ(13.0, TimeDelta::FromSeconds(13).InSecondsF()); + EXPECT_EQ(13, TimeDelta::FromMilliseconds(13).InMilliseconds()); + EXPECT_EQ(13.0, TimeDelta::FromMilliseconds(13).InMillisecondsF()); + EXPECT_EQ(13, TimeDelta::FromSecondsD(13.1).InSeconds()); + EXPECT_EQ(13.1, TimeDelta::FromSecondsD(13.1).InSecondsF()); + EXPECT_EQ(13, TimeDelta::FromMillisecondsD(13.3).InMilliseconds()); + EXPECT_EQ(13.3, TimeDelta::FromMillisecondsD(13.3).InMillisecondsF()); + EXPECT_EQ(13, TimeDelta::FromMicroseconds(13).InMicroseconds()); +} + +#if defined(OS_POSIX) && !defined(__LB_SHELL__) +// ToTimeSpec() only ever called for Macs (see process_util_posix.cc) + +TEST(TimeDelta, TimeSpecConversion) { + struct timespec result = TimeDelta::FromSeconds(0).ToTimeSpec(); + EXPECT_EQ(result.tv_sec, 0); + EXPECT_EQ(result.tv_nsec, 0); + + result = TimeDelta::FromSeconds(1).ToTimeSpec(); + EXPECT_EQ(result.tv_sec, 1); + EXPECT_EQ(result.tv_nsec, 0); + + result = TimeDelta::FromMicroseconds(1).ToTimeSpec(); + EXPECT_EQ(result.tv_sec, 0); + EXPECT_EQ(result.tv_nsec, 1000); + + result = TimeDelta::FromMicroseconds( + Time::kMicrosecondsPerSecond + 1).ToTimeSpec(); + EXPECT_EQ(result.tv_sec, 1); + EXPECT_EQ(result.tv_nsec, 1000); +} +#endif // OS_POSIX + +// Our internal time format is serialized in things like databases, so it's +// important that it's consistent across all our platforms. We use the 1601 +// Windows epoch as the internal format across all platforms. +TEST(TimeDelta, WindowsEpoch) { + Time::Exploded exploded; + exploded.year = 1970; + exploded.month = 1; + exploded.day_of_week = 0; // Should be unusued. + exploded.day_of_month = 1; + exploded.hour = 0; + exploded.minute = 0; + exploded.second = 0; + exploded.millisecond = 0; + Time t = Time::FromUTCExploded(exploded); + // Unix 1970 epoch. + EXPECT_EQ(GG_INT64_C(11644473600000000), t.ToInternalValue()); + + // We can't test 1601 epoch, since the system time functions on Linux + // only compute years starting from 1900. + +#if defined(OS_STARBOARD) + // But we can test this in Starboard, because we've implemented it in terms of + // ICU instead of possibly-flawed system APIs. + exploded.year = 1601; + // Unix 1970 epoch. + EXPECT_EQ(GG_INT64_C(0), Time::FromUTCExploded(exploded).ToInternalValue()); +#endif +}
diff --git a/src/base/time_win.cc b/src/base/time_win.cc new file mode 100644 index 0000000..6d8e432 --- /dev/null +++ b/src/base/time_win.cc
@@ -0,0 +1,489 @@ +// 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. + + +// Windows Timer Primer +// +// A good article: http://www.ddj.com/windows/184416651 +// A good mozilla bug: http://bugzilla.mozilla.org/show_bug.cgi?id=363258 +// +// The default windows timer, GetSystemTimeAsFileTime is not very precise. +// It is only good to ~15.5ms. +// +// QueryPerformanceCounter is the logical choice for a high-precision timer. +// However, it is known to be buggy on some hardware. Specifically, it can +// sometimes "jump". On laptops, QPC can also be very expensive to call. +// It's 3-4x slower than timeGetTime() on desktops, but can be 10x slower +// on laptops. A unittest exists which will show the relative cost of various +// timers on any system. +// +// The next logical choice is timeGetTime(). timeGetTime has a precision of +// 1ms, but only if you call APIs (timeBeginPeriod()) which affect all other +// applications on the system. By default, precision is only 15.5ms. +// Unfortunately, we don't want to call timeBeginPeriod because we don't +// want to affect other applications. Further, on mobile platforms, use of +// faster multimedia timers can hurt battery life. See the intel +// article about this here: +// http://softwarecommunity.intel.com/articles/eng/1086.htm +// +// To work around all this, we're going to generally use timeGetTime(). We +// will only increase the system-wide timer if we're not running on battery +// power. Using timeBeginPeriod(1) is a requirement in order to make our +// message loop waits have the same resolution that our time measurements +// do. Otherwise, WaitForSingleObject(..., 1) will no less than 15ms when +// there is nothing else to waken the Wait. + +#include "base/time.h" + +#pragma comment(lib, "winmm.lib") +#include <windows.h> +#include <mmsystem.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/cpu.h" +#include "base/memory/singleton.h" +#include "base/synchronization/lock.h" + +using base::Time; +using base::TimeDelta; +using base::TimeTicks; + +namespace { + +// From MSDN, FILETIME "Contains a 64-bit value representing the number of +// 100-nanosecond intervals since January 1, 1601 (UTC)." +int64 FileTimeToMicroseconds(const FILETIME& ft) { + // Need to bit_cast to fix alignment, then divide by 10 to convert + // 100-nanoseconds to milliseconds. This only works on little-endian + // machines. + return bit_cast<int64, FILETIME>(ft) / 10; +} + +void MicrosecondsToFileTime(int64 us, FILETIME* ft) { + DCHECK_GE(us, 0LL) << "Time is less than 0, negative values are not " + "representable in FILETIME"; + + // Multiply by 10 to convert milliseconds to 100-nanoseconds. Bit_cast will + // handle alignment problems. This only works on little-endian machines. + *ft = bit_cast<FILETIME, int64>(us * 10); +} + +int64 CurrentWallclockMicroseconds() { + FILETIME ft; + ::GetSystemTimeAsFileTime(&ft); + return FileTimeToMicroseconds(ft); +} + +// Time between resampling the un-granular clock for this API. 60 seconds. +const int kMaxMillisecondsToAvoidDrift = 60 * Time::kMillisecondsPerSecond; + +int64 initial_time = 0; +TimeTicks initial_ticks; + +void InitializeClock() { + initial_ticks = TimeTicks::Now(); + initial_time = CurrentWallclockMicroseconds(); +} + +} // namespace + +// Time ----------------------------------------------------------------------- + +// The internal representation of Time uses FILETIME, whose epoch is 1601-01-01 +// 00:00:00 UTC. ((1970-1601)*365+89)*24*60*60*1000*1000, where 89 is the +// number of leap year days between 1601 and 1970: (1970-1601)/4 excluding +// 1700, 1800, and 1900. +// static +const int64 Time::kTimeTToMicrosecondsOffset = GG_INT64_C(11644473600000000); + +bool Time::high_resolution_timer_enabled_ = false; +int Time::high_resolution_timer_activated_ = 0; + +// static +Time Time::Now() { + if (initial_time == 0) + InitializeClock(); + + // We implement time using the high-resolution timers so that we can get + // timeouts which are smaller than 10-15ms. If we just used + // CurrentWallclockMicroseconds(), we'd have the less-granular timer. + // + // To make this work, we initialize the clock (initial_time) and the + // counter (initial_ctr). To compute the initial time, we can check + // the number of ticks that have elapsed, and compute the delta. + // + // To avoid any drift, we periodically resync the counters to the system + // clock. + while (true) { + TimeTicks ticks = TimeTicks::Now(); + + // Calculate the time elapsed since we started our timer + TimeDelta elapsed = ticks - initial_ticks; + + // Check if enough time has elapsed that we need to resync the clock. + if (elapsed.InMilliseconds() > kMaxMillisecondsToAvoidDrift) { + InitializeClock(); + continue; + } + + return Time(elapsed + Time(initial_time)); + } +} + +// static +Time Time::NowFromSystemTime() { + // Force resync. + InitializeClock(); + return Time(initial_time); +} + +// static +Time Time::FromFileTime(FILETIME ft) { + if (bit_cast<int64, FILETIME>(ft) == 0) + return Time(); + if (ft.dwHighDateTime == std::numeric_limits<DWORD>::max() && + ft.dwLowDateTime == std::numeric_limits<DWORD>::max()) + return Max(); + return Time(FileTimeToMicroseconds(ft)); +} + +FILETIME Time::ToFileTime() const { + if (is_null()) + return bit_cast<FILETIME, int64>(0); + if (is_max()) { + FILETIME result; + result.dwHighDateTime = std::numeric_limits<DWORD>::max(); + result.dwLowDateTime = std::numeric_limits<DWORD>::max(); + return result; + } + FILETIME utc_ft; + MicrosecondsToFileTime(us_, &utc_ft); + return utc_ft; +} + +// static +void Time::EnableHighResolutionTimer(bool enable) { + // Test for single-threaded access. + static PlatformThreadId my_thread = PlatformThread::CurrentId(); + DCHECK(PlatformThread::CurrentId() == my_thread); + + if (high_resolution_timer_enabled_ == enable) + return; + + high_resolution_timer_enabled_ = enable; +} + +// static +bool Time::ActivateHighResolutionTimer(bool activating) { + if (!high_resolution_timer_enabled_ && activating) + return false; + + // Using anything other than 1ms makes timers granular + // to that interval. + const int kMinTimerIntervalMs = 1; + MMRESULT result; + if (activating) { + result = timeBeginPeriod(kMinTimerIntervalMs); + high_resolution_timer_activated_++; + } else { + result = timeEndPeriod(kMinTimerIntervalMs); + high_resolution_timer_activated_--; + } + return result == TIMERR_NOERROR; +} + +// static +bool Time::IsHighResolutionTimerInUse() { + // Note: we should track the high_resolution_timer_activated_ value + // under a lock if we want it to be accurate in a system with multiple + // message loops. We don't do that - because we don't want to take the + // expense of a lock for this. We *only* track this value so that unit + // tests can see if the high resolution timer is on or off. + return high_resolution_timer_enabled_ && + high_resolution_timer_activated_ > 0; +} + +// static +Time Time::FromExploded(bool is_local, const Exploded& exploded) { + // Create the system struct representing our exploded time. It will either be + // in local time or UTC. + SYSTEMTIME st; + st.wYear = exploded.year; + st.wMonth = exploded.month; + st.wDayOfWeek = exploded.day_of_week; + st.wDay = exploded.day_of_month; + st.wHour = exploded.hour; + st.wMinute = exploded.minute; + st.wSecond = exploded.second; + st.wMilliseconds = exploded.millisecond; + + FILETIME ft; + bool success = true; + // Ensure that it's in UTC. + if (is_local) { + SYSTEMTIME utc_st; + success = TzSpecificLocalTimeToSystemTime(NULL, &st, &utc_st) && + SystemTimeToFileTime(&utc_st, &ft); + } else { + success = !!SystemTimeToFileTime(&st, &ft); + } + + if (!success) { + NOTREACHED() << "Unable to convert time"; + return Time(0); + } + return Time(FileTimeToMicroseconds(ft)); +} + +void Time::Explode(bool is_local, Exploded* exploded) const { + if (us_ < 0LL) { + // We are not able to convert it to FILETIME. + ZeroMemory(exploded, sizeof(*exploded)); + return; + } + + // FILETIME in UTC. + FILETIME utc_ft; + MicrosecondsToFileTime(us_, &utc_ft); + + // FILETIME in local time if necessary. + bool success = true; + // FILETIME in SYSTEMTIME (exploded). + SYSTEMTIME st; + if (is_local) { + SYSTEMTIME utc_st; + // We don't use FileTimeToLocalFileTime here, since it uses the current + // settings for the time zone and daylight saving time. Therefore, if it is + // daylight saving time, it will take daylight saving time into account, + // even if the time you are converting is in standard time. + success = FileTimeToSystemTime(&utc_ft, &utc_st) && + SystemTimeToTzSpecificLocalTime(NULL, &utc_st, &st); + } else { + success = !!FileTimeToSystemTime(&utc_ft, &st); + } + + if (!success) { + NOTREACHED() << "Unable to convert time, don't know why"; + ZeroMemory(exploded, sizeof(*exploded)); + return; + } + + exploded->year = st.wYear; + exploded->month = st.wMonth; + exploded->day_of_week = st.wDayOfWeek; + exploded->day_of_month = st.wDay; + exploded->hour = st.wHour; + exploded->minute = st.wMinute; + exploded->second = st.wSecond; + exploded->millisecond = st.wMilliseconds; +} + +// TimeTicks ------------------------------------------------------------------ +namespace { + +// We define a wrapper to adapt between the __stdcall and __cdecl call of the +// mock function, and to avoid a static constructor. Assigning an import to a +// function pointer directly would require setup code to fetch from the IAT. +DWORD timeGetTimeWrapper() { + return timeGetTime(); +} + +DWORD (*tick_function)(void) = &timeGetTimeWrapper; + +// Accumulation of time lost due to rollover (in milliseconds). +int64 rollover_ms = 0; + +// The last timeGetTime value we saw, to detect rollover. +DWORD last_seen_now = 0; + +// Lock protecting rollover_ms and last_seen_now. +// Note: this is a global object, and we usually avoid these. However, the time +// code is low-level, and we don't want to use Singletons here (it would be too +// easy to use a Singleton without even knowing it, and that may lead to many +// gotchas). Its impact on startup time should be negligible due to low-level +// nature of time code. +base::Lock rollover_lock; + +// We use timeGetTime() to implement TimeTicks::Now(). This can be problematic +// because it returns the number of milliseconds since Windows has started, +// which will roll over the 32-bit value every ~49 days. We try to track +// rollover ourselves, which works if TimeTicks::Now() is called at least every +// 49 days. +TimeDelta RolloverProtectedNow() { + base::AutoLock locked(rollover_lock); + // We should hold the lock while calling tick_function to make sure that + // we keep last_seen_now stay correctly in sync. + DWORD now = tick_function(); + if (now < last_seen_now) + rollover_ms += 0x100000000I64; // ~49.7 days. + last_seen_now = now; + return TimeDelta::FromMilliseconds(now + rollover_ms); +} + +// Overview of time counters: +// (1) CPU cycle counter. (Retrieved via RDTSC) +// The CPU counter provides the highest resolution time stamp and is the least +// expensive to retrieve. However, the CPU counter is unreliable and should not +// be used in production. Its biggest issue is that it is per processor and it +// is not synchronized between processors. Also, on some computers, the counters +// will change frequency due to thermal and power changes, and stop in some +// states. +// +// (2) QueryPerformanceCounter (QPC). The QPC counter provides a high- +// resolution (100 nanoseconds) time stamp but is comparatively more expensive +// to retrieve. What QueryPerformanceCounter actually does is up to the HAL. +// (with some help from ACPI). +// According to http://blogs.msdn.com/oldnewthing/archive/2005/09/02/459952.aspx +// in the worst case, it gets the counter from the rollover interrupt on the +// programmable interrupt timer. In best cases, the HAL may conclude that the +// RDTSC counter runs at a constant frequency, then it uses that instead. On +// multiprocessor machines, it will try to verify the values returned from +// RDTSC on each processor are consistent with each other, and apply a handful +// of workarounds for known buggy hardware. In other words, QPC is supposed to +// give consistent result on a multiprocessor computer, but it is unreliable in +// reality due to bugs in BIOS or HAL on some, especially old computers. +// With recent updates on HAL and newer BIOS, QPC is getting more reliable but +// it should be used with caution. +// +// (3) System time. The system time provides a low-resolution (typically 10ms +// to 55 milliseconds) time stamp but is comparatively less expensive to +// retrieve and more reliable. +class HighResNowSingleton { + public: + static HighResNowSingleton* GetInstance() { + return Singleton<HighResNowSingleton>::get(); + } + + bool IsUsingHighResClock() { + return ticks_per_second_ != 0.0; + } + + void DisableHighResClock() { + ticks_per_second_ = 0.0; + } + + TimeDelta Now() { + if (IsUsingHighResClock()) + return TimeDelta::FromMicroseconds(UnreliableNow()); + + // Just fallback to the slower clock. + return RolloverProtectedNow(); + } + + int64 GetQPCDriftMicroseconds() { + if (!IsUsingHighResClock()) + return 0; + + // The static_cast<long> is needed as a hint to VS 2008 to tell it + // which version of abs() to use. Other compilers don't seem to + // need it, including VS 2010, but to keep code identical we use it + // everywhere. + // TODO(joi): Remove the hint if/when we no longer support VS 2008. + return abs(static_cast<long>((UnreliableNow() - ReliableNow()) - skew_)); + } + + int64 QPCValueToMicroseconds(LONGLONG qpc_value) { + if (!ticks_per_second_) + return 0; + + // Intentionally calculate microseconds in a round about manner to avoid + // overflow and precision issues. Think twice before simplifying! + int64 whole_seconds = qpc_value / ticks_per_second_; + int64 leftover_ticks = qpc_value % ticks_per_second_; + int64 microseconds = (whole_seconds * Time::kMicrosecondsPerSecond) + + ((leftover_ticks * Time::kMicrosecondsPerSecond) / + ticks_per_second_); + return microseconds; + } + + private: + HighResNowSingleton() + : ticks_per_second_(0), + skew_(0) { + InitializeClock(); + + // On Athlon X2 CPUs (e.g. model 15) QueryPerformanceCounter is + // unreliable. Fallback to low-res clock. + base::CPU cpu; + if (cpu.vendor_name() == "AuthenticAMD" && cpu.family() == 15) + DisableHighResClock(); + } + + // Synchronize the QPC clock with GetSystemTimeAsFileTime. + void InitializeClock() { + LARGE_INTEGER ticks_per_sec = {0}; + if (!QueryPerformanceFrequency(&ticks_per_sec)) + return; // Broken, we don't guarantee this function works. + ticks_per_second_ = ticks_per_sec.QuadPart; + + skew_ = UnreliableNow() - ReliableNow(); + } + + // Get the number of microseconds since boot in an unreliable fashion. + int64 UnreliableNow() { + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + return QPCValueToMicroseconds(now.QuadPart); + } + + // Get the number of microseconds since boot in a reliable fashion. + int64 ReliableNow() { + return RolloverProtectedNow().InMicroseconds(); + } + + int64 ticks_per_second_; // 0 indicates QPF failed and we're broken. + int64 skew_; // Skew between lo-res and hi-res clocks (for debugging). + + friend struct DefaultSingletonTraits<HighResNowSingleton>; +}; + +} // namespace + +// static +TimeTicks::TickFunctionType TimeTicks::SetMockTickFunction( + TickFunctionType ticker) { + TickFunctionType old = tick_function; + tick_function = ticker; + return old; +} + +// static +TimeTicks TimeTicks::Now() { + return TimeTicks() + RolloverProtectedNow(); +} + +// static +TimeTicks TimeTicks::HighResNow() { + return TimeTicks() + HighResNowSingleton::GetInstance()->Now(); +} + +// static +TimeTicks TimeTicks::NowFromSystemTraceTime() { + return HighResNow(); +} + +// static +int64 TimeTicks::GetQPCDriftMicroseconds() { + return HighResNowSingleton::GetInstance()->GetQPCDriftMicroseconds(); +} + +// static +TimeTicks TimeTicks::FromQPCValue(LONGLONG qpc_value) { + return TimeTicks( + HighResNowSingleton::GetInstance()->QPCValueToMicroseconds(qpc_value)); +} + +// static +bool TimeTicks::IsHighResClockWorking() { + return HighResNowSingleton::GetInstance()->IsUsingHighResClock(); +} + +// TimeDelta ------------------------------------------------------------------ + +// static +TimeDelta TimeDelta::FromQPCValue(LONGLONG qpc_value) { + return TimeDelta( + HighResNowSingleton::GetInstance()->QPCValueToMicroseconds(qpc_value)); +}
diff --git a/src/base/time_win_unittest.cc b/src/base/time_win_unittest.cc new file mode 100644 index 0000000..7ac3662 --- /dev/null +++ b/src/base/time_win_unittest.cc
@@ -0,0 +1,239 @@ +// 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 <windows.h> +#include <mmsystem.h> +#include <process.h> + +#include "base/threading/platform_thread.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::Time; +using base::TimeDelta; +using base::TimeTicks; + +namespace { + +class MockTimeTicks : public TimeTicks { + public: + static DWORD Ticker() { + return static_cast<int>(InterlockedIncrement(&ticker_)); + } + + static void InstallTicker() { + old_tick_function_ = SetMockTickFunction(&Ticker); + ticker_ = -5; + } + + static void UninstallTicker() { + SetMockTickFunction(old_tick_function_); + } + + private: + static volatile LONG ticker_; + static TickFunctionType old_tick_function_; +}; + +volatile LONG MockTimeTicks::ticker_; +MockTimeTicks::TickFunctionType MockTimeTicks::old_tick_function_; + +HANDLE g_rollover_test_start; + +unsigned __stdcall RolloverTestThreadMain(void* param) { + int64 counter = reinterpret_cast<int64>(param); + DWORD rv = WaitForSingleObject(g_rollover_test_start, INFINITE); + EXPECT_EQ(rv, WAIT_OBJECT_0); + + TimeTicks last = TimeTicks::Now(); + for (int index = 0; index < counter; index++) { + TimeTicks now = TimeTicks::Now(); + int64 milliseconds = (now - last).InMilliseconds(); + // This is a tight loop; we could have looped faster than our + // measurements, so the time might be 0 millis. + EXPECT_GE(milliseconds, 0); + EXPECT_LT(milliseconds, 250); + last = now; + } + return 0; +} + +} // namespace + +TEST(TimeTicks, WinRollover) { + // The internal counter rolls over at ~49days. We'll use a mock + // timer to test this case. + // Basic test algorithm: + // 1) Set clock to rollover - N + // 2) Create N threads + // 3) Start the threads + // 4) Each thread loops through TimeTicks() N times + // 5) Each thread verifies integrity of result. + + const int kThreads = 8; + // Use int64 so we can cast into a void* without a compiler warning. + const int64 kChecks = 10; + + // It takes a lot of iterations to reproduce the bug! + // (See bug 1081395) + for (int loop = 0; loop < 4096; loop++) { + // Setup + MockTimeTicks::InstallTicker(); + g_rollover_test_start = CreateEvent(0, TRUE, FALSE, 0); + HANDLE threads[kThreads]; + + for (int index = 0; index < kThreads; index++) { + void* argument = reinterpret_cast<void*>(kChecks); + unsigned thread_id; + threads[index] = reinterpret_cast<HANDLE>( + _beginthreadex(NULL, 0, RolloverTestThreadMain, argument, 0, + &thread_id)); + EXPECT_NE((HANDLE)NULL, threads[index]); + } + + // Start! + SetEvent(g_rollover_test_start); + + // Wait for threads to finish + for (int index = 0; index < kThreads; index++) { + DWORD rv = WaitForSingleObject(threads[index], INFINITE); + EXPECT_EQ(rv, WAIT_OBJECT_0); + } + + CloseHandle(g_rollover_test_start); + + // Teardown + MockTimeTicks::UninstallTicker(); + } +} + +TEST(TimeTicks, SubMillisecondTimers) { + // HighResNow doesn't work on some systems. Since the product still works + // even if it doesn't work, it makes this entire test questionable. + if (!TimeTicks::IsHighResClockWorking()) + return; + + const int kRetries = 1000; + bool saw_submillisecond_timer = false; + + // Run kRetries attempts to see a sub-millisecond timer. + for (int index = 0; index < 1000; index++) { + TimeTicks last_time = TimeTicks::HighResNow(); + TimeDelta delta; + // Spin until the clock has detected a change. + do { + delta = TimeTicks::HighResNow() - last_time; + } while (delta.InMicroseconds() == 0); + if (delta.InMicroseconds() < 1000) { + saw_submillisecond_timer = true; + break; + } + } + EXPECT_TRUE(saw_submillisecond_timer); +} + +TEST(TimeTicks, TimeGetTimeCaps) { + // Test some basic assumptions that we expect about how timeGetDevCaps works. + + TIMECAPS caps; + MMRESULT status = timeGetDevCaps(&caps, sizeof(caps)); + EXPECT_EQ(TIMERR_NOERROR, status); + if (status != TIMERR_NOERROR) { + printf("Could not get timeGetDevCaps\n"); + return; + } + + EXPECT_GE(static_cast<int>(caps.wPeriodMin), 1); + EXPECT_GT(static_cast<int>(caps.wPeriodMax), 1); + EXPECT_GE(static_cast<int>(caps.wPeriodMin), 1); + EXPECT_GT(static_cast<int>(caps.wPeriodMax), 1); + printf("timeGetTime range is %d to %dms\n", caps.wPeriodMin, + caps.wPeriodMax); +} + +TEST(TimeTicks, QueryPerformanceFrequency) { + // Test some basic assumptions that we expect about QPC. + + LARGE_INTEGER frequency; + BOOL rv = QueryPerformanceFrequency(&frequency); + EXPECT_EQ(TRUE, rv); + EXPECT_GT(frequency.QuadPart, 1000000); // Expect at least 1MHz + printf("QueryPerformanceFrequency is %5.2fMHz\n", + frequency.QuadPart / 1000000.0); +} + +TEST(TimeTicks, TimerPerformance) { + // Verify that various timer mechanisms can always complete quickly. + // Note: This is a somewhat arbitrary test. + const int kLoops = 10000; + // Due to the fact that these run on bbots, which are horribly slow, + // we can't really make any guarantees about minimum runtime. + // Really, we want these to finish in ~10ms, and that is generous. + const int kMaxTime = 35; // Maximum acceptible milliseconds for test. + + typedef TimeTicks (*TestFunc)(); + struct TestCase { + TestFunc func; + char *description; + }; + // Cheating a bit here: assumes sizeof(TimeTicks) == sizeof(Time) + // in order to create a single test case list. + COMPILE_ASSERT(sizeof(TimeTicks) == sizeof(Time), + test_only_works_with_same_sizes); + TestCase cases[] = { + { reinterpret_cast<TestFunc>(Time::Now), "Time::Now" }, + { TimeTicks::Now, "TimeTicks::Now" }, + { TimeTicks::HighResNow, "TimeTicks::HighResNow" }, + { NULL, "" } + }; + + int test_case = 0; + while (cases[test_case].func) { + TimeTicks start = TimeTicks::HighResNow(); + for (int index = 0; index < kLoops; index++) + cases[test_case].func(); + TimeTicks stop = TimeTicks::HighResNow(); + // Turning off the check for acceptible delays. Without this check, + // the test really doesn't do much other than measure. But the + // measurements are still useful for testing timers on various platforms. + // The reason to remove the check is because the tests run on many + // buildbots, some of which are VMs. These machines can run horribly + // slow, and there is really no value for checking against a max timer. + //EXPECT_LT((stop - start).InMilliseconds(), kMaxTime); + printf("%s: %1.2fus per call\n", cases[test_case].description, + (stop - start).InMillisecondsF() * 1000 / kLoops); + test_case++; + } +} + +TEST(TimeTicks, Drift) { + // If QPC is disabled, this isn't measuring anything. + if (!TimeTicks::IsHighResClockWorking()) + return; + + const int kIterations = 100; + int64 total_drift = 0; + + for (int i = 0; i < kIterations; ++i) { + int64 drift_microseconds = TimeTicks::GetQPCDriftMicroseconds(); + + // Make sure the drift never exceeds our limit. + EXPECT_LT(drift_microseconds, 50000); + + // Sleep for a few milliseconds (note that it means 1000 microseconds). + // If we check the drift too frequently, it's going to increase + // monotonically, making our measurement less realistic. + base::PlatformThread::Sleep( + base::TimeDelta::FromMilliseconds((i % 2 == 0) ? 1 : 2)); + + total_drift += drift_microseconds; + } + + // Sanity check. We expect some time drift to occur, especially across + // the number of iterations we do. + EXPECT_LT(0, total_drift); + + printf("average time drift in microseconds: %lld\n", + total_drift / kIterations); +}
diff --git a/src/base/timer.cc b/src/base/timer.cc new file mode 100644 index 0000000..952ce37 --- /dev/null +++ b/src/base/timer.cc
@@ -0,0 +1,247 @@ +// 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/timer.h" + +#include "base/logging.h" +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "base/threading/platform_thread.h" + +namespace base { + +// BaseTimerTaskInternal is a simple delegate for scheduling a callback to +// Timer in the thread's default task runner. It also handles the following +// edge cases: +// - deleted by the task runner. +// - abandoned (orphaned) by Timer. +class BaseTimerTaskInternal { + public: + BaseTimerTaskInternal(Timer* timer) + : timer_(timer) { + } + + ~BaseTimerTaskInternal() { + // This task may be getting cleared because the task runner has been + // destructed. If so, don't leave Timer with a dangling pointer + // to this. + if (timer_) + timer_->StopAndAbandon(); + } + + void Run() { + // timer_ is NULL if we were abandoned. + if (!timer_) + return; + + // *this will be deleted by the task runner, so Timer needs to + // forget us: + timer_->scheduled_task_ = NULL; + + // Although Timer should not call back into *this, let's clear + // the timer_ member first to be pedantic. + Timer* timer = timer_; + timer_ = NULL; + timer->RunScheduledTask(); + } + + // The task remains in the MessageLoop queue, but nothing will happen when it + // runs. + void Abandon() { + timer_ = NULL; + } + + private: + Timer* timer_; +}; + +Timer::Timer(bool retain_user_task, + bool is_repeating, + bool is_task_run_before_scheduling_next) + : scheduled_task_(NULL), + thread_id_(0), + is_repeating_(is_repeating), + is_task_run_before_scheduling_next_(is_task_run_before_scheduling_next), + retain_user_task_(retain_user_task), + is_running_(false) { + if (is_task_run_before_scheduling_next_) { + DCHECK(is_repeating_); + } +} + +Timer::Timer(const tracked_objects::Location& posted_from, + TimeDelta delay, + const base::Closure& user_task, + bool is_repeating, + bool is_task_run_before_scheduling_next) + : scheduled_task_(NULL), + posted_from_(posted_from), + delay_(delay), + user_task_(user_task), + thread_id_(0), + is_repeating_(is_repeating), + is_task_run_before_scheduling_next_(is_task_run_before_scheduling_next), + retain_user_task_(true), + is_running_(false) { + if (is_task_run_before_scheduling_next_) { + DCHECK(is_repeating_); + } +} + +Timer::~Timer() { + StopAndAbandon(); +} + +void Timer::Start(const tracked_objects::Location& posted_from, + TimeDelta delay, + const base::Closure& user_task) { + SetTaskInfo(posted_from, delay, user_task); + Reset(); +} + +void Timer::Stop() { + is_running_ = false; + if (!retain_user_task_) + user_task_.Reset(); +} + +void Timer::Reset() { + DCHECK(!user_task_.is_null()); + + // If there's no pending task, start one up and return. + if (!scheduled_task_) { + PostNewScheduledTask(delay_); + return; + } + + // Set the new desired_run_time_. + desired_run_time_ = TimeTicks::Now() + delay_; + + // We can use the existing scheduled task if it arrives before the new + // desired_run_time_. + if (desired_run_time_ > scheduled_run_time_) { + is_running_ = true; + return; + } + + // We can't reuse the scheduled_task_, so abandon it and post a new one. + AbandonScheduledTask(); + PostNewScheduledTask(delay_); +} + +void Timer::SetTaskInfo(const tracked_objects::Location& posted_from, + TimeDelta delay, + const base::Closure& user_task) { + posted_from_ = posted_from; + delay_ = delay; + user_task_ = user_task; +} + +// This function is not re-implemented using SetupNewScheduledTask() and +// PostNewScheduledTask() to ensure that the default behavior of Timer is +// exactly the same as before. +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; + // 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_) + thread_id_ = static_cast<int>(PlatformThread::CurrentId()); +} + +Timer::NewScheduledTaskInfo Timer::SetupNewScheduledTask( + TimeDelta expected_delay) { + DCHECK(scheduled_task_ == NULL); + DCHECK(is_task_run_before_scheduling_next_); + DCHECK(thread_id_); + + is_running_ = true; + scheduled_task_ = new BaseTimerTaskInternal(this); + + NewScheduledTaskInfo task_info; + task_info.posted_from = posted_from_; + task_info.task = + base::Bind(&BaseTimerTaskInternal::Run, base::Owned(scheduled_task_)); + + scheduled_run_time_ = desired_run_time_ = TimeTicks::Now() + expected_delay; + + return task_info; +} + +void Timer::PostNewScheduledTask(NewScheduledTaskInfo task_info, + TimeDelta delay) { + // Some task runners expect a non-zero delay for PostDelayedTask. + if (delay.ToInternalValue() == 0) { + ThreadTaskRunnerHandle::Get()->PostTask(task_info.posted_from, + task_info.task); + } else { + ThreadTaskRunnerHandle::Get()->PostDelayedTask(task_info.posted_from, + task_info.task, delay); + } +} + +void Timer::AbandonScheduledTask() { + DCHECK(thread_id_ == 0 || + thread_id_ == static_cast<int>(PlatformThread::CurrentId())); + if (scheduled_task_) { + scheduled_task_->Abandon(); + scheduled_task_ = NULL; + } +} + +void Timer::RunScheduledTask() { + // Task may have been disabled. + if (!is_running_) + return; + + // First check if we need to delay the task because of a new target time. + if (desired_run_time_ > scheduled_run_time_) { + // TimeTicks::Now() can be expensive, so only call it if we know the user + // has changed the desired_run_time_. + TimeTicks now = TimeTicks::Now(); + // Task runner may have called us late anyway, so only post a continuation + // task if the desired_run_time_ is in the future. + if (desired_run_time_ > now) { + // Post a new task to span the remaining time. + PostNewScheduledTask(desired_run_time_ - now); + return; + } + } + + // Make a local copy of the task to run. The Stop method will reset the + // user_task_ member if retain_user_task_ is false. + base::Closure task = user_task_; + + if (!is_repeating_) { + Stop(); + task.Run(); + return; + } + + if (is_task_run_before_scheduling_next_) { + // Setup member variables and the next tasks before the current one runs as + // we cannot access any member variables after calling task.Run(). + NewScheduledTaskInfo task_info = SetupNewScheduledTask(delay_); + base::Time task_start_time = base::Time::Now(); + task.Run(); + base::TimeDelta task_duration = base::Time::Now() - task_start_time; + if (task_duration >= delay_) { + PostNewScheduledTask(task_info, base::TimeDelta::FromInternalValue(0)); + } else { + PostNewScheduledTask(task_info, delay_ - task_duration); + } + } else { + PostNewScheduledTask(delay_); + task.Run(); + } + + // No more member accesses here: *this could be deleted at this point. +} + +} // namespace base
diff --git a/src/base/timer.h b/src/base/timer.h new file mode 100644 index 0000000..e1ea113 --- /dev/null +++ b/src/base/timer.h
@@ -0,0 +1,283 @@ +// 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. + +// OneShotTimer and RepeatingTimer provide a simple timer API. As the names +// suggest, OneShotTimer calls you back once after a time delay expires. +// RepeatingTimer on the other hand calls you back periodically with the +// prescribed time interval. +// +// OneShotTimer and RepeatingTimer both cancel the timer when they go out of +// scope, which makes it easy to ensure that you do not get called when your +// object has gone out of scope. Just instantiate a OneShotTimer or +// RepeatingTimer as a member variable of the class for which you wish to +// receive timer events. +// +// Sample RepeatingTimer usage: +// +// class MyClass { +// public: +// void StartDoingStuff() { +// timer_.Start(FROM_HERE, TimeDelta::FromSeconds(1), +// this, &MyClass::DoStuff); +// } +// void StopDoingStuff() { +// timer_.Stop(); +// } +// private: +// void DoStuff() { +// // This method is called every second to do stuff. +// ... +// } +// base::RepeatingTimer<MyClass> timer_; +// }; +// +// Both OneShotTimer and RepeatingTimer also support a Reset method, which +// allows you to easily defer the timer event until the timer delay passes once +// again. So, in the above example, if 0.5 seconds have already passed, +// calling Reset on timer_ would postpone DoStuff by another 1 second. In +// other words, Reset is shorthand for calling Stop and then Start again with +// the same arguments. +// +// NOTE: These APIs are not thread safe. Always call from the same thread. + +#ifndef BASE_TIMER_H_ +#define BASE_TIMER_H_ + +// IMPORTANT: If you change timer code, make sure that all tests (including +// disabled ones) from timer_unittests.cc pass locally. Some are disabled +// because they're flaky on the buildbot, but when you run them locally you +// should be able to tell the difference. + +#include "base/base_export.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/time.h" + +#if defined(__LB_SHELL_DEBUG_TASKS__) +#include <stdio.h> +#include <string> +#endif + +class MessageLoop; + +namespace base { + +class BaseTimerTaskInternal; + +//----------------------------------------------------------------------------- +// This class wraps MessageLoop::PostDelayedTask to manage delayed and repeating +// tasks. It must be destructed on the same thread that starts tasks. There are +// DCHECKs in place to verify this. +// For a Timer with repeating task, the user can choose whether the Timer +// should post the next task T(n+1) before or after running the current task +// T(n). As tasks are run in sequence, in the first case T(n+1) is likely to +// run before other tasks scheduled while the T(n) is running. In the second +// case T(n+1) will be run after other tasks scheduled while T(n) is running. +// By default Timer uses the behavior but the user should use the second +// behavior if the repeating task can be lengthy and is intended to be run at a +// slightly lower priority. +class BASE_EXPORT Timer { + public: + // Construct a timer in repeating or one-shot mode. Start or SetTaskInfo must + // be called later to set task info. |retain_user_task| determines whether the + // user_task is retained or reset when it runs or stops. When + // |is_task_run_before_scheduling_next| is true, the next task will be posted + // after the current one is finished. + Timer(bool retain_user_task, + bool is_repeating, + bool is_task_run_before_scheduling_next = false); + + // Construct a timer with retained task info. When + // |is_task_run_before_scheduling_next| is true, the next task will be posted + // after the current one is finished. + Timer(const tracked_objects::Location& posted_from, + TimeDelta delay, + const base::Closure& user_task, + bool is_repeating, + bool is_task_run_before_scheduling_next = false); + + virtual ~Timer(); + + // Returns true if the timer is running (i.e., not stopped). + bool IsRunning() const { + return is_running_; + } + + // Returns the current delay for this timer. + TimeDelta GetCurrentDelay() const { + return delay_; + } + + // Start the timer to run at the given |delay| from now. If the timer is + // already running, it will be replaced to call the given |user_task|. + void Start(const tracked_objects::Location& posted_from, + TimeDelta delay, + const base::Closure& user_task); + + // Call this method to stop and cancel the timer. It is a no-op if the timer + // is not running. + void Stop(); + + // Call this method to reset the timer delay. The user_task_ must be set. If + // the timer is not running, this will start it by posting a task. + void Reset(); + + const base::Closure& user_task() const { return user_task_; } + const TimeTicks& desired_run_time() const { return desired_run_time_; } + + protected: + // Used to initiate a new delayed task. This has the side-effect of disabling + // scheduled_task_ if it is non-null. + void SetTaskInfo(const tracked_objects::Location& posted_from, + TimeDelta delay, + const base::Closure& user_task); + + private: + struct NewScheduledTaskInfo { + tracked_objects::Location posted_from; + base::Closure task; + }; + + friend class BaseTimerTaskInternal; + + // Allocates a new scheduled_task_ and posts it on the current MessageLoop + // with the given |delay|. scheduled_task_ must be NULL. scheduled_run_time_ + // and desired_run_time_ are reset to Now() + delay. + void PostNewScheduledTask(TimeDelta delay); + // Allocates a new scheduled_task_ so it can be posted on the current + // MessageLoop later. scheduled_task_ must be NULL. scheduled_run_time_ and + // desired_run_time_ are reset to Now() + delay. + NewScheduledTaskInfo SetupNewScheduledTask(TimeDelta expected_delay); + // Post the task on the current MessageLoop. Note that the delay isn't the + // same as the expected_delay in the above function. It has been adjusted + // according to the duration of the current task. + void PostNewScheduledTask(NewScheduledTaskInfo task_info, TimeDelta delay); + + // Disable scheduled_task_ and abandon it so that it no longer refers back to + // this object. + void AbandonScheduledTask(); + + // Called by BaseTimerTaskInternal when the MessageLoop runs it. + void RunScheduledTask(); + + // Stop running task (if any) and abandon scheduled task (if any). + void StopAndAbandon() { + Stop(); + AbandonScheduledTask(); + } + + // When non-NULL, the scheduled_task_ is waiting in the MessageLoop to call + // RunScheduledTask() at scheduled_run_time_. + BaseTimerTaskInternal* scheduled_task_; + + // Location in user code. + tracked_objects::Location posted_from_; + // Delay requested by user. + TimeDelta delay_; + // user_task_ is what the user wants to be run at desired_run_time_. + base::Closure user_task_; + + // The estimated time that the MessageLoop will run the scheduled_task_ that + // will call RunScheduledTask(). + TimeTicks scheduled_run_time_; + + // The desired run time of user_task_. The user may update this at any time, + // even if their previous request has not run yet. If desired_run_time_ is + // 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. + TimeTicks desired_run_time_; + + // Thread ID of current MessageLoop for verifying single-threaded usage. + int thread_id_; + + // Repeating timers automatically post the task again before calling the task + // callback. + const bool is_repeating_; + + // The next task will be scheduled after the current one is finished. Can + // only be true when is_repeating_ is true. + const bool is_task_run_before_scheduling_next_; + + // If true, hold on to the user_task_ closure object for reuse. + const bool retain_user_task_; + + // If true, user_task_ is scheduled to run sometime in the future. + bool is_running_; + + DISALLOW_COPY_AND_ASSIGN(Timer); +}; + +//----------------------------------------------------------------------------- +// This class is an implementation detail of OneShotTimer and RepeatingTimer. +// Please do not use this class directly. +template <class Receiver, bool kIsRepeating> +class BaseTimerMethodPointer : public Timer { + public: + typedef void (Receiver::*ReceiverMethod)(); + + // This is here to work around the fact that Timer::Start is "hidden" by the + // Start definition below, rather than being overloaded. + // TODO(tim): We should remove uses of BaseTimerMethodPointer::Start below + // and convert callers to use the base::Closure version in Timer::Start, + // see bug 148832. + using Timer::Start; + + BaseTimerMethodPointer() : Timer(kIsRepeating, kIsRepeating) {} + + // Start the timer to run at the given |delay| from now. If the timer is + // already running, it will be replaced to call a task formed from + // |reviewer->*method|. + void Start(const tracked_objects::Location& posted_from, + TimeDelta delay, + Receiver* receiver, + ReceiverMethod method) { + Timer::Start(posted_from, delay, + base::Bind(method, base::Unretained(receiver))); + } +}; + +//----------------------------------------------------------------------------- +// A simple, one-shot timer. See usage notes at the top of the file. +template <class Receiver> +class OneShotTimer : public BaseTimerMethodPointer<Receiver, false> {}; + +//----------------------------------------------------------------------------- +// A simple, repeating timer. See usage notes at the top of the file. +template <class Receiver> +class RepeatingTimer : public BaseTimerMethodPointer<Receiver, true> {}; + +//----------------------------------------------------------------------------- +// A Delay timer is like The Button from Lost. Once started, you have to keep +// calling Reset otherwise it will call the given method in the MessageLoop +// thread. +// +// Once created, it is inactive until Reset is called. Once |delay| seconds have +// passed since the last call to Reset, the callback is made. Once the callback +// has been made, it's inactive until Reset is called again. +// +// If destroyed, the timeout is canceled and will not occur even if already +// inflight. +template <class Receiver> +class DelayTimer : protected Timer { + public: + typedef void (Receiver::*ReceiverMethod)(); + + DelayTimer(const tracked_objects::Location& posted_from, + TimeDelta delay, + Receiver* receiver, + ReceiverMethod method) + : Timer(posted_from, delay, + base::Bind(method, base::Unretained(receiver)), + false) {} + + void Reset() { Timer::Reset(); } +}; + +} // namespace base + +#endif // BASE_TIMER_H_
diff --git a/src/base/timer_unittest.cc b/src/base/timer_unittest.cc new file mode 100644 index 0000000..9c31264 --- /dev/null +++ b/src/base/timer_unittest.cc
@@ -0,0 +1,488 @@ +// 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/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/timer.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::TimeDelta; + +namespace { + +// The message loops on which each timer should be tested. +const MessageLoop::Type testing_message_loops[] = { + MessageLoop::TYPE_DEFAULT, MessageLoop::TYPE_IO, +#if !defined(OS_IOS) && !defined(OS_STARBOARD) + // iOS and Starboard do not allow direct running of the UI loop. + MessageLoop::TYPE_UI, +#endif +}; + +const int kNumTestingMessageLoops = arraysize(testing_message_loops); + +class OneShotTimerTester { + public: + OneShotTimerTester(bool* did_run, unsigned milliseconds = 10) + : did_run_(did_run), + delay_ms_(milliseconds) { + } + void Start() { + timer_.Start(FROM_HERE, TimeDelta::FromMilliseconds(delay_ms_), this, + &OneShotTimerTester::Run); + } + private: + void Run() { + *did_run_ = true; + MessageLoop::current()->Quit(); + } + bool* did_run_; + base::OneShotTimer<OneShotTimerTester> timer_; + const unsigned delay_ms_; +}; + +class OneShotSelfDeletingTimerTester { + public: + explicit OneShotSelfDeletingTimerTester(bool* did_run) : + did_run_(did_run), + timer_(new base::OneShotTimer<OneShotSelfDeletingTimerTester>()) { + } + void Start() { + timer_->Start(FROM_HERE, TimeDelta::FromMilliseconds(10), this, + &OneShotSelfDeletingTimerTester::Run); + } + private: + void Run() { + *did_run_ = true; + timer_.reset(); + MessageLoop::current()->Quit(); + } + bool* did_run_; + scoped_ptr<base::OneShotTimer<OneShotSelfDeletingTimerTester> > timer_; +}; + +class RepeatingTimerTester { + public: + explicit RepeatingTimerTester(bool* did_run) + : did_run_(did_run), counter_(10) { + } + + void Start() { + timer_.Start(FROM_HERE, TimeDelta::FromMilliseconds(10), this, + &RepeatingTimerTester::Run); + } + private: + void Run() { + if (--counter_ == 0) { + *did_run_ = true; + MessageLoop::current()->Quit(); + } + } + bool* did_run_; + int counter_; + base::RepeatingTimer<RepeatingTimerTester> timer_; +}; + +void RunTest_OneShotTimer(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + bool did_run = false; + OneShotTimerTester f(&did_run); + f.Start(); + + MessageLoop::current()->Run(); + + EXPECT_TRUE(did_run); +} + +void RunTest_OneShotTimer_Cancel(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + bool did_run_a = false; + OneShotTimerTester* a = new OneShotTimerTester(&did_run_a); + + // This should run before the timer expires. + MessageLoop::current()->DeleteSoon(FROM_HERE, a); + + // Now start the timer. + a->Start(); + + bool did_run_b = false; + OneShotTimerTester b(&did_run_b); + b.Start(); + + MessageLoop::current()->Run(); + + EXPECT_FALSE(did_run_a); + EXPECT_TRUE(did_run_b); +} + +void RunTest_OneShotSelfDeletingTimer(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + bool did_run = false; + OneShotSelfDeletingTimerTester f(&did_run); + f.Start(); + + MessageLoop::current()->Run(); + + EXPECT_TRUE(did_run); +} + +void RunTest_RepeatingTimer(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + bool did_run = false; + RepeatingTimerTester f(&did_run); + f.Start(); + + MessageLoop::current()->Run(); + + EXPECT_TRUE(did_run); +} + +void RunTest_RepeatingTimer_Cancel(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + bool did_run_a = false; + RepeatingTimerTester* a = new RepeatingTimerTester(&did_run_a); + + // This should run before the timer expires. + MessageLoop::current()->DeleteSoon(FROM_HERE, a); + + // Now start the timer. + a->Start(); + + bool did_run_b = false; + RepeatingTimerTester b(&did_run_b); + b.Start(); + + MessageLoop::current()->Run(); + + EXPECT_FALSE(did_run_a); + EXPECT_TRUE(did_run_b); +} + +class DelayTimerTarget { + public: + DelayTimerTarget() + : signaled_(false) { + } + + bool signaled() const { return signaled_; } + + void Signal() { + ASSERT_FALSE(signaled_); + signaled_ = true; + } + + private: + bool signaled_; +}; + +void RunTest_DelayTimer_NoCall(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + // If Delay is never called, the timer shouldn't go off. + DelayTimerTarget target; + base::DelayTimer<DelayTimerTarget> timer(FROM_HERE, + TimeDelta::FromMilliseconds(1), &target, &DelayTimerTarget::Signal); + + bool did_run = false; + OneShotTimerTester tester(&did_run); + tester.Start(); + MessageLoop::current()->Run(); + + ASSERT_FALSE(target.signaled()); +} + +void RunTest_DelayTimer_OneCall(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + DelayTimerTarget target; + base::DelayTimer<DelayTimerTarget> timer(FROM_HERE, + TimeDelta::FromMilliseconds(1), &target, &DelayTimerTarget::Signal); + timer.Reset(); + + bool did_run = false; + OneShotTimerTester tester(&did_run, 100 /* milliseconds */); + tester.Start(); + MessageLoop::current()->Run(); + + ASSERT_TRUE(target.signaled()); +} + +struct ResetHelper { + ResetHelper(base::DelayTimer<DelayTimerTarget>* timer, + DelayTimerTarget* target) + : timer_(timer), + target_(target) { + } + + void Reset() { + ASSERT_FALSE(target_->signaled()); + timer_->Reset(); + } + + private: + base::DelayTimer<DelayTimerTarget> *const timer_; + DelayTimerTarget *const target_; +}; + +void RunTest_DelayTimer_Reset(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + // If Delay is never called, the timer shouldn't go off. + DelayTimerTarget target; + base::DelayTimer<DelayTimerTarget> timer(FROM_HERE, + TimeDelta::FromMilliseconds(50), &target, &DelayTimerTarget::Signal); + timer.Reset(); + + ResetHelper reset_helper(&timer, &target); + + base::OneShotTimer<ResetHelper> timers[20]; + for (size_t i = 0; i < arraysize(timers); ++i) { + timers[i].Start(FROM_HERE, TimeDelta::FromMilliseconds(i * 10), + &reset_helper, &ResetHelper::Reset); + } + + bool did_run = false; + OneShotTimerTester tester(&did_run, 300); + tester.Start(); + MessageLoop::current()->Run(); + + ASSERT_TRUE(target.signaled()); +} + +class DelayTimerFatalTarget { + public: + void Signal() { + ASSERT_TRUE(false); + } +}; + + +void RunTest_DelayTimer_Deleted(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + DelayTimerFatalTarget target; + + { + base::DelayTimer<DelayTimerFatalTarget> timer( + FROM_HERE, TimeDelta::FromMilliseconds(50), &target, + &DelayTimerFatalTarget::Signal); + timer.Reset(); + } + + // When the timer is deleted, the DelayTimerFatalTarget should never be + // called. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); +} + +} // namespace + +//----------------------------------------------------------------------------- +// Each test is run against each type of MessageLoop. That way we are sure +// that timers work properly in all configurations. + +TEST(TimerTest, OneShotTimer) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_OneShotTimer(testing_message_loops[i]); + } +} + +TEST(TimerTest, OneShotTimer_Cancel) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_OneShotTimer_Cancel(testing_message_loops[i]); + } +} + +// If underline timer does not handle properly, we will crash or fail +// in full page heap environment. +TEST(TimerTest, OneShotSelfDeletingTimer) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_OneShotSelfDeletingTimer(testing_message_loops[i]); + } +} + +TEST(TimerTest, RepeatingTimer) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_RepeatingTimer(testing_message_loops[i]); + } +} + +TEST(TimerTest, RepeatingTimer_Cancel) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_RepeatingTimer_Cancel(testing_message_loops[i]); + } +} + +TEST(TimerTest, DelayTimer_NoCall) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_DelayTimer_NoCall(testing_message_loops[i]); + } +} + +TEST(TimerTest, DelayTimer_OneCall) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_DelayTimer_OneCall(testing_message_loops[i]); + } +} + +// It's flaky on the buildbot, http://crbug.com/25038. +TEST(TimerTest, DISABLED_DelayTimer_Reset) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_DelayTimer_Reset(testing_message_loops[i]); + } +} + +TEST(TimerTest, DelayTimer_Deleted) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_DelayTimer_Deleted(testing_message_loops[i]); + } +} + +TEST(TimerTest, MessageLoopShutdown) { + // This test is designed to verify that shutdown of the + // message loop does not cause crashes if there were pending + // timers not yet fired. It may only trigger exceptions + // if debug heap checking is enabled. + bool did_run = false; + { + OneShotTimerTester a(&did_run); + OneShotTimerTester b(&did_run); + OneShotTimerTester c(&did_run); + OneShotTimerTester d(&did_run); + { + MessageLoop loop(MessageLoop::TYPE_DEFAULT); + a.Start(); + b.Start(); + } // MessageLoop destructs by falling out of scope. + } // OneShotTimers destruct. SHOULD NOT CRASH, of course. + + EXPECT_FALSE(did_run); +} + +void TimerTestCallback() { +} + +TEST(TimerTest, NonRepeatIsRunning) { + { + MessageLoop loop(MessageLoop::TYPE_DEFAULT); + base::Timer timer(false, false); + EXPECT_FALSE(timer.IsRunning()); + timer.Start(FROM_HERE, TimeDelta::FromDays(1), + base::Bind(&TimerTestCallback)); + EXPECT_TRUE(timer.IsRunning()); + timer.Stop(); + EXPECT_FALSE(timer.IsRunning()); + EXPECT_TRUE(timer.user_task().is_null()); + } + + { + base::Timer timer(true, false); + MessageLoop loop(MessageLoop::TYPE_DEFAULT); + EXPECT_FALSE(timer.IsRunning()); + timer.Start(FROM_HERE, TimeDelta::FromDays(1), + base::Bind(&TimerTestCallback)); + EXPECT_TRUE(timer.IsRunning()); + timer.Stop(); + EXPECT_FALSE(timer.IsRunning()); + ASSERT_FALSE(timer.user_task().is_null()); + timer.Reset(); + EXPECT_TRUE(timer.IsRunning()); + } +} + +TEST(TimerTest, NonRepeatMessageLoopDeath) { + base::Timer timer(false, false); + { + MessageLoop loop(MessageLoop::TYPE_DEFAULT); + EXPECT_FALSE(timer.IsRunning()); + timer.Start(FROM_HERE, TimeDelta::FromDays(1), + base::Bind(&TimerTestCallback)); + EXPECT_TRUE(timer.IsRunning()); + } + EXPECT_FALSE(timer.IsRunning()); + EXPECT_TRUE(timer.user_task().is_null()); +} + +TEST(TimerTest, RetainRepeatIsRunning) { + MessageLoop loop(MessageLoop::TYPE_DEFAULT); + base::Timer timer(FROM_HERE, TimeDelta::FromDays(1), + base::Bind(&TimerTestCallback), true); + EXPECT_FALSE(timer.IsRunning()); + timer.Reset(); + EXPECT_TRUE(timer.IsRunning()); + timer.Stop(); + EXPECT_FALSE(timer.IsRunning()); + timer.Reset(); + EXPECT_TRUE(timer.IsRunning()); +} + +TEST(TimerTest, RetainNonRepeatIsRunning) { + MessageLoop loop(MessageLoop::TYPE_DEFAULT); + base::Timer timer(FROM_HERE, TimeDelta::FromDays(1), + base::Bind(&TimerTestCallback), false); + EXPECT_FALSE(timer.IsRunning()); + timer.Reset(); + EXPECT_TRUE(timer.IsRunning()); + timer.Stop(); + EXPECT_FALSE(timer.IsRunning()); + timer.Reset(); + EXPECT_TRUE(timer.IsRunning()); +} + +namespace { + +bool g_callback_happened1 = false; +bool g_callback_happened2 = false; + +void ClearAllCallbackHappened() { + g_callback_happened1 = false; + g_callback_happened2 = false; +} + +void SetCallbackHappened1() { + g_callback_happened1 = true; + MessageLoop::current()->Quit(); +} + +void SetCallbackHappened2() { + g_callback_happened2 = true; + MessageLoop::current()->Quit(); +} + +TEST(TimerTest, ContinuationStopStart) { + { + ClearAllCallbackHappened(); + MessageLoop loop(MessageLoop::TYPE_DEFAULT); + base::Timer timer(false, false); + timer.Start(FROM_HERE, TimeDelta::FromMilliseconds(10), + base::Bind(&SetCallbackHappened1)); + timer.Stop(); + timer.Start(FROM_HERE, TimeDelta::FromMilliseconds(40), + base::Bind(&SetCallbackHappened2)); + MessageLoop::current()->Run(); + EXPECT_FALSE(g_callback_happened1); + EXPECT_TRUE(g_callback_happened2); + } +} + +TEST(TimerTest, ContinuationReset) { + { + ClearAllCallbackHappened(); + MessageLoop loop(MessageLoop::TYPE_DEFAULT); + base::Timer timer(false, false); + timer.Start(FROM_HERE, TimeDelta::FromMilliseconds(10), + base::Bind(&SetCallbackHappened1)); + timer.Reset(); + // Since Reset happened before task ran, the user_task must not be cleared: + ASSERT_FALSE(timer.user_task().is_null()); + MessageLoop::current()->Run(); + EXPECT_TRUE(g_callback_happened1); + } +} + +} // namespace
diff --git a/src/base/tools_sanity_unittest.cc b/src/base/tools_sanity_unittest.cc new file mode 100644 index 0000000..a901f59 --- /dev/null +++ b/src/base/tools_sanity_unittest.cc
@@ -0,0 +1,287 @@ +// 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. +// +// This file contains intentional memory errors, some of which may lead to +// crashes if the test is ran without special memory testing tools. We use these +// errors to verify the sanity of the tools. + +#include "base/atomicops.h" +#include "base/message_loop.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/thread.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_STARBOARD) +#include "starboard/memory.h" +#define free SbMemoryFree +#define malloc SbMemoryAllocate +#endif + +namespace base { + +namespace { + +const base::subtle::Atomic32 kMagicValue = 42; + +// Helper for memory accesses that can potentially corrupt memory or cause a +// crash during a native run. +#if defined(ADDRESS_SANITIZER) +#if defined(OS_IOS) || defined(__LB_SHELL__) || defined(OS_STARBOARD) +// EXPECT_DEATH is not supported on IOS. +// TODO: We have EXPECT_DEATH disabled for all Steel platforms, +// but we could potentially support it on Linux. +#define HARMFUL_ACCESS(action,error_regexp) do { action; } while (0) +#else +#define HARMFUL_ACCESS(action,error_regexp) EXPECT_DEATH(action,error_regexp) +#endif // !OS_IOS +#else +#define HARMFUL_ACCESS(action,error_regexp) \ +do { if (RunningOnValgrind()) { action; } } while (0) +#endif + +void ReadUninitializedValue(char *ptr) { + // Comparison with 64 is to prevent clang from optimizing away the + // jump -- valgrind only catches jumps and conditional moves, but clang uses + // the borrow flag if the condition is just `*ptr == '\0'`. + if (*ptr == 64) { + (*ptr)++; + } else { + (*ptr)--; + } +} + +void ReadValueOutOfArrayBoundsLeft(char *ptr) { + char c = ptr[-2]; + VLOG(1) << "Reading a byte out of bounds: " << c; +} + +void ReadValueOutOfArrayBoundsRight(char *ptr, size_t size) { + char c = ptr[size + 1]; + VLOG(1) << "Reading a byte out of bounds: " << c; +} + +// This is harmless if you run it under Valgrind thanks to redzones. +void WriteValueOutOfArrayBoundsLeft(char *ptr) { + ptr[-1] = kMagicValue; +} + +// This is harmless if you run it under Valgrind thanks to redzones. +void WriteValueOutOfArrayBoundsRight(char *ptr, size_t size) { + ptr[size] = kMagicValue; +} + +void MakeSomeErrors(char *ptr, size_t size) { + ReadUninitializedValue(ptr); + HARMFUL_ACCESS(ReadValueOutOfArrayBoundsLeft(ptr), + "heap-buffer-overflow.*2 bytes to the left"); + HARMFUL_ACCESS(ReadValueOutOfArrayBoundsRight(ptr, size), + "heap-buffer-overflow.*1 bytes to the right"); + HARMFUL_ACCESS(WriteValueOutOfArrayBoundsLeft(ptr), + "heap-buffer-overflow.*1 bytes to the left"); + HARMFUL_ACCESS(WriteValueOutOfArrayBoundsRight(ptr, size), + "heap-buffer-overflow.*0 bytes to the right"); +} + +} // namespace + +#if defined(ADDRESS_SANITIZER) && \ + (defined(OS_IOS) || defined(__LB_SHELL__) || defined(OS_STARBOARD)) +// Because iOS doesn't support death tests, each of the following tests will +// crash the whole program under ASan. +#define MAYBE_AccessesToNewMemory DISABLED_AccessesToNewMemory +#define MAYBE_AccessesToMallocMemory DISABLED_AccessesToMallocMemory +#define MAYBE_ArrayDeletedWithoutBraces DISABLED_ArrayDeletedWithoutBraces +#define MAYBE_MemoryLeak DISABLED_MemoryLeak +#define MAYBE_SingleElementDeletedWithBraces \ + DISABLED_SingleElementDeletedWithBraces +#else +#define MAYBE_AccessesToNewMemory AccessesToNewMemory +#define MAYBE_AccessesToMallocMemory AccessesToMallocMemory +#define MAYBE_ArrayDeletedWithoutBraces ArrayDeletedWithoutBraces +#define MAYBE_MemoryLeak MemoryLeak +#define MAYBE_SingleElementDeletedWithBraces SingleElementDeletedWithBraces +#endif +// A memory leak detector should report an error in this test. +TEST(ToolsSanityTest, MAYBE_MemoryLeak) { + // Without the |volatile|, clang optimizes away the next two lines. + int* volatile leak = new int[256]; // Leak some memory intentionally. + leak[4] = 1; // Make sure the allocated memory is used. +} + +TEST(ToolsSanityTest, MAYBE_AccessesToNewMemory) { + char *foo = new char[10]; + MakeSomeErrors(foo, 10); + delete [] foo; + // Use after delete. + HARMFUL_ACCESS(foo[5] = 0, "heap-use-after-free"); +} + +TEST(ToolsSanityTest, MAYBE_AccessesToMallocMemory) { + char *foo = reinterpret_cast<char*>(malloc(10)); + MakeSomeErrors(foo, 10); + free(foo); + // Use after free. + HARMFUL_ACCESS(foo[5] = 0, "heap-use-after-free"); +} + + +static int* allocateArray() { + // Clang warns about the mismatched new[]/delete if they occur in the same + // function. + return new int[10]; +} + +TEST(ToolsSanityTest, MAYBE_ArrayDeletedWithoutBraces) { +#if !defined(ADDRESS_SANITIZER) + // This test may corrupt memory if not run under Valgrind or compiled with + // AddressSanitizer. + if (!RunningOnValgrind()) + return; +#endif + + // Without the |volatile|, clang optimizes away the next two lines. + int* volatile foo = allocateArray(); + delete foo; +} + +static int* allocateScalar() { + // Clang warns about the mismatched new/delete[] if they occur in the same + // function. + return new int; +} + +TEST(ToolsSanityTest, MAYBE_SingleElementDeletedWithBraces) { +#if !defined(ADDRESS_SANITIZER) + // This test may corrupt memory if not run under Valgrind or compiled with + // AddressSanitizer. + if (!RunningOnValgrind()) + return; +#endif + + // Without the |volatile|, clang optimizes away the next two lines. + int* volatile foo = allocateScalar(); + (void) foo; + delete [] foo; +} + +#if defined(ADDRESS_SANITIZER) +TEST(ToolsSanityTest, DISABLED_AddressSanitizerNullDerefCrashTest) { + // Intentionally crash to make sure AddressSanitizer is running. + // This test should not be ran on bots. + int* volatile zero = NULL; + *zero = 0; +} + +TEST(ToolsSanityTest, DISABLED_AddressSanitizerLocalOOBCrashTest) { + // Intentionally crash to make sure AddressSanitizer is instrumenting + // the local variables. + // This test should not be ran on bots. + int array[5]; + // Work around the OOB warning reported by Clang. + int* volatile access = &array[5]; + *access = 43; +} + +namespace { +int g_asan_test_global_array[10]; +} // namespace + +TEST(ToolsSanityTest, DISABLED_AddressSanitizerGlobalOOBCrashTest) { + // Intentionally crash to make sure AddressSanitizer is instrumenting + // the global variables. + // This test should not be ran on bots. + + // Work around the OOB warning reported by Clang. + int* volatile access = g_asan_test_global_array - 1; + *access = 43; +} + +#endif + +namespace { + +// We use caps here just to ensure that the method name doesn't interfere with +// the wildcarded suppressions. +class TOOLS_SANITY_TEST_CONCURRENT_THREAD : public PlatformThread::Delegate { + public: + explicit TOOLS_SANITY_TEST_CONCURRENT_THREAD(bool *value) : value_(value) {} + virtual ~TOOLS_SANITY_TEST_CONCURRENT_THREAD() {} + virtual void ThreadMain() OVERRIDE { + *value_ = true; + + // Sleep for a few milliseconds so the two threads are more likely to live + // simultaneously. Otherwise we may miss the report due to mutex + // lock/unlock's inside thread creation code in pure-happens-before mode... + PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); + } + private: + bool *value_; +}; + +class ReleaseStoreThread : public PlatformThread::Delegate { + public: + explicit ReleaseStoreThread(base::subtle::Atomic32 *value) : value_(value) {} + virtual ~ReleaseStoreThread() {} + virtual void ThreadMain() OVERRIDE { + base::subtle::Release_Store(value_, kMagicValue); + + // Sleep for a few milliseconds so the two threads are more likely to live + // simultaneously. Otherwise we may miss the report due to mutex + // lock/unlock's inside thread creation code in pure-happens-before mode... + PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); + } + private: + base::subtle::Atomic32 *value_; +}; + +class AcquireLoadThread : public PlatformThread::Delegate { + public: + explicit AcquireLoadThread(base::subtle::Atomic32 *value) : value_(value) {} + virtual ~AcquireLoadThread() {} + virtual void ThreadMain() OVERRIDE { + // Wait for the other thread to make Release_Store + PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); + base::subtle::Acquire_Load(value_); + } + private: + base::subtle::Atomic32 *value_; +}; + +void RunInParallel(PlatformThread::Delegate *d1, PlatformThread::Delegate *d2) { + PlatformThreadHandle a; + PlatformThreadHandle b; + PlatformThread::Create(0, d1, &a); + PlatformThread::Create(0, d2, &b); + PlatformThread::Join(a); + PlatformThread::Join(b); +} + +} // namespace + +// A data race detector should report an error in this test. +TEST(ToolsSanityTest, DataRace) { + bool shared = false; + TOOLS_SANITY_TEST_CONCURRENT_THREAD thread1(&shared), thread2(&shared); + RunInParallel(&thread1, &thread2); + EXPECT_TRUE(shared); +} + +TEST(ToolsSanityTest, AnnotateBenignRace) { + bool shared = false; + ANNOTATE_BENIGN_RACE(&shared, "Intentional race - make sure doesn't show up"); + TOOLS_SANITY_TEST_CONCURRENT_THREAD thread1(&shared), thread2(&shared); + RunInParallel(&thread1, &thread2); + EXPECT_TRUE(shared); +} + +TEST(ToolsSanityTest, AtomicsAreIgnored) { + base::subtle::Atomic32 shared = 0; + ReleaseStoreThread thread1(&shared); + AcquireLoadThread thread2(&shared); + RunInParallel(&thread1, &thread2); + EXPECT_EQ(kMagicValue, shared); +} + +} // namespace base
diff --git a/src/base/tracked_objects.cc b/src/base/tracked_objects.cc new file mode 100644 index 0000000..9165169 --- /dev/null +++ b/src/base/tracked_objects.cc
@@ -0,0 +1,885 @@ +// 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/tracked_objects.h" + +#include <math.h> +#include <stdlib.h> + +#include "base/format_macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/process_util.h" +#include "base/profiler/alternate_timer.h" +#include "base/stringprintf.h" +#include "base/third_party/valgrind/memcheck.h" +#include "base/threading/thread_restrictions.h" +#include "base/port.h" + +using base::TimeDelta; + +namespace tracked_objects { + +namespace { +// Flag to compile out almost all of the task tracking code. +#if defined(__LB_SHELL__FOR_RELEASE__) +const bool kTrackAllTaskObjects = false; +#else +const bool kTrackAllTaskObjects = true; +#endif + +// TODO(jar): Evaluate the perf impact of enabling this. If the perf impact is +// negligible, enable by default. +// Flag to compile out parent-child link recording. +const bool kTrackParentChildLinks = false; + +// When ThreadData is first initialized, should we start in an ACTIVE state to +// record all of the startup-time tasks, or should we start up DEACTIVATED, so +// that we only record after parsing the command line flag --enable-tracking. +// Note that the flag may force either state, so this really controls only the +// period of time up until that flag is parsed. If there is no flag seen, then +// this state may prevail for much or all of the process lifetime. +const ThreadData::Status kInitialStartupState = + ThreadData::PROFILING_CHILDREN_ACTIVE; + +// Control whether an alternate time source (Now() function) is supported by +// the ThreadData class. This compile time flag should be set to true if we +// want other modules (such as a memory allocator, or a thread-specific CPU time +// clock) to be able to provide a thread-specific Now() function. Without this +// compile-time flag, the code will only support the wall-clock time. This flag +// can be flipped to efficiently disable this path (if there is a performance +// problem with its presence). +static const bool kAllowAlternateTimeSourceHandling = true; + +} // namespace + +//------------------------------------------------------------------------------ +// DeathData tallies durations when a death takes place. + +DeathData::DeathData() { + Clear(); +} + +DeathData::DeathData(int count) { + Clear(); + count_ = count; +} + +// TODO(jar): I need to see if this macro to optimize branching is worth using. +// +// This macro has no branching, so it is surely fast, and is equivalent to: +// if (assign_it) +// target = source; +// We use a macro rather than a template to force this to inline. +// Related code for calculating max is discussed on the web. +#define CONDITIONAL_ASSIGN(assign_it, target, source) \ + ((target) ^= ((target) ^ (source)) & -static_cast<int32>(assign_it)) + +void DeathData::RecordDeath(const int32 queue_duration, + const int32 run_duration, + int32 random_number) { + ++count_; + queue_duration_sum_ += queue_duration; + run_duration_sum_ += run_duration; + + if (queue_duration_max_ < queue_duration) + queue_duration_max_ = queue_duration; + if (run_duration_max_ < run_duration) + run_duration_max_ = run_duration; + + // Take a uniformly distributed sample over all durations ever supplied. + // The probability that we (instead) use this new sample is 1/count_. This + // results in a completely uniform selection of the sample (at least when we + // don't clamp count_... but that should be inconsequentially likely). + // We ignore the fact that we correlated our selection of a sample to the run + // and queue times (i.e., we used them to generate random_number). + if (count_ <= 0) { // Handle wrapping of count_, such as in bug 138961. + CHECK_GE(count_ - 1, 0); // Detect memory corruption. + // We'll just clamp at INT_MAX, but we should note this in the UI as such. + count_ = INT_MAX; + } + if (0 == (random_number % count_)) { + queue_duration_sample_ = queue_duration; + run_duration_sample_ = run_duration; + } +} + +int DeathData::count() const { return count_; } + +int32 DeathData::run_duration_sum() const { return run_duration_sum_; } + +int32 DeathData::run_duration_max() const { return run_duration_max_; } + +int32 DeathData::run_duration_sample() const { + return run_duration_sample_; +} + +int32 DeathData::queue_duration_sum() const { + return queue_duration_sum_; +} + +int32 DeathData::queue_duration_max() const { + return queue_duration_max_; +} + +int32 DeathData::queue_duration_sample() const { + return queue_duration_sample_; +} + +void DeathData::ResetMax() { + run_duration_max_ = 0; + queue_duration_max_ = 0; +} + +void DeathData::Clear() { + count_ = 0; + run_duration_sum_ = 0; + run_duration_max_ = 0; + run_duration_sample_ = 0; + queue_duration_sum_ = 0; + queue_duration_max_ = 0; + queue_duration_sample_ = 0; +} + +//------------------------------------------------------------------------------ +DeathDataSnapshot::DeathDataSnapshot() + : count(-1), + run_duration_sum(-1), + run_duration_max(-1), + run_duration_sample(-1), + queue_duration_sum(-1), + queue_duration_max(-1), + queue_duration_sample(-1) { +} + +DeathDataSnapshot::DeathDataSnapshot( + const tracked_objects::DeathData& death_data) + : count(death_data.count()), + run_duration_sum(death_data.run_duration_sum()), + run_duration_max(death_data.run_duration_max()), + run_duration_sample(death_data.run_duration_sample()), + queue_duration_sum(death_data.queue_duration_sum()), + queue_duration_max(death_data.queue_duration_max()), + queue_duration_sample(death_data.queue_duration_sample()) { +} + +DeathDataSnapshot::~DeathDataSnapshot() { +} + +//------------------------------------------------------------------------------ +BirthOnThread::BirthOnThread(const Location& location, + const ThreadData& current) + : location_(location), + birth_thread_(¤t) { +} + +//------------------------------------------------------------------------------ +BirthOnThreadSnapshot::BirthOnThreadSnapshot() { +} + +BirthOnThreadSnapshot::BirthOnThreadSnapshot( + const tracked_objects::BirthOnThread& birth) + : location(birth.location()), + thread_name(birth.birth_thread()->thread_name()) { +} + +BirthOnThreadSnapshot::~BirthOnThreadSnapshot() { +} + +//------------------------------------------------------------------------------ +Births::Births(const Location& location, const ThreadData& current) + : BirthOnThread(location, current), + birth_count_(1) { } + +int Births::birth_count() const { return birth_count_; } + +void Births::RecordBirth() { ++birth_count_; } + +void Births::ForgetBirth() { --birth_count_; } + +void Births::Clear() { birth_count_ = 0; } + +//------------------------------------------------------------------------------ +// ThreadData maintains the central data for all births and deaths on a single +// thread. + +// TODO(jar): We should pull all these static vars together, into a struct, and +// optimize layout so that we benefit from locality of reference during accesses +// to them. + +// static +NowFunction* ThreadData::now_function_ = NULL; + +// A TLS slot which points to the ThreadData instance for the current thread. We +// do a fake initialization here (zeroing out data), and then the real in-place +// construction happens when we call tls_index_.Initialize(). +// static +base::ThreadLocalStorage::StaticSlot ThreadData::tls_index_ = TLS_INITIALIZER; + +// static +int ThreadData::worker_thread_data_creation_count_ = 0; + +// static +int ThreadData::cleanup_count_ = 0; + +// static +int ThreadData::incarnation_counter_ = 0; + +// static +ThreadData* ThreadData::all_thread_data_list_head_ = NULL; + +// static +ThreadData* ThreadData::first_retired_worker_ = NULL; + +// static +base::LazyInstance<base::Lock>::Leaky + ThreadData::list_lock_ = LAZY_INSTANCE_INITIALIZER; + +// static +ThreadData::Status ThreadData::status_ = ThreadData::UNINITIALIZED; + +ThreadData::ThreadData(const std::string& suggested_name) + : next_(NULL), + next_retired_worker_(NULL), + worker_thread_number_(0), + incarnation_count_for_pool_(-1) { + DCHECK_GE(suggested_name.size(), 0u); + thread_name_ = suggested_name; + PushToHeadOfList(); // Which sets real incarnation_count_for_pool_. +} + +ThreadData::ThreadData(int thread_number) + : next_(NULL), + next_retired_worker_(NULL), + worker_thread_number_(thread_number), + incarnation_count_for_pool_(-1) { + CHECK_GT(thread_number, 0); + base::StringAppendF(&thread_name_, "WorkerThread-%d", thread_number); + PushToHeadOfList(); // Which sets real incarnation_count_for_pool_. +} + +ThreadData::~ThreadData() {} + +void ThreadData::PushToHeadOfList() { + // Toss in a hint of randomness (atop the uniniitalized value). + (void)VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE(&random_number_, + sizeof(random_number_)); + random_number_ += static_cast<int32>(this - static_cast<ThreadData*>(0)); + random_number_ ^= (Now() - TrackedTime()).InMilliseconds(); + + DCHECK(!next_); + base::AutoLock lock(*list_lock_.Pointer()); + incarnation_count_for_pool_ = incarnation_counter_; + next_ = all_thread_data_list_head_; + all_thread_data_list_head_ = this; +} + +// static +ThreadData* ThreadData::first() { + base::AutoLock lock(*list_lock_.Pointer()); + return all_thread_data_list_head_; +} + +ThreadData* ThreadData::next() const { return next_; } + +// static +void ThreadData::InitializeThreadContext(const std::string& suggested_name) { + if (!Initialize()) // Always initialize if needed. + return; + ThreadData* current_thread_data = + reinterpret_cast<ThreadData*>(tls_index_.Get()); + if (current_thread_data) + return; // Browser tests instigate this. + current_thread_data = new ThreadData(suggested_name); + tls_index_.Set(current_thread_data); +} + +// static +ThreadData* ThreadData::Get() { + if (!tls_index_.initialized()) + return NULL; // For unittests only. + ThreadData* registered = reinterpret_cast<ThreadData*>(tls_index_.Get()); + if (registered) + return registered; + + // We must be a worker thread, since we didn't pre-register. + ThreadData* worker_thread_data = NULL; + int worker_thread_number = 0; + { + base::AutoLock lock(*list_lock_.Pointer()); + if (first_retired_worker_) { + worker_thread_data = first_retired_worker_; + first_retired_worker_ = first_retired_worker_->next_retired_worker_; + worker_thread_data->next_retired_worker_ = NULL; + } else { + worker_thread_number = ++worker_thread_data_creation_count_; + } + } + + // If we can't find a previously used instance, then we have to create one. + if (!worker_thread_data) { + DCHECK_GT(worker_thread_number, 0); + worker_thread_data = new ThreadData(worker_thread_number); + } + DCHECK_GT(worker_thread_data->worker_thread_number_, 0); + + tls_index_.Set(worker_thread_data); + return worker_thread_data; +} + +// static +void ThreadData::OnThreadTermination(void* thread_data) { + DCHECK(thread_data); // TLS should *never* call us with a NULL. + // We must NOT do any allocations during this callback. There is a chance + // that the allocator is no longer active on this thread. + if (!kTrackAllTaskObjects) + return; // Not compiled in. + reinterpret_cast<ThreadData*>(thread_data)->OnThreadTerminationCleanup(); +} + +void ThreadData::OnThreadTerminationCleanup() { +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) + // The Chromium code avoids doing allocations here and puts ThreadDatas + // into a linked list for reuse. + // However, every call to InitializeThreadContext() will create + // a new ThreadData without any possibility of reuse. + // We prefer to delete the ThreadData to avoid leaking mutexes and memory. + delete this; +#else + // The list_lock_ was created when we registered the callback, so it won't be + // allocated here despite the lazy reference. + base::AutoLock lock(*list_lock_.Pointer()); + if (incarnation_counter_ != incarnation_count_for_pool_) + return; // ThreadData was constructed in an earlier unit test. + ++cleanup_count_; + // Only worker threads need to be retired and reused. + if (!worker_thread_number_) { + return; + } + // We must NOT do any allocations during this callback. + // Using the simple linked lists avoids all allocations. + DCHECK_EQ(this->next_retired_worker_, reinterpret_cast<ThreadData*>(NULL)); + this->next_retired_worker_ = first_retired_worker_; + first_retired_worker_ = this; +#endif +} + +// static +void ThreadData::Snapshot(bool reset_max, ProcessDataSnapshot* process_data) { + // Add births that have run to completion to |collected_data|. + // |birth_counts| tracks the total number of births recorded at each location + // for which we have not seen a death count. + BirthCountMap birth_counts; + ThreadData::SnapshotAllExecutedTasks(reset_max, process_data, &birth_counts); + + // Add births that are still active -- i.e. objects that have tallied a birth, + // but have not yet tallied a matching death, and hence must be either + // running, queued up, or being held in limbo for future posting. + for (BirthCountMap::const_iterator it = birth_counts.begin(); + it != birth_counts.end(); ++it) { + if (it->second > 0) { + process_data->tasks.push_back( + TaskSnapshot(*it->first, DeathData(it->second), "Still_Alive")); + } + } +} + +Births* ThreadData::TallyABirth(const Location& location) { + BirthMap::iterator it = birth_map_.find(location); + Births* child; + if (it != birth_map_.end()) { + child = it->second; + child->RecordBirth(); + } else { + child = new Births(location, *this); // Leak this. + // Lock since the map may get relocated now, and other threads sometimes + // snapshot it (but they lock before copying it). + base::AutoLock lock(map_lock_); + birth_map_[location] = child; + } + + if (kTrackParentChildLinks && status_ > PROFILING_ACTIVE && + !parent_stack_.empty()) { + const Births* parent = parent_stack_.top(); + ParentChildPair pair(parent, child); + if (parent_child_set_.find(pair) == parent_child_set_.end()) { + // Lock since the map may get relocated now, and other threads sometimes + // snapshot it (but they lock before copying it). + base::AutoLock lock(map_lock_); + parent_child_set_.insert(pair); + } + } + + return child; +} + +void ThreadData::TallyADeath(const Births& birth, + int32 queue_duration, + int32 run_duration) { + // Stir in some randomness, plus add constant in case durations are zero. + const int32 kSomePrimeNumber = 2147483647; + random_number_ += queue_duration + run_duration + kSomePrimeNumber; + // An address is going to have some randomness to it as well ;-). + random_number_ ^= static_cast<int32>(&birth - reinterpret_cast<Births*>(0)); + + // We don't have queue durations without OS timer. OS timer is automatically + // used for task-post-timing, so the use of an alternate timer implies all + // queue times are invalid. + if (kAllowAlternateTimeSourceHandling && now_function_) + queue_duration = 0; + + DeathMap::iterator it = death_map_.find(&birth); + DeathData* death_data; + if (it != death_map_.end()) { + death_data = &it->second; + } else { + base::AutoLock lock(map_lock_); // Lock as the map may get relocated now. + death_data = &death_map_[&birth]; + } // Release lock ASAP. + death_data->RecordDeath(queue_duration, run_duration, random_number_); + + if (!kTrackParentChildLinks) + return; + if (!parent_stack_.empty()) { // We might get turned off. + DCHECK_EQ(parent_stack_.top(), &birth); + parent_stack_.pop(); + } +} + +// static +Births* ThreadData::TallyABirthIfActive(const Location& location) { + if (!kTrackAllTaskObjects) + return NULL; // Not compiled in. + + if (!TrackingStatus()) + return NULL; + ThreadData* current_thread_data = Get(); + if (!current_thread_data) + return NULL; + return current_thread_data->TallyABirth(location); +} + +// static +void ThreadData::TallyRunOnNamedThreadIfTracking( + const base::TrackingInfo& completed_task, + const TrackedTime& start_of_run, + const TrackedTime& end_of_run) { + if (!kTrackAllTaskObjects) + return; // Not compiled in. + + // Even if we have been DEACTIVATED, we will process any pending births so + // that our data structures (which counted the outstanding births) remain + // consistent. + const Births* birth = completed_task.birth_tally; + if (!birth) + return; + ThreadData* current_thread_data = Get(); + if (!current_thread_data) + return; + + // To avoid conflating our stats with the delay duration in a PostDelayedTask, + // we identify such tasks, and replace their post_time with the time they + // were scheduled (requested?) to emerge from the delayed task queue. This + // means that queueing delay for such tasks will show how long they went + // unserviced, after they *could* be serviced. This is the same stat as we + // have for non-delayed tasks, and we consistently call it queueing delay. + TrackedTime effective_post_time = completed_task.delayed_run_time.is_null() + ? tracked_objects::TrackedTime(completed_task.time_posted) + : tracked_objects::TrackedTime(completed_task.delayed_run_time); + + // Watch out for a race where status_ is changing, and hence one or both + // of start_of_run or end_of_run is zero. In that case, we didn't bother to + // get a time value since we "weren't tracking" and we were trying to be + // efficient by not calling for a genuine time value. For simplicity, we'll + // use a default zero duration when we can't calculate a true value. + int32 queue_duration = 0; + int32 run_duration = 0; + if (!start_of_run.is_null()) { + queue_duration = (start_of_run - effective_post_time).InMilliseconds(); + if (!end_of_run.is_null()) + run_duration = (end_of_run - start_of_run).InMilliseconds(); + } + current_thread_data->TallyADeath(*birth, queue_duration, run_duration); +} + +// static +void ThreadData::TallyRunOnWorkerThreadIfTracking( + const Births* birth, + const TrackedTime& time_posted, + const TrackedTime& start_of_run, + const TrackedTime& end_of_run) { + if (!kTrackAllTaskObjects) + return; // Not compiled in. + + // Even if we have been DEACTIVATED, we will process any pending births so + // that our data structures (which counted the outstanding births) remain + // consistent. + if (!birth) + return; + + // TODO(jar): Support the option to coalesce all worker-thread activity under + // one ThreadData instance that uses locks to protect *all* access. This will + // reduce memory (making it provably bounded), but run incrementally slower + // (since we'll use locks on TallyBirth and TallyDeath). The good news is + // that the locks on TallyDeath will be *after* the worker thread has run, and + // hence nothing will be waiting for the completion (... besides some other + // thread that might like to run). Also, the worker threads tasks are + // generally longer, and hence the cost of the lock may perchance be amortized + // over the long task's lifetime. + ThreadData* current_thread_data = Get(); + if (!current_thread_data) + return; + + int32 queue_duration = 0; + int32 run_duration = 0; + if (!start_of_run.is_null()) { + queue_duration = (start_of_run - time_posted).InMilliseconds(); + if (!end_of_run.is_null()) + run_duration = (end_of_run - start_of_run).InMilliseconds(); + } + current_thread_data->TallyADeath(*birth, queue_duration, run_duration); +} + +// static +void ThreadData::TallyRunInAScopedRegionIfTracking( + const Births* birth, + const TrackedTime& start_of_run, + const TrackedTime& end_of_run) { + if (!kTrackAllTaskObjects) + return; // Not compiled in. + + // Even if we have been DEACTIVATED, we will process any pending births so + // that our data structures (which counted the outstanding births) remain + // consistent. + if (!birth) + return; + + ThreadData* current_thread_data = Get(); + if (!current_thread_data) + return; + + int32 queue_duration = 0; + int32 run_duration = 0; + if (!start_of_run.is_null() && !end_of_run.is_null()) + run_duration = (end_of_run - start_of_run).InMilliseconds(); + current_thread_data->TallyADeath(*birth, queue_duration, run_duration); +} + +// static +void ThreadData::SnapshotAllExecutedTasks(bool reset_max, + ProcessDataSnapshot* process_data, + BirthCountMap* birth_counts) { + if (!kTrackAllTaskObjects) + return; // Not compiled in. + + // Get an unchanging copy of a ThreadData list. + ThreadData* my_list = ThreadData::first(); + + // Gather data serially. + // This hackish approach *can* get some slighly corrupt tallies, as we are + // grabbing values without the protection of a lock, but it has the advantage + // of working even with threads that don't have message loops. If a user + // sees any strangeness, they can always just run their stats gathering a + // second time. + for (ThreadData* thread_data = my_list; + thread_data; + thread_data = thread_data->next()) { + thread_data->SnapshotExecutedTasks(reset_max, process_data, birth_counts); + } +} + +void ThreadData::SnapshotExecutedTasks(bool reset_max, + ProcessDataSnapshot* process_data, + BirthCountMap* birth_counts) { + // Get copy of data, so that the data will not change during the iterations + // and processing. + ThreadData::BirthMap birth_map; + ThreadData::DeathMap death_map; + ThreadData::ParentChildSet parent_child_set; + SnapshotMaps(reset_max, &birth_map, &death_map, &parent_child_set); + + for (ThreadData::DeathMap::const_iterator it = death_map.begin(); + it != death_map.end(); ++it) { + process_data->tasks.push_back( + TaskSnapshot(*it->first, it->second, thread_name())); + (*birth_counts)[it->first] -= it->first->birth_count(); + } + + for (ThreadData::BirthMap::const_iterator it = birth_map.begin(); + it != birth_map.end(); ++it) { + (*birth_counts)[it->second] += it->second->birth_count(); + } + + if (!kTrackParentChildLinks) + return; + + for (ThreadData::ParentChildSet::const_iterator it = parent_child_set.begin(); + it != parent_child_set.end(); ++it) { + process_data->descendants.push_back(ParentChildPairSnapshot(*it)); + } +} + +// This may be called from another thread. +void ThreadData::SnapshotMaps(bool reset_max, + BirthMap* birth_map, + DeathMap* death_map, + ParentChildSet* parent_child_set) { + base::AutoLock lock(map_lock_); + for (BirthMap::const_iterator it = birth_map_.begin(); + it != birth_map_.end(); ++it) + (*birth_map)[it->first] = it->second; + for (DeathMap::iterator it = death_map_.begin(); + it != death_map_.end(); ++it) { + (*death_map)[it->first] = it->second; + if (reset_max) + it->second.ResetMax(); + } + + if (!kTrackParentChildLinks) + return; + + for (ParentChildSet::iterator it = parent_child_set_.begin(); + it != parent_child_set_.end(); ++it) + parent_child_set->insert(*it); +} + +// static +void ThreadData::ResetAllThreadData() { + ThreadData* my_list = first(); + + for (ThreadData* thread_data = my_list; + thread_data; + thread_data = thread_data->next()) + thread_data->Reset(); +} + +void ThreadData::Reset() { + base::AutoLock lock(map_lock_); + for (DeathMap::iterator it = death_map_.begin(); + it != death_map_.end(); ++it) + it->second.Clear(); + for (BirthMap::iterator it = birth_map_.begin(); + it != birth_map_.end(); ++it) + it->second->Clear(); +} + +static void OptionallyInitializeAlternateTimer() { + NowFunction* alternate_time_source = GetAlternateTimeSource(); + if (alternate_time_source) + ThreadData::SetAlternateTimeSource(alternate_time_source); +} + +bool ThreadData::Initialize() { + if (!kTrackAllTaskObjects) + return false; // Not compiled in. + if (status_ >= DEACTIVATED) + return true; // Someone else did the initialization. + // Due to racy lazy initialization in tests, we'll need to recheck status_ + // after we acquire the lock. + + // Ensure that we don't double initialize tls. We are called when single + // threaded in the product, but some tests may be racy and lazy about our + // initialization. + base::AutoLock lock(*list_lock_.Pointer()); + if (status_ >= DEACTIVATED) + return true; // Someone raced in here and beat us. + + // Put an alternate timer in place if the environment calls for it, such as + // for tracking TCMalloc allocations. This insertion is idempotent, so we + // don't mind if there is a race, and we'd prefer not to be in a lock while + // doing this work. + if (kAllowAlternateTimeSourceHandling) + OptionallyInitializeAlternateTimer(); + + // Perform the "real" TLS initialization now, and leave it intact through + // process termination. + if (!tls_index_.initialized()) { // Testing may have initialized this. + DCHECK_EQ(status_, UNINITIALIZED); + tls_index_.Initialize(&ThreadData::OnThreadTermination); + if (!tls_index_.initialized()) + return false; + } else { + // TLS was initialzed for us earlier. + DCHECK_EQ(status_, DORMANT_DURING_TESTS); + } + + // Incarnation counter is only significant to testing, as it otherwise will + // never again change in this process. + ++incarnation_counter_; + + // The lock is not critical for setting status_, but it doesn't hurt. It also + // ensures that if we have a racy initialization, that we'll bail as soon as + // we get the lock earlier in this method. + status_ = kInitialStartupState; + if (!kTrackParentChildLinks && + kInitialStartupState == PROFILING_CHILDREN_ACTIVE) + status_ = PROFILING_ACTIVE; + DCHECK(status_ != UNINITIALIZED); + return true; +} + +// static +bool ThreadData::InitializeAndSetTrackingStatus(Status status) { + DCHECK_GE(status, DEACTIVATED); + DCHECK_LE(status, PROFILING_CHILDREN_ACTIVE); + + if (!Initialize()) // No-op if already initialized. + return false; // Not compiled in. + + if (!kTrackParentChildLinks && status > DEACTIVATED) + status = PROFILING_ACTIVE; + status_ = status; + return true; +} + +// static +ThreadData::Status ThreadData::status() { + return status_; +} + +// static +bool ThreadData::TrackingStatus() { + return status_ > DEACTIVATED; +} + +// static +bool ThreadData::TrackingParentChildStatus() { + return status_ >= PROFILING_CHILDREN_ACTIVE; +} + +// static +TrackedTime ThreadData::NowForStartOfRun(const Births* parent) { + if (kTrackParentChildLinks && parent && status_ > PROFILING_ACTIVE) { + ThreadData* current_thread_data = Get(); + if (current_thread_data) + current_thread_data->parent_stack_.push(parent); + } + return Now(); +} + +// static +TrackedTime ThreadData::NowForEndOfRun() { + return Now(); +} + +// static +void ThreadData::SetAlternateTimeSource(NowFunction* now_function) { + DCHECK(now_function); + if (kAllowAlternateTimeSourceHandling) + now_function_ = now_function; +} + +// static +TrackedTime ThreadData::Now() { + if (kAllowAlternateTimeSourceHandling && now_function_) + return TrackedTime::FromMilliseconds((*now_function_)()); + if (kTrackAllTaskObjects && TrackingStatus()) + return TrackedTime::Now(); + return TrackedTime(); // Super fast when disabled, or not compiled. +} + +// static +void ThreadData::EnsureCleanupWasCalled(int major_threads_shutdown_count) { + base::AutoLock lock(*list_lock_.Pointer()); + if (worker_thread_data_creation_count_ == 0) + return; // We haven't really run much, and couldn't have leaked. + // Verify that we've at least shutdown/cleanup the major namesd threads. The + // caller should tell us how many thread shutdowns should have taken place by + // now. + return; // TODO(jar): until this is working on XP, don't run the real test. + CHECK_GT(cleanup_count_, major_threads_shutdown_count); +} + +// static +void ThreadData::ShutdownSingleThreadedCleanup(bool leak) { + // This is only called from test code, where we need to cleanup so that + // additional tests can be run. + // We must be single threaded... but be careful anyway. + if (!InitializeAndSetTrackingStatus(DEACTIVATED)) + return; + ThreadData* thread_data_list; + { + base::AutoLock lock(*list_lock_.Pointer()); + thread_data_list = all_thread_data_list_head_; + all_thread_data_list_head_ = NULL; + ++incarnation_counter_; + // To be clean, break apart the retired worker list (though we leak them). + while (first_retired_worker_) { + ThreadData* worker = first_retired_worker_; + CHECK_GT(worker->worker_thread_number_, 0); + first_retired_worker_ = worker->next_retired_worker_; + worker->next_retired_worker_ = NULL; + } + } + + // Put most global static back in pristine shape. + worker_thread_data_creation_count_ = 0; + cleanup_count_ = 0; + tls_index_.Set(NULL); + status_ = DORMANT_DURING_TESTS; // Almost UNINITIALIZED. + + // To avoid any chance of racing in unit tests, which is the only place we + // call this function, we may sometimes leak all the data structures we + // recovered, as they may still be in use on threads from prior tests! + if (leak) + return; + + // When we want to cleanup (on a single thread), here is what we do. + + // Do actual recursive delete in all ThreadData instances. + while (thread_data_list) { + ThreadData* next_thread_data = thread_data_list; + thread_data_list = thread_data_list->next(); + + for (BirthMap::iterator it = next_thread_data->birth_map_.begin(); + next_thread_data->birth_map_.end() != it; ++it) + delete it->second; // Delete the Birth Records. + delete next_thread_data; // Includes all Death Records. + } +} + +//------------------------------------------------------------------------------ +TaskSnapshot::TaskSnapshot() { +} + +TaskSnapshot::TaskSnapshot(const BirthOnThread& birth, + const DeathData& death_data, + const std::string& death_thread_name) + : birth(birth), + death_data(death_data), + death_thread_name(death_thread_name) { +} + +TaskSnapshot::~TaskSnapshot() { +} + +//------------------------------------------------------------------------------ +// ParentChildPairSnapshot + +ParentChildPairSnapshot::ParentChildPairSnapshot() { +} + +ParentChildPairSnapshot::ParentChildPairSnapshot( + const ThreadData::ParentChildPair& parent_child) + : parent(*parent_child.first), + child(*parent_child.second) { +} + +ParentChildPairSnapshot::~ParentChildPairSnapshot() { +} + +//------------------------------------------------------------------------------ +// ProcessDataSnapshot + +ProcessDataSnapshot::ProcessDataSnapshot() +#if !defined(OS_NACL) + : process_id(base::GetCurrentProcId()) { +#else + : process_id(0) { +#endif +} + +ProcessDataSnapshot::~ProcessDataSnapshot() { +} + +} // namespace tracked_objects
diff --git a/src/base/tracked_objects.h b/src/base/tracked_objects.h new file mode 100644 index 0000000..2bcb94a --- /dev/null +++ b/src/base/tracked_objects.h
@@ -0,0 +1,718 @@ +// 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_TRACKED_OBJECTS_H_ +#define BASE_TRACKED_OBJECTS_H_ + +#include <map> +#include <set> +#include <stack> +#include <string> +#include <utility> +#include <vector> + +#include "base/base_export.h" +#include "base/gtest_prod_util.h" +#include "base/lazy_instance.h" +#include "base/location.h" +#include "base/profiler/alternate_timer.h" +#include "base/profiler/tracked_time.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_local_storage.h" +#include "base/tracking_info.h" + +// TrackedObjects provides a database of stats about objects (generally Tasks) +// that are tracked. Tracking means their birth, death, duration, birth thread, +// death thread, and birth place are recorded. This data is carefully spread +// across a series of objects so that the counts and times can be rapidly +// updated without (usually) having to lock the data, and hence there is usually +// very little contention caused by the tracking. The data can be viewed via +// the about:profiler URL, with a variety of sorting and filtering choices. +// +// These classes serve as the basis of a profiler of sorts for the Tasks system. +// As a result, design decisions were made to maximize speed, by minimizing +// recurring allocation/deallocation, lock contention and data copying. In the +// "stable" state, which is reached relatively quickly, there is no separate +// marginal allocation cost associated with construction or destruction of +// tracked objects, no locks are generally employed, and probably the largest +// computational cost is associated with obtaining start and stop times for +// instances as they are created and destroyed. +// +// The following describes the lifecycle of tracking an instance. +// +// First off, when the instance is created, the FROM_HERE macro is expanded +// to specify the birth place (file, line, function) where the instance was +// created. That data is used to create a transient Location instance +// encapsulating the above triple of information. The strings (like __FILE__) +// are passed around by reference, with the assumption that they are static, and +// will never go away. This ensures that the strings can be dealt with as atoms +// with great efficiency (i.e., copying of strings is never needed, and +// comparisons for equality can be based on pointer comparisons). +// +// Next, a Births instance is created for use ONLY on the thread where this +// instance was created. That Births instance records (in a base class +// BirthOnThread) references to the static data provided in a Location instance, +// as well as a pointer specifying the thread on which the birth takes place. +// Hence there is at most one Births instance for each Location on each thread. +// The derived Births class contains slots for recording statistics about all +// instances born at the same location. Statistics currently include only the +// count of instances constructed. +// +// Since the base class BirthOnThread contains only constant data, it can be +// freely accessed by any thread at any time (i.e., only the statistic needs to +// be handled carefully, and stats are updated exclusively on the birth thread). +// +// For Tasks, having now either constructed or found the Births instance +// described above, a pointer to the Births instance is then recorded into the +// PendingTask structure in MessageLoop. This fact alone is very useful in +// debugging, when there is a question of where an instance came from. In +// addition, the birth time is also recorded and used to later evaluate the +// lifetime duration of the whole Task. As a result of the above embedding, we +// can find out a Task's location of birth, and thread of birth, without using +// any locks, as all that data is constant across the life of the process. +// +// The above work *could* also be done for any other object as well by calling +// TallyABirthIfActive() and TallyRunOnNamedThreadIfTracking() as appropriate. +// +// The amount of memory used in the above data structures depends on how many +// threads there are, and how many Locations of construction there are. +// Fortunately, we don't use memory that is the product of those two counts, but +// rather we only need one Births instance for each thread that constructs an +// instance at a Location. In many cases, instances are only created on one +// thread, so the memory utilization is actually fairly restrained. +// +// Lastly, when an instance is deleted, the final tallies of statistics are +// carefully accumulated. That tallying writes into slots (members) in a +// collection of DeathData instances. For each birth place Location that is +// destroyed on a thread, there is a DeathData instance to record the additional +// death count, as well as accumulate the run-time and queue-time durations for +// the instance as it is destroyed (dies). By maintaining a single place to +// aggregate this running sum *only* for the given thread, we avoid the need to +// lock such DeathData instances. (i.e., these accumulated stats in a DeathData +// instance are exclusively updated by the singular owning thread). +// +// With the above lifecycle description complete, the major remaining detail is +// explaining how each thread maintains a list of DeathData instances, and of +// Births instances, and is able to avoid additional (redundant/unnecessary) +// allocations. +// +// Each thread maintains a list of data items specific to that thread in a +// ThreadData instance (for that specific thread only). The two critical items +// are lists of DeathData and Births instances. These lists are maintained in +// STL maps, which are indexed by Location. As noted earlier, we can compare +// locations very efficiently as we consider the underlying data (file, +// function, line) to be atoms, and hence pointer comparison is used rather than +// (slow) string comparisons. +// +// To provide a mechanism for iterating over all "known threads," which means +// threads that have recorded a birth or a death, we create a singly linked list +// of ThreadData instances. Each such instance maintains a pointer to the next +// one. A static member of ThreadData provides a pointer to the first item on +// this global list, and access via that all_thread_data_list_head_ item +// requires the use of the list_lock_. +// When new ThreadData instances is added to the global list, it is pre-pended, +// which ensures that any prior acquisition of the list is valid (i.e., the +// holder can iterate over it without fear of it changing, or the necessity of +// using an additional lock. Iterations are actually pretty rare (used +// primarilly for cleanup, or snapshotting data for display), so this lock has +// very little global performance impact. +// +// The above description tries to define the high performance (run time) +// portions of these classes. After gathering statistics, calls instigated +// by visiting about:profiler will assemble and aggregate data for display. The +// following data structures are used for producing such displays. They are +// not performance critical, and their only major constraint is that they should +// be able to run concurrently with ongoing augmentation of the birth and death +// data. +// +// This header also exports collection of classes that provide "snapshotted" +// representations of the core tracked_objects:: classes. These snapshotted +// representations are designed for safe transmission of the tracked_objects:: +// data across process boundaries. Each consists of: +// (1) a default constructor, to support the IPC serialization macros, +// (2) a constructor that extracts data from the type being snapshotted, and +// (3) the snapshotted data. +// +// For a given birth location, information about births is spread across data +// structures that are asynchronously changing on various threads. For +// serialization and display purposes, we need to construct TaskSnapshot +// instances for each combination of birth thread, death thread, and location, +// along with the count of such lifetimes. We gather such data into a +// TaskSnapshot instances, so that such instances can be sorted and +// aggregated (and remain frozen during our processing). +// +// The ProcessDataSnapshot struct is a serialized representation of the list +// of ThreadData objects for a process. It holds a set of TaskSnapshots +// and tracks parent/child relationships for the executed tasks. The statistics +// in a snapshot are gathered asynhcronously relative to their ongoing updates. +// It is possible, though highly unlikely, that stats could be incorrectly +// recorded by this process (all data is held in 32 bit ints, but we are not +// atomically collecting all data, so we could have count that does not, for +// example, match with the number of durations we accumulated). The advantage +// to having fast (non-atomic) updates of the data outweighs the minimal risk of +// a singular corrupt statistic snapshot (only the snapshot could be corrupt, +// not the underlying and ongoing statistic). In constrast, pointer data that +// is accessed during snapshotting is completely invariant, and hence is +// perfectly acquired (i.e., no potential corruption, and no risk of a bad +// memory reference). +// +// TODO(jar): We can implement a Snapshot system that *tries* to grab the +// snapshots on the source threads *when* they have MessageLoops available +// (worker threads don't have message loops generally, and hence gathering from +// them will continue to be asynchronous). We had an implementation of this in +// the past, but the difficulty is dealing with message loops being terminated. +// We can *try* to spam the available threads via some message loop proxy to +// achieve this feat, and it *might* be valuable when we are colecting data for +// upload via UMA (where correctness of data may be more significant than for a +// single screen of about:profiler). +// +// TODO(jar): We should support (optionally) the recording of parent-child +// relationships for tasks. This should be done by detecting what tasks are +// Born during the running of a parent task. The resulting data can be used by +// a smarter profiler to aggregate the cost of a series of child tasks into +// the ancestor task. It can also be used to illuminate what child or parent is +// related to each task. +// +// TODO(jar): We need to store DataCollections, and provide facilities for +// taking the difference between two gathered DataCollections. For now, we're +// just adding a hack that Reset()s to zero all counts and stats. This is also +// done in a slighly thread-unsafe fashion, as the resetting is done +// asynchronously relative to ongoing updates (but all data is 32 bit in size). +// For basic profiling, this will work "most of the time," and should be +// sufficient... but storing away DataCollections is the "right way" to do this. +// We'll accomplish this via JavaScript storage of snapshots, and then we'll +// remove the Reset() methods. We may also need a short-term-max value in +// DeathData that is reset (as synchronously as possible) during each snapshot. +// This will facilitate displaying a max value for each snapshot period. + +namespace tracked_objects { + +//------------------------------------------------------------------------------ +// For a specific thread, and a specific birth place, the collection of all +// death info (with tallies for each death thread, to prevent access conflicts). +class ThreadData; +class BASE_EXPORT BirthOnThread { + public: + BirthOnThread(const Location& location, const ThreadData& current); + + const Location location() const { return location_; } + const ThreadData* birth_thread() const { return birth_thread_; } + + private: + // File/lineno of birth. This defines the essence of the task, as the context + // of the birth (construction) often tell what the item is for. This field + // is const, and hence safe to access from any thread. + const Location location_; + + // The thread that records births into this object. Only this thread is + // allowed to update birth_count_ (which changes over time). + const ThreadData* const birth_thread_; + + DISALLOW_COPY_AND_ASSIGN(BirthOnThread); +}; + +//------------------------------------------------------------------------------ +// A "snapshotted" representation of the BirthOnThread class. + +struct BASE_EXPORT BirthOnThreadSnapshot { + BirthOnThreadSnapshot(); + explicit BirthOnThreadSnapshot(const BirthOnThread& birth); + ~BirthOnThreadSnapshot(); + + LocationSnapshot location; + std::string thread_name; +}; + +//------------------------------------------------------------------------------ +// A class for accumulating counts of births (without bothering with a map<>). + +class BASE_EXPORT Births: public BirthOnThread { + public: + Births(const Location& location, const ThreadData& current); + + int birth_count() const; + + // When we have a birth we update the count for this birthplace. + void RecordBirth(); + + // When a birthplace is changed (updated), we need to decrement the counter + // for the old instance. + void ForgetBirth(); + + // Hack to quickly reset all counts to zero. + void Clear(); + + private: + // The number of births on this thread for our location_. + int birth_count_; + + DISALLOW_COPY_AND_ASSIGN(Births); +}; + +//------------------------------------------------------------------------------ +// Basic info summarizing multiple destructions of a tracked object with a +// single birthplace (fixed Location). Used both on specific threads, and also +// in snapshots when integrating assembled data. + +class BASE_EXPORT DeathData { + public: + // Default initializer. + DeathData(); + + // When deaths have not yet taken place, and we gather data from all the + // threads, we create DeathData stats that tally the number of births without + // a corresponding death. + explicit DeathData(int count); + + // Update stats for a task destruction (death) that had a Run() time of + // |duration|, and has had a queueing delay of |queue_duration|. + void RecordDeath(const int32 queue_duration, + const int32 run_duration, + int random_number); + + // Metrics accessors, used only for serialization and in tests. + int count() const; + int32 run_duration_sum() const; + int32 run_duration_max() const; + int32 run_duration_sample() const; + int32 queue_duration_sum() const; + int32 queue_duration_max() const; + int32 queue_duration_sample() const; + + // Reset the max values to zero. + void ResetMax(); + + // Reset all tallies to zero. This is used as a hack on realtime data. + void Clear(); + + private: + // Members are ordered from most regularly read and updated, to least + // frequently used. This might help a bit with cache lines. + // Number of runs seen (divisor for calculating averages). + int count_; + // Basic tallies, used to compute averages. + int32 run_duration_sum_; + int32 queue_duration_sum_; + // Max values, used by local visualization routines. These are often read, + // but rarely updated. + int32 run_duration_max_; + int32 queue_duration_max_; + // Samples, used by crowd sourcing gatherers. These are almost never read, + // and rarely updated. + int32 run_duration_sample_; + int32 queue_duration_sample_; +}; + +//------------------------------------------------------------------------------ +// A "snapshotted" representation of the DeathData class. + +struct BASE_EXPORT DeathDataSnapshot { + DeathDataSnapshot(); + explicit DeathDataSnapshot(const DeathData& death_data); + ~DeathDataSnapshot(); + + int count; + int32 run_duration_sum; + int32 run_duration_max; + int32 run_duration_sample; + int32 queue_duration_sum; + int32 queue_duration_max; + int32 queue_duration_sample; +}; + +//------------------------------------------------------------------------------ +// A temporary collection of data that can be sorted and summarized. It is +// gathered (carefully) from many threads. Instances are held in arrays and +// processed, filtered, and rendered. +// The source of this data was collected on many threads, and is asynchronously +// changing. The data in this instance is not asynchronously changing. + +struct BASE_EXPORT TaskSnapshot { + TaskSnapshot(); + TaskSnapshot(const BirthOnThread& birth, + const DeathData& death_data, + const std::string& death_thread_name); + ~TaskSnapshot(); + + BirthOnThreadSnapshot birth; + DeathDataSnapshot death_data; + std::string death_thread_name; +}; + +//------------------------------------------------------------------------------ +// For each thread, we have a ThreadData that stores all tracking info generated +// on this thread. This prevents the need for locking as data accumulates. +// We use ThreadLocalStorage to quickly identfy the current ThreadData context. +// We also have a linked list of ThreadData instances, and that list is used to +// harvest data from all existing instances. + +struct ProcessDataSnapshot; +class BASE_EXPORT ThreadData { + public: + // Current allowable states of the tracking system. The states can vary + // between ACTIVE and DEACTIVATED, but can never go back to UNINITIALIZED. + enum Status { + UNINITIALIZED, // PRistine, link-time state before running. + DORMANT_DURING_TESTS, // Only used during testing. + DEACTIVATED, // No longer recording profling. + PROFILING_ACTIVE, // Recording profiles (no parent-child links). + PROFILING_CHILDREN_ACTIVE, // Fully active, recording parent-child links. + }; + + typedef std::map<Location, Births*> BirthMap; + typedef std::map<const Births*, DeathData> DeathMap; + typedef std::pair<const Births*, const Births*> ParentChildPair; + typedef std::set<ParentChildPair> ParentChildSet; + typedef std::stack<const Births*> ParentStack; + + // Initialize the current thread context with a new instance of ThreadData. + // This is used by all threads that have names, and should be explicitly + // set *before* any births on the threads have taken place. It is generally + // only used by the message loop, which has a well defined thread name. + static void InitializeThreadContext(const std::string& suggested_name); + + // Using Thread Local Store, find the current instance for collecting data. + // If an instance does not exist, construct one (and remember it for use on + // this thread. + // This may return NULL if the system is disabled for any reason. + static ThreadData* Get(); + + // Fills |process_data| with all the recursive results in our process. + // During the scavenging, if |reset_max| is true, then the DeathData instances + // max-values are reset to zero during this scan. + static void Snapshot(bool reset_max, ProcessDataSnapshot* process_data); + + // Finds (or creates) a place to count births from the given location in this + // thread, and increment that tally. + // TallyABirthIfActive will returns NULL if the birth cannot be tallied. + static Births* TallyABirthIfActive(const Location& location); + + // Records the end of a timed run of an object. The |completed_task| contains + // a pointer to a Births, the time_posted, and a delayed_start_time if any. + // The |start_of_run| indicates when we started to perform the run of the + // task. The delayed_start_time is non-null for tasks that were posted as + // delayed tasks, and it indicates when the task should have run (i.e., when + // it should have posted out of the timer queue, and into the work queue. + // The |end_of_run| was just obtained by a call to Now() (just after the task + // finished). It is provided as an argument to help with testing. + static void TallyRunOnNamedThreadIfTracking( + const base::TrackingInfo& completed_task, + const TrackedTime& start_of_run, + const TrackedTime& end_of_run); + + // Record the end of a timed run of an object. The |birth| is the record for + // the instance, the |time_posted| records that instant, which is presumed to + // be when the task was posted into a queue to run on a worker thread. + // The |start_of_run| is when the worker thread started to perform the run of + // the task. + // The |end_of_run| was just obtained by a call to Now() (just after the task + // finished). + static void TallyRunOnWorkerThreadIfTracking( + const Births* birth, + const TrackedTime& time_posted, + const TrackedTime& start_of_run, + const TrackedTime& end_of_run); + + // Record the end of execution in region, generally corresponding to a scope + // being exited. + static void TallyRunInAScopedRegionIfTracking( + const Births* birth, + const TrackedTime& start_of_run, + const TrackedTime& end_of_run); + + const std::string& thread_name() const { return thread_name_; } + + // Hack: asynchronously clear all birth counts and death tallies data values + // in all ThreadData instances. The numerical (zeroing) part is done without + // use of a locks or atomics exchanges, and may (for int64 values) produce + // bogus counts VERY rarely. + static void ResetAllThreadData(); + + // Initializes all statics if needed (this initialization call should be made + // while we are single threaded). Returns false if unable to initialize. + static bool Initialize(); + + // Sets internal status_. + // If |status| is false, then status_ is set to DEACTIVATED. + // If |status| is true, then status_ is set to, PROFILING_ACTIVE, or + // PROFILING_CHILDREN_ACTIVE. + // If tracking is not compiled in, this function will return false. + // If parent-child tracking is not compiled in, then an attempt to set the + // status to PROFILING_CHILDREN_ACTIVE will only result in a status of + // PROFILING_ACTIVE (i.e., it can't be set to a higher level than what is + // compiled into the binary, and parent-child tracking at the + // PROFILING_CHILDREN_ACTIVE level might not be compiled in). + static bool InitializeAndSetTrackingStatus(Status status); + + static Status status(); + + // Indicate if any sort of profiling is being done (i.e., we are more than + // DEACTIVATED). + static bool TrackingStatus(); + + // For testing only, indicate if the status of parent-child tracking is turned + // on. This is currently a compiled option, atop TrackingStatus(). + static bool TrackingParentChildStatus(); + + // Special versions of Now() for getting times at start and end of a tracked + // run. They are super fast when tracking is disabled, and have some internal + // side effects when we are tracking, so that we can deduce the amount of time + // accumulated outside of execution of tracked runs. + // The task that will be tracked is passed in as |parent| so that parent-child + // relationships can be (optionally) calculated. + static TrackedTime NowForStartOfRun(const Births* parent); + static TrackedTime NowForEndOfRun(); + + // Provide a time function that does nothing (runs fast) when we don't have + // the profiler enabled. It will generally be optimized away when it is + // ifdef'ed to be small enough (allowing the profiler to be "compiled out" of + // the code). + static TrackedTime Now(); + + // Use the function |now| to provide current times, instead of calling the + // TrackedTime::Now() function. Since this alternate function is being used, + // the other time arguments (used for calculating queueing delay) will be + // ignored. + static void SetAlternateTimeSource(NowFunction* now); + + // This function can be called at process termination to validate that thread + // cleanup routines have been called for at least some number of named + // threads. + static void EnsureCleanupWasCalled(int major_threads_shutdown_count); + + private: + // Allow only tests to call ShutdownSingleThreadedCleanup. We NEVER call it + // in production code. + // TODO(jar): Make this a friend in DEBUG only, so that the optimizer has a + // better change of optimizing (inlining? etc.) private methods (knowing that + // there will be no need for an external entry point). + friend class TrackedObjectsTest; + FRIEND_TEST_ALL_PREFIXES(TrackedObjectsTest, MinimalStartupShutdown); + FRIEND_TEST_ALL_PREFIXES(TrackedObjectsTest, TinyStartupShutdown); + FRIEND_TEST_ALL_PREFIXES(TrackedObjectsTest, ParentChildTest); + friend class TrackedTimeTest; + + typedef std::map<const BirthOnThread*, int> BirthCountMap; + + // Worker thread construction creates a name since there is none. + explicit ThreadData(int thread_number); + + // Message loop based construction should provide a name. + explicit ThreadData(const std::string& suggested_name); + + ~ThreadData(); + + // Push this instance to the head of all_thread_data_list_head_, linking it to + // the previous head. This is performed after each construction, and leaves + // the instance permanently on that list. + void PushToHeadOfList(); + + // (Thread safe) Get start of list of all ThreadData instances using the lock. + static ThreadData* first(); + + // Iterate through the null terminated list of ThreadData instances. + ThreadData* next() const; + + + // In this thread's data, record a new birth. + Births* TallyABirth(const Location& location); + + // Find a place to record a death on this thread. + void TallyADeath(const Births& birth, int32 queue_duration, int32 duration); + + // Snapshot (under a lock) the profiled data for the tasks in each ThreadData + // instance. Also updates the |birth_counts| tally for each task to keep + // track of the number of living instances of the task. If |reset_max| is + // true, then the max values in each DeathData instance are reset during the + // scan. + static void SnapshotAllExecutedTasks(bool reset_max, + ProcessDataSnapshot* process_data, + BirthCountMap* birth_counts); + + // Snapshots (under a lock) the profiled data for the tasks for this thread + // and writes all of the executed tasks' data -- i.e. the data for the tasks + // with with entries in the death_map_ -- into |process_data|. Also updates + // the |birth_counts| tally for each task to keep track of the number of + // living instances of the task -- that is, each task maps to the number of + // births for the task that have not yet been balanced by a death. If + // |reset_max| is true, then the max values in each DeathData instance are + // reset during the scan. + void SnapshotExecutedTasks(bool reset_max, + ProcessDataSnapshot* process_data, + BirthCountMap* birth_counts); + + // Using our lock, make a copy of the specified maps. This call may be made + // on non-local threads, which necessitate the use of the lock to prevent + // the map(s) from being reallocaed while they are copied. If |reset_max| is + // true, then, just after we copy the DeathMap, we will set the max values to + // zero in the active DeathMap (not the snapshot). + void SnapshotMaps(bool reset_max, + BirthMap* birth_map, + DeathMap* death_map, + ParentChildSet* parent_child_set); + + // Using our lock to protect the iteration, Clear all birth and death data. + void Reset(); + + // This method is called by the TLS system when a thread terminates. + // The argument may be NULL if this thread has never tracked a birth or death. + static void OnThreadTermination(void* thread_data); + + // This method should be called when a worker thread terminates, so that we + // can save all the thread data into a cache of reusable ThreadData instances. + void OnThreadTerminationCleanup(); + + // Cleans up data structures, and returns statics to near pristine (mostly + // uninitialized) state. If there is any chance that other threads are still + // using the data structures, then the |leak| argument should be passed in as + // true, and the data structures (birth maps, death maps, ThreadData + // insntances, etc.) will be leaked and not deleted. If you have joined all + // threads since the time that InitializeAndSetTrackingStatus() was called, + // then you can pass in a |leak| value of false, and this function will + // delete recursively all data structures, starting with the list of + // ThreadData instances. + static void ShutdownSingleThreadedCleanup(bool leak); + + // When non-null, this specifies an external function that supplies monotone + // increasing time functcion. + static NowFunction* now_function_; + + // We use thread local store to identify which ThreadData to interact with. + static base::ThreadLocalStorage::StaticSlot tls_index_; + + // List of ThreadData instances for use with worker threads. When a worker + // thread is done (terminated), we push it onto this llist. When a new worker + // thread is created, we first try to re-use a ThreadData instance from the + // list, and if none are available, construct a new one. + // This is only accessed while list_lock_ is held. + static ThreadData* first_retired_worker_; + + // Link to the most recently created instance (starts a null terminated list). + // The list is traversed by about:profiler when it needs to snapshot data. + // This is only accessed while list_lock_ is held. + static ThreadData* all_thread_data_list_head_; + + // The next available worker thread number. This should only be accessed when + // the list_lock_ is held. + static int worker_thread_data_creation_count_; + + // The number of times TLS has called us back to cleanup a ThreadData + // instance. This is only accessed while list_lock_ is held. + static int cleanup_count_; + + // Incarnation sequence number, indicating how many times (during unittests) + // we've either transitioned out of UNINITIALIZED, or into that state. This + // value is only accessed while the list_lock_ is held. + static int incarnation_counter_; + + // Protection for access to all_thread_data_list_head_, and to + // unregistered_thread_data_pool_. This lock is leaked at shutdown. + // The lock is very infrequently used, so we can afford to just make a lazy + // instance and be safe. + static base::LazyInstance<base::Lock>::Leaky list_lock_; + + // We set status_ to SHUTDOWN when we shut down the tracking service. + static Status status_; + + // Link to next instance (null terminated list). Used to globally track all + // registered instances (corresponds to all registered threads where we keep + // data). + ThreadData* next_; + + // Pointer to another ThreadData instance for a Worker-Thread that has been + // retired (its thread was terminated). This value is non-NULL only for a + // retired ThreadData associated with a Worker-Thread. + ThreadData* next_retired_worker_; + + // The name of the thread that is being recorded. If this thread has no + // message_loop, then this is a worker thread, with a sequence number postfix. + std::string thread_name_; + + // Indicate if this is a worker thread, and the ThreadData contexts should be + // stored in the unregistered_thread_data_pool_ when not in use. + // Value is zero when it is not a worker thread. Value is a positive integer + // corresponding to the created thread name if it is a worker thread. + int worker_thread_number_; + + // A map used on each thread to keep track of Births on this thread. + // This map should only be accessed on the thread it was constructed on. + // When a snapshot is needed, this structure can be locked in place for the + // duration of the snapshotting activity. + BirthMap birth_map_; + + // Similar to birth_map_, this records informations about death of tracked + // instances (i.e., when a tracked instance was destroyed on this thread). + // It is locked before changing, and hence other threads may access it by + // locking before reading it. + DeathMap death_map_; + + // A set of parents that created children tasks on this thread. Each pair + // corresponds to potentially non-local Births (location and thread), and a + // local Births (that took place on this thread). + ParentChildSet parent_child_set_; + + // Lock to protect *some* access to BirthMap and DeathMap. The maps are + // regularly read and written on this thread, but may only be read from other + // threads. To support this, we acquire this lock if we are writing from this + // thread, or reading from another thread. For reading from this thread we + // don't need a lock, as there is no potential for a conflict since the + // writing is only done from this thread. + mutable base::Lock map_lock_; + + // The stack of parents that are currently being profiled. This includes only + // tasks that have started a timer recently via NowForStartOfRun(), but not + // yet concluded with a NowForEndOfRun(). Usually this stack is one deep, but + // if a scoped region is profiled, or <sigh> a task runs a nested-message + // loop, then the stack can grow larger. Note that we don't try to deduct + // time in nested porfiles, as our current timer is based on wall-clock time, + // and not CPU time (and we're hopeful that nested timing won't be a + // significant additional cost). + ParentStack parent_stack_; + + // A random number that we used to select decide which sample to keep as a + // representative sample in each DeathData instance. We can't start off with + // much randomness (because we can't call RandInt() on all our threads), so + // we stir in more and more as we go. + int32 random_number_; + + // Record of what the incarnation_counter_ was when this instance was created. + // If the incarnation_counter_ has changed, then we avoid pushing into the + // pool (this is only critical in tests which go through multiple + // incarnations). + int incarnation_count_for_pool_; + + DISALLOW_COPY_AND_ASSIGN(ThreadData); +}; + +//------------------------------------------------------------------------------ +// A snapshotted representation of a (parent, child) task pair, for tracking +// hierarchical profiles. + +struct BASE_EXPORT ParentChildPairSnapshot { + public: + ParentChildPairSnapshot(); + explicit ParentChildPairSnapshot( + const ThreadData::ParentChildPair& parent_child); + ~ParentChildPairSnapshot(); + + BirthOnThreadSnapshot parent; + BirthOnThreadSnapshot child; +}; + +//------------------------------------------------------------------------------ +// A snapshotted representation of the list of ThreadData objects for a process. + +struct BASE_EXPORT ProcessDataSnapshot { + public: + ProcessDataSnapshot(); + ~ProcessDataSnapshot(); + + std::vector<TaskSnapshot> tasks; + std::vector<ParentChildPairSnapshot> descendants; + int process_id; +}; + +} // namespace tracked_objects + +#endif // BASE_TRACKED_OBJECTS_H_
diff --git a/src/base/tracked_objects_unittest.cc b/src/base/tracked_objects_unittest.cc new file mode 100644 index 0000000..1e64508 --- /dev/null +++ b/src/base/tracked_objects_unittest.cc
@@ -0,0 +1,581 @@ +// 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. + +// Test of classes in the tracked_objects.h classes. + +#include "base/tracked_objects.h" + +#include "base/memory/scoped_ptr.h" +#include "base/process_util.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +const int kLineNumber = 1776; +const char kFile[] = "FixedUnitTestFileName"; +const char kWorkerThreadName[] = "WorkerThread-1"; +const char kMainThreadName[] = "SomeMainThreadName"; +const char kStillAlive[] = "Still_Alive"; + +namespace tracked_objects { + +class TrackedObjectsTest : public testing::Test { + protected: + TrackedObjectsTest() { + // On entry, leak any database structures in case they are still in use by + // prior threads. + ThreadData::ShutdownSingleThreadedCleanup(true); + } + + virtual ~TrackedObjectsTest() { + // We should not need to leak any structures we create, since we are + // single threaded, and carefully accounting for items. + ThreadData::ShutdownSingleThreadedCleanup(false); + } + + // Reset the profiler state. + void Reset() { + ThreadData::ShutdownSingleThreadedCleanup(false); + } + + // Simulate a birth on the thread named |thread_name|, at the given + // |location|. + void TallyABirth(const Location& location, const std::string& thread_name) { + // If the |thread_name| is empty, we don't initialize system with a thread + // name, so we're viewed as a worker thread. + if (!thread_name.empty()) + ThreadData::InitializeThreadContext(kMainThreadName); + + // Do not delete |birth|. We don't own it. + Births* birth = ThreadData::TallyABirthIfActive(location); + + if (ThreadData::status() == ThreadData::DEACTIVATED) + EXPECT_EQ(reinterpret_cast<Births*>(NULL), birth); + else + EXPECT_NE(reinterpret_cast<Births*>(NULL), birth); + } + + // Helper function to verify the most common test expectations. + void ExpectSimpleProcessData(const ProcessDataSnapshot& process_data, + const std::string& function_name, + const std::string& birth_thread, + const std::string& death_thread, + int count, + int run_ms, + int queue_ms) { + ASSERT_EQ(1u, process_data.tasks.size()); + + EXPECT_EQ(kFile, process_data.tasks[0].birth.location.file_name); + EXPECT_EQ(function_name, + process_data.tasks[0].birth.location.function_name); + EXPECT_EQ(kLineNumber, process_data.tasks[0].birth.location.line_number); + + EXPECT_EQ(birth_thread, process_data.tasks[0].birth.thread_name); + + EXPECT_EQ(count, process_data.tasks[0].death_data.count); + EXPECT_EQ(count * run_ms, + process_data.tasks[0].death_data.run_duration_sum); + EXPECT_EQ(run_ms, process_data.tasks[0].death_data.run_duration_max); + EXPECT_EQ(run_ms, process_data.tasks[0].death_data.run_duration_sample); + EXPECT_EQ(count * queue_ms, + process_data.tasks[0].death_data.queue_duration_sum); + EXPECT_EQ(queue_ms, process_data.tasks[0].death_data.queue_duration_max); + EXPECT_EQ(queue_ms, process_data.tasks[0].death_data.queue_duration_sample); + + EXPECT_EQ(death_thread, process_data.tasks[0].death_thread_name); + + EXPECT_EQ(0u, process_data.descendants.size()); + + EXPECT_EQ(base::GetCurrentProcId(), process_data.process_id); + } +}; + +TEST_F(TrackedObjectsTest, MinimalStartupShutdown) { + // Minimal test doesn't even create any tasks. + if (!ThreadData::InitializeAndSetTrackingStatus( + ThreadData::PROFILING_CHILDREN_ACTIVE)) + return; + + EXPECT_FALSE(ThreadData::first()); // No activity even on this thread. + ThreadData* data = ThreadData::Get(); + EXPECT_TRUE(ThreadData::first()); // Now class was constructed. + ASSERT_TRUE(data); + EXPECT_FALSE(data->next()); + EXPECT_EQ(data, ThreadData::Get()); + ThreadData::BirthMap birth_map; + ThreadData::DeathMap death_map; + ThreadData::ParentChildSet parent_child_set; + data->SnapshotMaps(false, &birth_map, &death_map, &parent_child_set); + EXPECT_EQ(0u, birth_map.size()); + EXPECT_EQ(0u, death_map.size()); + EXPECT_EQ(0u, parent_child_set.size()); + + // Clean up with no leaking. + Reset(); + + // Do it again, just to be sure we reset state completely. + EXPECT_TRUE(ThreadData::InitializeAndSetTrackingStatus( + ThreadData::PROFILING_CHILDREN_ACTIVE)); + EXPECT_FALSE(ThreadData::first()); // No activity even on this thread. + data = ThreadData::Get(); + EXPECT_TRUE(ThreadData::first()); // Now class was constructed. + ASSERT_TRUE(data); + EXPECT_FALSE(data->next()); + EXPECT_EQ(data, ThreadData::Get()); + birth_map.clear(); + death_map.clear(); + parent_child_set.clear(); + data->SnapshotMaps(false, &birth_map, &death_map, &parent_child_set); + EXPECT_EQ(0u, birth_map.size()); + EXPECT_EQ(0u, death_map.size()); + EXPECT_EQ(0u, parent_child_set.size()); +} + +TEST_F(TrackedObjectsTest, TinyStartupShutdown) { + if (!ThreadData::InitializeAndSetTrackingStatus( + ThreadData::PROFILING_CHILDREN_ACTIVE)) + return; + + // Instigate tracking on a single tracked object, on our thread. + const char kFunction[] = "TinyStartupShutdown"; + Location location(kFunction, kFile, kLineNumber, NULL); + Births* first_birth = ThreadData::TallyABirthIfActive(location); + + ThreadData* data = ThreadData::first(); + ASSERT_TRUE(data); + EXPECT_FALSE(data->next()); + EXPECT_EQ(data, ThreadData::Get()); + ThreadData::BirthMap birth_map; + ThreadData::DeathMap death_map; + ThreadData::ParentChildSet parent_child_set; + data->SnapshotMaps(false, &birth_map, &death_map, &parent_child_set); + EXPECT_EQ(1u, birth_map.size()); // 1 birth location. + EXPECT_EQ(1, birth_map.begin()->second->birth_count()); // 1 birth. + EXPECT_EQ(0u, death_map.size()); // No deaths. + EXPECT_EQ(0u, parent_child_set.size()); // No children. + + + // Now instigate another birth, while we are timing the run of the first + // execution. + ThreadData::NowForStartOfRun(first_birth); + // Create a child (using the same birth location). + // TrackingInfo will call TallyABirth() during construction. + base::TimeTicks kBogusBirthTime; + base::TrackingInfo pending_task(location, kBogusBirthTime); + TrackedTime start_time(pending_task.time_posted); + // Finally conclude the outer run. + TrackedTime end_time = ThreadData::NowForEndOfRun(); + ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, start_time, + end_time); + + birth_map.clear(); + death_map.clear(); + parent_child_set.clear(); + data->SnapshotMaps(false, &birth_map, &death_map, &parent_child_set); + EXPECT_EQ(1u, birth_map.size()); // 1 birth location. + EXPECT_EQ(2, birth_map.begin()->second->birth_count()); // 2 births. + EXPECT_EQ(1u, death_map.size()); // 1 location. + EXPECT_EQ(1, death_map.begin()->second.count()); // 1 death. + if (ThreadData::TrackingParentChildStatus()) { + EXPECT_EQ(1u, parent_child_set.size()); // 1 child. + EXPECT_EQ(parent_child_set.begin()->first, + parent_child_set.begin()->second); + } else { + EXPECT_EQ(0u, parent_child_set.size()); // no stats. + } + + // The births were at the same location as the one known death. + EXPECT_EQ(birth_map.begin()->second, death_map.begin()->first); + + ProcessDataSnapshot process_data; + ThreadData::Snapshot(false, &process_data); + + const int32 time_elapsed = (end_time - start_time).InMilliseconds(); + ASSERT_EQ(1u, process_data.tasks.size()); + EXPECT_EQ(kFile, process_data.tasks[0].birth.location.file_name); + EXPECT_EQ(kFunction, process_data.tasks[0].birth.location.function_name); + EXPECT_EQ(kLineNumber, process_data.tasks[0].birth.location.line_number); + EXPECT_EQ(kWorkerThreadName, process_data.tasks[0].birth.thread_name); + EXPECT_EQ(1, process_data.tasks[0].death_data.count); + EXPECT_EQ(time_elapsed, process_data.tasks[0].death_data.run_duration_sum); + EXPECT_EQ(time_elapsed, process_data.tasks[0].death_data.run_duration_max); + EXPECT_EQ(time_elapsed, process_data.tasks[0].death_data.run_duration_sample); + EXPECT_EQ(0, process_data.tasks[0].death_data.queue_duration_sum); + EXPECT_EQ(0, process_data.tasks[0].death_data.queue_duration_max); + EXPECT_EQ(0, process_data.tasks[0].death_data.queue_duration_sample); + EXPECT_EQ(kWorkerThreadName, process_data.tasks[0].death_thread_name); + + if (ThreadData::TrackingParentChildStatus()) { + ASSERT_EQ(1u, process_data.descendants.size()); + EXPECT_EQ(kFile, process_data.descendants[0].parent.location.file_name); + EXPECT_EQ(kFunction, + process_data.descendants[0].parent.location.function_name); + EXPECT_EQ(kLineNumber, + process_data.descendants[0].parent.location.line_number); + EXPECT_EQ(kWorkerThreadName, + process_data.descendants[0].parent.thread_name); + EXPECT_EQ(kFile, process_data.descendants[0].child.location.file_name); + EXPECT_EQ(kFunction, + process_data.descendants[0].child.location.function_name); + EXPECT_EQ(kLineNumber, + process_data.descendants[0].child.location.line_number); + EXPECT_EQ(kWorkerThreadName, process_data.descendants[0].child.thread_name); + } else { + EXPECT_EQ(0u, process_data.descendants.size()); + } +} + +TEST_F(TrackedObjectsTest, DeathDataTest) { + if (!ThreadData::InitializeAndSetTrackingStatus( + ThreadData::PROFILING_CHILDREN_ACTIVE)) + return; + + scoped_ptr<DeathData> data(new DeathData()); + ASSERT_NE(data, reinterpret_cast<DeathData*>(NULL)); + EXPECT_EQ(data->run_duration_sum(), 0); + EXPECT_EQ(data->run_duration_sample(), 0); + EXPECT_EQ(data->queue_duration_sum(), 0); + EXPECT_EQ(data->queue_duration_sample(), 0); + EXPECT_EQ(data->count(), 0); + + int32 run_ms = 42; + int32 queue_ms = 8; + + const int kUnrandomInt = 0; // Fake random int that ensure we sample data. + data->RecordDeath(queue_ms, run_ms, kUnrandomInt); + EXPECT_EQ(data->run_duration_sum(), run_ms); + EXPECT_EQ(data->run_duration_sample(), run_ms); + EXPECT_EQ(data->queue_duration_sum(), queue_ms); + EXPECT_EQ(data->queue_duration_sample(), queue_ms); + EXPECT_EQ(data->count(), 1); + + data->RecordDeath(queue_ms, run_ms, kUnrandomInt); + EXPECT_EQ(data->run_duration_sum(), run_ms + run_ms); + EXPECT_EQ(data->run_duration_sample(), run_ms); + EXPECT_EQ(data->queue_duration_sum(), queue_ms + queue_ms); + EXPECT_EQ(data->queue_duration_sample(), queue_ms); + EXPECT_EQ(data->count(), 2); + + DeathDataSnapshot snapshot(*data); + EXPECT_EQ(2, snapshot.count); + EXPECT_EQ(2 * run_ms, snapshot.run_duration_sum); + EXPECT_EQ(run_ms, snapshot.run_duration_max); + EXPECT_EQ(run_ms, snapshot.run_duration_sample); + EXPECT_EQ(2 * queue_ms, snapshot.queue_duration_sum); + EXPECT_EQ(queue_ms, snapshot.queue_duration_max); + EXPECT_EQ(queue_ms, snapshot.queue_duration_sample); +} + +TEST_F(TrackedObjectsTest, DeactivatedBirthOnlyToSnapshotWorkerThread) { + // Start in the deactivated state. + if (!ThreadData::InitializeAndSetTrackingStatus(ThreadData::DEACTIVATED)) + return; + + const char kFunction[] = "DeactivatedBirthOnlyToSnapshotWorkerThread"; + Location location(kFunction, kFile, kLineNumber, NULL); + TallyABirth(location, std::string()); + + ProcessDataSnapshot process_data; + ThreadData::Snapshot(false, &process_data); + EXPECT_EQ(0u, process_data.tasks.size()); + EXPECT_EQ(0u, process_data.descendants.size()); + EXPECT_EQ(base::GetCurrentProcId(), process_data.process_id); +} + +TEST_F(TrackedObjectsTest, DeactivatedBirthOnlyToSnapshotMainThread) { + // Start in the deactivated state. + if (!ThreadData::InitializeAndSetTrackingStatus(ThreadData::DEACTIVATED)) + return; + + const char kFunction[] = "DeactivatedBirthOnlyToSnapshotMainThread"; + Location location(kFunction, kFile, kLineNumber, NULL); + TallyABirth(location, kMainThreadName); + + ProcessDataSnapshot process_data; + ThreadData::Snapshot(false, &process_data); + EXPECT_EQ(0u, process_data.tasks.size()); + EXPECT_EQ(0u, process_data.descendants.size()); + EXPECT_EQ(base::GetCurrentProcId(), process_data.process_id); +} + +TEST_F(TrackedObjectsTest, BirthOnlyToSnapshotWorkerThread) { + if (!ThreadData::InitializeAndSetTrackingStatus( + ThreadData::PROFILING_CHILDREN_ACTIVE)) + return; + + const char kFunction[] = "BirthOnlyToSnapshotWorkerThread"; + Location location(kFunction, kFile, kLineNumber, NULL); + TallyABirth(location, std::string()); + + ProcessDataSnapshot process_data; + ThreadData::Snapshot(false, &process_data); + ExpectSimpleProcessData(process_data, kFunction, kWorkerThreadName, + kStillAlive, 1, 0, 0); +} + +TEST_F(TrackedObjectsTest, BirthOnlyToSnapshotMainThread) { + if (!ThreadData::InitializeAndSetTrackingStatus( + ThreadData::PROFILING_CHILDREN_ACTIVE)) + return; + + const char kFunction[] = "BirthOnlyToSnapshotMainThread"; + Location location(kFunction, kFile, kLineNumber, NULL); + TallyABirth(location, kMainThreadName); + + ProcessDataSnapshot process_data; + ThreadData::Snapshot(false, &process_data); + ExpectSimpleProcessData(process_data, kFunction, kMainThreadName, kStillAlive, + 1, 0, 0); +} + +TEST_F(TrackedObjectsTest, LifeCycleToSnapshotMainThread) { + if (!ThreadData::InitializeAndSetTrackingStatus( + ThreadData::PROFILING_CHILDREN_ACTIVE)) + return; + + const char kFunction[] = "LifeCycleToSnapshotMainThread"; + Location location(kFunction, kFile, kLineNumber, NULL); + TallyABirth(location, kMainThreadName); + + const base::TimeTicks kTimePosted = base::TimeTicks() + + base::TimeDelta::FromMilliseconds(1); + const base::TimeTicks kDelayedStartTime = base::TimeTicks(); + // TrackingInfo will call TallyABirth() during construction. + base::TrackingInfo pending_task(location, kDelayedStartTime); + pending_task.time_posted = kTimePosted; // Overwrite implied Now(). + + const TrackedTime kStartOfRun = TrackedTime() + + Duration::FromMilliseconds(5); + const TrackedTime kEndOfRun = TrackedTime() + Duration::FromMilliseconds(7); + ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, + kStartOfRun, kEndOfRun); + + ProcessDataSnapshot process_data; + ThreadData::Snapshot(false, &process_data); + ExpectSimpleProcessData(process_data, kFunction, kMainThreadName, + kMainThreadName, 1, 2, 4); +} + +// We will deactivate tracking after the birth, and before the death, and +// demonstrate that the lifecycle is completely tallied. This ensures that +// our tallied births are matched by tallied deaths (except for when the +// task is still running, or is queued). +TEST_F(TrackedObjectsTest, LifeCycleMidDeactivatedToSnapshotMainThread) { + if (!ThreadData::InitializeAndSetTrackingStatus( + ThreadData::PROFILING_CHILDREN_ACTIVE)) + return; + + const char kFunction[] = "LifeCycleMidDeactivatedToSnapshotMainThread"; + Location location(kFunction, kFile, kLineNumber, NULL); + TallyABirth(location, kMainThreadName); + + const base::TimeTicks kTimePosted = base::TimeTicks() + + base::TimeDelta::FromMilliseconds(1); + const base::TimeTicks kDelayedStartTime = base::TimeTicks(); + // TrackingInfo will call TallyABirth() during construction. + base::TrackingInfo pending_task(location, kDelayedStartTime); + pending_task.time_posted = kTimePosted; // Overwrite implied Now(). + + // Turn off tracking now that we have births. + EXPECT_TRUE(ThreadData::InitializeAndSetTrackingStatus( + ThreadData::DEACTIVATED)); + + const TrackedTime kStartOfRun = TrackedTime() + + Duration::FromMilliseconds(5); + const TrackedTime kEndOfRun = TrackedTime() + Duration::FromMilliseconds(7); + ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, + kStartOfRun, kEndOfRun); + + ProcessDataSnapshot process_data; + ThreadData::Snapshot(false, &process_data); + ExpectSimpleProcessData(process_data, kFunction, kMainThreadName, + kMainThreadName, 1, 2, 4); +} + +// We will deactivate tracking before starting a life cycle, and neither +// the birth nor the death will be recorded. +TEST_F(TrackedObjectsTest, LifeCyclePreDeactivatedToSnapshotMainThread) { + // Start in the deactivated state. + if (!ThreadData::InitializeAndSetTrackingStatus(ThreadData::DEACTIVATED)) + return; + + const char kFunction[] = "LifeCyclePreDeactivatedToSnapshotMainThread"; + Location location(kFunction, kFile, kLineNumber, NULL); + TallyABirth(location, kMainThreadName); + + const base::TimeTicks kTimePosted = base::TimeTicks() + + base::TimeDelta::FromMilliseconds(1); + const base::TimeTicks kDelayedStartTime = base::TimeTicks(); + // TrackingInfo will call TallyABirth() during construction. + base::TrackingInfo pending_task(location, kDelayedStartTime); + pending_task.time_posted = kTimePosted; // Overwrite implied Now(). + + const TrackedTime kStartOfRun = TrackedTime() + + Duration::FromMilliseconds(5); + const TrackedTime kEndOfRun = TrackedTime() + Duration::FromMilliseconds(7); + ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, + kStartOfRun, kEndOfRun); + + ProcessDataSnapshot process_data; + ThreadData::Snapshot(false, &process_data); + EXPECT_EQ(0u, process_data.tasks.size()); + EXPECT_EQ(0u, process_data.descendants.size()); + EXPECT_EQ(base::GetCurrentProcId(), process_data.process_id); +} + +TEST_F(TrackedObjectsTest, LifeCycleToSnapshotWorkerThread) { + if (!ThreadData::InitializeAndSetTrackingStatus( + ThreadData::PROFILING_CHILDREN_ACTIVE)) + return; + + const char kFunction[] = "LifeCycleToSnapshotWorkerThread"; + Location location(kFunction, kFile, kLineNumber, NULL); + // Do not delete |birth|. We don't own it. + Births* birth = ThreadData::TallyABirthIfActive(location); + EXPECT_NE(reinterpret_cast<Births*>(NULL), birth); + + const TrackedTime kTimePosted = TrackedTime() + Duration::FromMilliseconds(1); + const TrackedTime kStartOfRun = TrackedTime() + + Duration::FromMilliseconds(5); + const TrackedTime kEndOfRun = TrackedTime() + Duration::FromMilliseconds(7); + ThreadData::TallyRunOnWorkerThreadIfTracking(birth, kTimePosted, + kStartOfRun, kEndOfRun); + + // Call for the ToSnapshot, but tell it to not reset the maxes after scanning. + ProcessDataSnapshot process_data; + ThreadData::Snapshot(false, &process_data); + ExpectSimpleProcessData(process_data, kFunction, kWorkerThreadName, + kWorkerThreadName, 1, 2, 4); + + // Call for the ToSnapshot, but tell it to reset the maxes after scanning. + // We'll still get the same values, but the data will be reset (which we'll + // see in a moment). + ProcessDataSnapshot process_data_pre_reset; + ThreadData::Snapshot(true, &process_data_pre_reset); + ExpectSimpleProcessData(process_data, kFunction, kWorkerThreadName, + kWorkerThreadName, 1, 2, 4); + + // Call for the ToSnapshot, and now we'll see the result of the last + // translation, as the max will have been pushed back to zero. + ProcessDataSnapshot process_data_post_reset; + ThreadData::Snapshot(true, &process_data_post_reset); + ASSERT_EQ(1u, process_data_post_reset.tasks.size()); + EXPECT_EQ(kFile, process_data_post_reset.tasks[0].birth.location.file_name); + EXPECT_EQ(kFunction, + process_data_post_reset.tasks[0].birth.location.function_name); + EXPECT_EQ(kLineNumber, + process_data_post_reset.tasks[0].birth.location.line_number); + EXPECT_EQ(kWorkerThreadName, + process_data_post_reset.tasks[0].birth.thread_name); + EXPECT_EQ(1, process_data_post_reset.tasks[0].death_data.count); + EXPECT_EQ(2, process_data_post_reset.tasks[0].death_data.run_duration_sum); + EXPECT_EQ(0, process_data_post_reset.tasks[0].death_data.run_duration_max); + EXPECT_EQ(2, process_data_post_reset.tasks[0].death_data.run_duration_sample); + EXPECT_EQ(4, process_data_post_reset.tasks[0].death_data.queue_duration_sum); + EXPECT_EQ(0, process_data_post_reset.tasks[0].death_data.queue_duration_max); + EXPECT_EQ(4, + process_data_post_reset.tasks[0].death_data.queue_duration_sample); + EXPECT_EQ(kWorkerThreadName, + process_data_post_reset.tasks[0].death_thread_name); + EXPECT_EQ(0u, process_data_post_reset.descendants.size()); + EXPECT_EQ(base::GetCurrentProcId(), process_data_post_reset.process_id); +} + +TEST_F(TrackedObjectsTest, TwoLives) { + if (!ThreadData::InitializeAndSetTrackingStatus( + ThreadData::PROFILING_CHILDREN_ACTIVE)) + return; + + const char kFunction[] = "TwoLives"; + Location location(kFunction, kFile, kLineNumber, NULL); + TallyABirth(location, kMainThreadName); + + const base::TimeTicks kTimePosted = base::TimeTicks() + + base::TimeDelta::FromMilliseconds(1); + const base::TimeTicks kDelayedStartTime = base::TimeTicks(); + // TrackingInfo will call TallyABirth() during construction. + base::TrackingInfo pending_task(location, kDelayedStartTime); + pending_task.time_posted = kTimePosted; // Overwrite implied Now(). + + const TrackedTime kStartOfRun = TrackedTime() + + Duration::FromMilliseconds(5); + const TrackedTime kEndOfRun = TrackedTime() + Duration::FromMilliseconds(7); + ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, + kStartOfRun, kEndOfRun); + + // TrackingInfo will call TallyABirth() during construction. + base::TrackingInfo pending_task2(location, kDelayedStartTime); + pending_task2.time_posted = kTimePosted; // Overwrite implied Now(). + + ThreadData::TallyRunOnNamedThreadIfTracking(pending_task2, + kStartOfRun, kEndOfRun); + + ProcessDataSnapshot process_data; + ThreadData::Snapshot(false, &process_data); + ExpectSimpleProcessData(process_data, kFunction, kMainThreadName, + kMainThreadName, 2, 2, 4); +} + +TEST_F(TrackedObjectsTest, DifferentLives) { + if (!ThreadData::InitializeAndSetTrackingStatus( + ThreadData::PROFILING_CHILDREN_ACTIVE)) + return; + + // Use a well named thread. + ThreadData::InitializeThreadContext(kMainThreadName); + const char kFunction[] = "DifferentLives"; + Location location(kFunction, kFile, kLineNumber, NULL); + + const base::TimeTicks kTimePosted = base::TimeTicks() + + base::TimeDelta::FromMilliseconds(1); + const base::TimeTicks kDelayedStartTime = base::TimeTicks(); + // TrackingInfo will call TallyABirth() during construction. + base::TrackingInfo pending_task(location, kDelayedStartTime); + pending_task.time_posted = kTimePosted; // Overwrite implied Now(). + + const TrackedTime kStartOfRun = TrackedTime() + + Duration::FromMilliseconds(5); + const TrackedTime kEndOfRun = TrackedTime() + Duration::FromMilliseconds(7); + ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, + kStartOfRun, kEndOfRun); + + const int kSecondFakeLineNumber = 999; + Location second_location(kFunction, kFile, kSecondFakeLineNumber, NULL); + + // TrackingInfo will call TallyABirth() during construction. + base::TrackingInfo pending_task2(second_location, kDelayedStartTime); + pending_task2.time_posted = kTimePosted; // Overwrite implied Now(). + + ProcessDataSnapshot process_data; + ThreadData::Snapshot(false, &process_data); + ASSERT_EQ(2u, process_data.tasks.size()); + EXPECT_EQ(kFile, process_data.tasks[0].birth.location.file_name); + EXPECT_EQ(kFunction, process_data.tasks[0].birth.location.function_name); + EXPECT_EQ(kLineNumber, process_data.tasks[0].birth.location.line_number); + EXPECT_EQ(kMainThreadName, process_data.tasks[0].birth.thread_name); + EXPECT_EQ(1, process_data.tasks[0].death_data.count); + EXPECT_EQ(2, process_data.tasks[0].death_data.run_duration_sum); + EXPECT_EQ(2, process_data.tasks[0].death_data.run_duration_max); + EXPECT_EQ(2, process_data.tasks[0].death_data.run_duration_sample); + EXPECT_EQ(4, process_data.tasks[0].death_data.queue_duration_sum); + EXPECT_EQ(4, process_data.tasks[0].death_data.queue_duration_max); + EXPECT_EQ(4, process_data.tasks[0].death_data.queue_duration_sample); + EXPECT_EQ(kMainThreadName, process_data.tasks[0].death_thread_name); + EXPECT_EQ(kFile, process_data.tasks[1].birth.location.file_name); + EXPECT_EQ(kFunction, process_data.tasks[1].birth.location.function_name); + EXPECT_EQ(kSecondFakeLineNumber, + process_data.tasks[1].birth.location.line_number); + EXPECT_EQ(kMainThreadName, process_data.tasks[1].birth.thread_name); + EXPECT_EQ(1, process_data.tasks[1].death_data.count); + EXPECT_EQ(0, process_data.tasks[1].death_data.run_duration_sum); + EXPECT_EQ(0, process_data.tasks[1].death_data.run_duration_max); + EXPECT_EQ(0, process_data.tasks[1].death_data.run_duration_sample); + EXPECT_EQ(0, process_data.tasks[1].death_data.queue_duration_sum); + EXPECT_EQ(0, process_data.tasks[1].death_data.queue_duration_max); + EXPECT_EQ(0, process_data.tasks[1].death_data.queue_duration_sample); + EXPECT_EQ(kStillAlive, process_data.tasks[1].death_thread_name); + EXPECT_EQ(0u, process_data.descendants.size()); + EXPECT_EQ(base::GetCurrentProcId(), process_data.process_id); +} + +} // namespace tracked_objects
diff --git a/src/base/tracking_info.cc b/src/base/tracking_info.cc new file mode 100644 index 0000000..0b091f8 --- /dev/null +++ b/src/base/tracking_info.cc
@@ -0,0 +1,28 @@ +// 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/tracking_info.h" + +#include <stddef.h> +#include "base/tracked_objects.h" + +namespace base { + +TrackingInfo::TrackingInfo() + : birth_tally(NULL) { +} + +TrackingInfo::TrackingInfo( + const tracked_objects::Location& posted_from, + base::TimeTicks delayed_run_time) + : birth_tally( + tracked_objects::ThreadData::TallyABirthIfActive(posted_from)), + time_posted(TimeTicks::Now()), + delayed_run_time(delayed_run_time) { +} + +TrackingInfo::~TrackingInfo() {} + +} // namespace base +
diff --git a/src/base/tracking_info.h b/src/base/tracking_info.h new file mode 100644 index 0000000..738bdee --- /dev/null +++ b/src/base/tracking_info.h
@@ -0,0 +1,42 @@ +// 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. + +// This is a simple struct with tracking information that is stored +// with a PendingTask (when message_loop is handling the task). +// Only the information that is shared with the profiler in tracked_objects +// are included in this structure. + + +#ifndef BASE_TRACKING_INFO_H_ +#define BASE_TRACKING_INFO_H_ + +#include "base/time.h" + +namespace tracked_objects { +class Location; +class Births; +} + +namespace base { + +// This structure is copied around by value. +struct BASE_EXPORT TrackingInfo { + TrackingInfo(); + TrackingInfo(const tracked_objects::Location& posted_from, + base::TimeTicks delayed_run_time); + ~TrackingInfo(); + + // Record of location and thread that the task came from. + tracked_objects::Births* birth_tally; + + // Time when the related task was posted. + base::TimeTicks time_posted; + + // The time when the task should be run. + base::TimeTicks delayed_run_time; +}; + +} // namespace base + +#endif // BASE_TRACKING_INFO_H_
diff --git a/src/base/tuple.h b/src/base/tuple.h new file mode 100644 index 0000000..01bda74 --- /dev/null +++ b/src/base/tuple.h
@@ -0,0 +1,1065 @@ +// 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. + +// A Tuple is a generic templatized container, similar in concept to std::pair. +// There are classes Tuple0 to Tuple6, cooresponding to the number of elements +// it contains. The convenient MakeTuple() function takes 0 to 6 arguments, +// and will construct and return the appropriate Tuple object. The functions +// DispatchToMethod and DispatchToFunction take a function pointer or instance +// and method pointer, and unpack a tuple into arguments to the call. +// +// Tuple elements are copied by value, and stored in the tuple. See the unit +// tests for more details of how/when the values are copied. +// +// Example usage: +// // These two methods of creating a Tuple are identical. +// Tuple2<int, const char*> tuple_a(1, "wee"); +// Tuple2<int, const char*> tuple_b = MakeTuple(1, "wee"); +// +// void SomeFunc(int a, const char* b) { } +// DispatchToFunction(&SomeFunc, tuple_a); // SomeFunc(1, "wee") +// DispatchToFunction( +// &SomeFunc, MakeTuple(10, "foo")); // SomeFunc(10, "foo") +// +// struct { void SomeMeth(int a, int b, int c) { } } foo; +// DispatchToMethod(&foo, &Foo::SomeMeth, MakeTuple(1, 2, 3)); +// // foo->SomeMeth(1, 2, 3); + +#ifndef BASE_TUPLE_H__ +#define BASE_TUPLE_H__ + +// Traits ---------------------------------------------------------------------- +// +// A simple traits class for tuple arguments. +// +// ValueType: the bare, nonref version of a type (same as the type for nonrefs). +// RefType: the ref version of a type (same as the type for refs). +// ParamType: what type to pass to functions (refs should not be constified). + +template <class P> +struct TupleTraits { + typedef P ValueType; + typedef P& RefType; + typedef const P& ParamType; +}; + +template <class P> +struct TupleTraits<P&> { + typedef P ValueType; + typedef P& RefType; + typedef P& ParamType; +}; + +template <class P> +struct TupleTypes { }; + +// Tuple ----------------------------------------------------------------------- +// +// This set of classes is useful for bundling 0 or more heterogeneous data types +// into a single variable. The advantage of this is that it greatly simplifies +// function objects that need to take an arbitrary number of parameters; see +// RunnableMethod and IPC::MessageWithTuple. +// +// Tuple0 is supplied to act as a 'void' type. It can be used, for example, +// when dispatching to a function that accepts no arguments (see the +// Dispatchers below). +// Tuple1<A> is rarely useful. One such use is when A is non-const ref that you +// want filled by the dispatchee, and the tuple is merely a container for that +// output (a "tier"). See MakeRefTuple and its usages. + +struct Tuple0 { + typedef Tuple0 ValueTuple; + typedef Tuple0 RefTuple; + typedef Tuple0 ParamTuple; +}; + +template <class A> +struct Tuple1 { + public: + typedef A TypeA; + + Tuple1() {} + explicit Tuple1(typename TupleTraits<A>::ParamType a) : a(a) {} + + A a; +}; + +template <class A, class B> +struct Tuple2 { + public: + typedef A TypeA; + typedef B TypeB; + + Tuple2() {} + Tuple2(typename TupleTraits<A>::ParamType a, + typename TupleTraits<B>::ParamType b) + : a(a), b(b) { + } + + A a; + B b; +}; + +template <class A, class B, class C> +struct Tuple3 { + public: + typedef A TypeA; + typedef B TypeB; + typedef C TypeC; + + Tuple3() {} + Tuple3(typename TupleTraits<A>::ParamType a, + typename TupleTraits<B>::ParamType b, + typename TupleTraits<C>::ParamType c) + : a(a), b(b), c(c){ + } + + A a; + B b; + C c; +}; + +template <class A, class B, class C, class D> +struct Tuple4 { + public: + typedef A TypeA; + typedef B TypeB; + typedef C TypeC; + typedef D TypeD; + + Tuple4() {} + Tuple4(typename TupleTraits<A>::ParamType a, + typename TupleTraits<B>::ParamType b, + typename TupleTraits<C>::ParamType c, + typename TupleTraits<D>::ParamType d) + : a(a), b(b), c(c), d(d) { + } + + A a; + B b; + C c; + D d; +}; + +template <class A, class B, class C, class D, class E> +struct Tuple5 { + public: + typedef A TypeA; + typedef B TypeB; + typedef C TypeC; + typedef D TypeD; + typedef E TypeE; + + Tuple5() {} + Tuple5(typename TupleTraits<A>::ParamType a, + typename TupleTraits<B>::ParamType b, + typename TupleTraits<C>::ParamType c, + typename TupleTraits<D>::ParamType d, + typename TupleTraits<E>::ParamType e) + : a(a), b(b), c(c), d(d), e(e) { + } + + A a; + B b; + C c; + D d; + E e; +}; + +template <class A, class B, class C, class D, class E, class F> +struct Tuple6 { + public: + typedef A TypeA; + typedef B TypeB; + typedef C TypeC; + typedef D TypeD; + typedef E TypeE; + typedef F TypeF; + + Tuple6() {} + Tuple6(typename TupleTraits<A>::ParamType a, + typename TupleTraits<B>::ParamType b, + typename TupleTraits<C>::ParamType c, + typename TupleTraits<D>::ParamType d, + typename TupleTraits<E>::ParamType e, + typename TupleTraits<F>::ParamType f) + : a(a), b(b), c(c), d(d), e(e), f(f) { + } + + A a; + B b; + C c; + D d; + E e; + F f; +}; + +template <class A, class B, class C, class D, class E, class F, class G> +struct Tuple7 { + public: + typedef A TypeA; + typedef B TypeB; + typedef C TypeC; + typedef D TypeD; + typedef E TypeE; + typedef F TypeF; + typedef G TypeG; + + Tuple7() {} + Tuple7(typename TupleTraits<A>::ParamType a, + typename TupleTraits<B>::ParamType b, + typename TupleTraits<C>::ParamType c, + typename TupleTraits<D>::ParamType d, + typename TupleTraits<E>::ParamType e, + typename TupleTraits<F>::ParamType f, + typename TupleTraits<G>::ParamType g) + : a(a), b(b), c(c), d(d), e(e), f(f), g(g) { + } + + A a; + B b; + C c; + D d; + E e; + F f; + G g; +}; + +template <class A, class B, class C, class D, class E, class F, class G, + class H> +struct Tuple8 { + public: + typedef A TypeA; + typedef B TypeB; + typedef C TypeC; + typedef D TypeD; + typedef E TypeE; + typedef F TypeF; + typedef G TypeG; + typedef H TypeH; + + Tuple8() {} + Tuple8(typename TupleTraits<A>::ParamType a, + typename TupleTraits<B>::ParamType b, + typename TupleTraits<C>::ParamType c, + typename TupleTraits<D>::ParamType d, + typename TupleTraits<E>::ParamType e, + typename TupleTraits<F>::ParamType f, + typename TupleTraits<G>::ParamType g, + typename TupleTraits<H>::ParamType h) + : a(a), b(b), c(c), d(d), e(e), f(f), g(g), h(h) { + } + + A a; + B b; + C c; + D d; + E e; + F f; + G g; + H h; +}; + +// Tuple types ---------------------------------------------------------------- +// +// Allows for selection of ValueTuple/RefTuple/ParamTuple without needing the +// definitions of class types the tuple takes as parameters. + +template <> +struct TupleTypes< Tuple0 > { + typedef Tuple0 ValueTuple; + typedef Tuple0 RefTuple; + typedef Tuple0 ParamTuple; +}; + +template <class A> +struct TupleTypes< Tuple1<A> > { + typedef Tuple1<typename TupleTraits<A>::ValueType> ValueTuple; + typedef Tuple1<typename TupleTraits<A>::RefType> RefTuple; + typedef Tuple1<typename TupleTraits<A>::ParamType> ParamTuple; +}; + +template <class A, class B> +struct TupleTypes< Tuple2<A, B> > { + typedef Tuple2<typename TupleTraits<A>::ValueType, + typename TupleTraits<B>::ValueType> ValueTuple; +typedef Tuple2<typename TupleTraits<A>::RefType, + typename TupleTraits<B>::RefType> RefTuple; + typedef Tuple2<typename TupleTraits<A>::ParamType, + typename TupleTraits<B>::ParamType> ParamTuple; +}; + +template <class A, class B, class C> +struct TupleTypes< Tuple3<A, B, C> > { + typedef Tuple3<typename TupleTraits<A>::ValueType, + typename TupleTraits<B>::ValueType, + typename TupleTraits<C>::ValueType> ValueTuple; +typedef Tuple3<typename TupleTraits<A>::RefType, + typename TupleTraits<B>::RefType, + typename TupleTraits<C>::RefType> RefTuple; + typedef Tuple3<typename TupleTraits<A>::ParamType, + typename TupleTraits<B>::ParamType, + typename TupleTraits<C>::ParamType> ParamTuple; +}; + +template <class A, class B, class C, class D> +struct TupleTypes< Tuple4<A, B, C, D> > { + typedef Tuple4<typename TupleTraits<A>::ValueType, + typename TupleTraits<B>::ValueType, + typename TupleTraits<C>::ValueType, + typename TupleTraits<D>::ValueType> ValueTuple; +typedef Tuple4<typename TupleTraits<A>::RefType, + typename TupleTraits<B>::RefType, + typename TupleTraits<C>::RefType, + typename TupleTraits<D>::RefType> RefTuple; + typedef Tuple4<typename TupleTraits<A>::ParamType, + typename TupleTraits<B>::ParamType, + typename TupleTraits<C>::ParamType, + typename TupleTraits<D>::ParamType> ParamTuple; +}; + +template <class A, class B, class C, class D, class E> +struct TupleTypes< Tuple5<A, B, C, D, E> > { + typedef Tuple5<typename TupleTraits<A>::ValueType, + typename TupleTraits<B>::ValueType, + typename TupleTraits<C>::ValueType, + typename TupleTraits<D>::ValueType, + typename TupleTraits<E>::ValueType> ValueTuple; +typedef Tuple5<typename TupleTraits<A>::RefType, + typename TupleTraits<B>::RefType, + typename TupleTraits<C>::RefType, + typename TupleTraits<D>::RefType, + typename TupleTraits<E>::RefType> RefTuple; + typedef Tuple5<typename TupleTraits<A>::ParamType, + typename TupleTraits<B>::ParamType, + typename TupleTraits<C>::ParamType, + typename TupleTraits<D>::ParamType, + typename TupleTraits<E>::ParamType> ParamTuple; +}; + +template <class A, class B, class C, class D, class E, class F> +struct TupleTypes< Tuple6<A, B, C, D, E, F> > { + typedef Tuple6<typename TupleTraits<A>::ValueType, + typename TupleTraits<B>::ValueType, + typename TupleTraits<C>::ValueType, + typename TupleTraits<D>::ValueType, + typename TupleTraits<E>::ValueType, + typename TupleTraits<F>::ValueType> ValueTuple; +typedef Tuple6<typename TupleTraits<A>::RefType, + typename TupleTraits<B>::RefType, + typename TupleTraits<C>::RefType, + typename TupleTraits<D>::RefType, + typename TupleTraits<E>::RefType, + typename TupleTraits<F>::RefType> RefTuple; + typedef Tuple6<typename TupleTraits<A>::ParamType, + typename TupleTraits<B>::ParamType, + typename TupleTraits<C>::ParamType, + typename TupleTraits<D>::ParamType, + typename TupleTraits<E>::ParamType, + typename TupleTraits<F>::ParamType> ParamTuple; +}; + +template <class A, class B, class C, class D, class E, class F, class G> +struct TupleTypes< Tuple7<A, B, C, D, E, F, G> > { + typedef Tuple7<typename TupleTraits<A>::ValueType, + typename TupleTraits<B>::ValueType, + typename TupleTraits<C>::ValueType, + typename TupleTraits<D>::ValueType, + typename TupleTraits<E>::ValueType, + typename TupleTraits<F>::ValueType, + typename TupleTraits<G>::ValueType> ValueTuple; +typedef Tuple7<typename TupleTraits<A>::RefType, + typename TupleTraits<B>::RefType, + typename TupleTraits<C>::RefType, + typename TupleTraits<D>::RefType, + typename TupleTraits<E>::RefType, + typename TupleTraits<F>::RefType, + typename TupleTraits<G>::RefType> RefTuple; + typedef Tuple7<typename TupleTraits<A>::ParamType, + typename TupleTraits<B>::ParamType, + typename TupleTraits<C>::ParamType, + typename TupleTraits<D>::ParamType, + typename TupleTraits<E>::ParamType, + typename TupleTraits<F>::ParamType, + typename TupleTraits<G>::ParamType> ParamTuple; +}; + +template <class A, class B, class C, class D, class E, class F, class G, + class H> +struct TupleTypes< Tuple8<A, B, C, D, E, F, G, H> > { + typedef Tuple8<typename TupleTraits<A>::ValueType, + typename TupleTraits<B>::ValueType, + typename TupleTraits<C>::ValueType, + typename TupleTraits<D>::ValueType, + typename TupleTraits<E>::ValueType, + typename TupleTraits<F>::ValueType, + typename TupleTraits<G>::ValueType, + typename TupleTraits<H>::ValueType> ValueTuple; +typedef Tuple8<typename TupleTraits<A>::RefType, + typename TupleTraits<B>::RefType, + typename TupleTraits<C>::RefType, + typename TupleTraits<D>::RefType, + typename TupleTraits<E>::RefType, + typename TupleTraits<F>::RefType, + typename TupleTraits<G>::RefType, + typename TupleTraits<H>::RefType> RefTuple; + typedef Tuple8<typename TupleTraits<A>::ParamType, + typename TupleTraits<B>::ParamType, + typename TupleTraits<C>::ParamType, + typename TupleTraits<D>::ParamType, + typename TupleTraits<E>::ParamType, + typename TupleTraits<F>::ParamType, + typename TupleTraits<G>::ParamType, + typename TupleTraits<H>::ParamType> ParamTuple; +}; + +// Tuple creators ------------------------------------------------------------- +// +// Helper functions for constructing tuples while inferring the template +// argument types. + +inline Tuple0 MakeTuple() { + return Tuple0(); +} + +template <class A> +inline Tuple1<A> MakeTuple(const A& a) { + return Tuple1<A>(a); +} + +template <class A, class B> +inline Tuple2<A, B> MakeTuple(const A& a, const B& b) { + return Tuple2<A, B>(a, b); +} + +template <class A, class B, class C> +inline Tuple3<A, B, C> MakeTuple(const A& a, const B& b, const C& c) { + return Tuple3<A, B, C>(a, b, c); +} + +template <class A, class B, class C, class D> +inline Tuple4<A, B, C, D> MakeTuple(const A& a, const B& b, const C& c, + const D& d) { + return Tuple4<A, B, C, D>(a, b, c, d); +} + +template <class A, class B, class C, class D, class E> +inline Tuple5<A, B, C, D, E> MakeTuple(const A& a, const B& b, const C& c, + const D& d, const E& e) { + return Tuple5<A, B, C, D, E>(a, b, c, d, e); +} + +template <class A, class B, class C, class D, class E, class F> +inline Tuple6<A, B, C, D, E, F> MakeTuple(const A& a, const B& b, const C& c, + const D& d, const E& e, const F& f) { + return Tuple6<A, B, C, D, E, F>(a, b, c, d, e, f); +} + +template <class A, class B, class C, class D, class E, class F, class G> +inline Tuple7<A, B, C, D, E, F, G> MakeTuple(const A& a, const B& b, const C& c, + const D& d, const E& e, const F& f, + const G& g) { + return Tuple7<A, B, C, D, E, F, G>(a, b, c, d, e, f, g); +} + +template <class A, class B, class C, class D, class E, class F, class G, + class H> +inline Tuple8<A, B, C, D, E, F, G, H> MakeTuple(const A& a, const B& b, + const C& c, const D& d, + const E& e, const F& f, + const G& g, const H& h) { + return Tuple8<A, B, C, D, E, F, G, H>(a, b, c, d, e, f, g, h); +} + +// The following set of helpers make what Boost refers to as "Tiers" - a tuple +// of references. + +template <class A> +inline Tuple1<A&> MakeRefTuple(A& a) { + return Tuple1<A&>(a); +} + +template <class A, class B> +inline Tuple2<A&, B&> MakeRefTuple(A& a, B& b) { + return Tuple2<A&, B&>(a, b); +} + +template <class A, class B, class C> +inline Tuple3<A&, B&, C&> MakeRefTuple(A& a, B& b, C& c) { + return Tuple3<A&, B&, C&>(a, b, c); +} + +template <class A, class B, class C, class D> +inline Tuple4<A&, B&, C&, D&> MakeRefTuple(A& a, B& b, C& c, D& d) { + return Tuple4<A&, B&, C&, D&>(a, b, c, d); +} + +template <class A, class B, class C, class D, class E> +inline Tuple5<A&, B&, C&, D&, E&> MakeRefTuple(A& a, B& b, C& c, D& d, E& e) { + return Tuple5<A&, B&, C&, D&, E&>(a, b, c, d, e); +} + +template <class A, class B, class C, class D, class E, class F> +inline Tuple6<A&, B&, C&, D&, E&, F&> MakeRefTuple(A& a, B& b, C& c, D& d, E& e, + F& f) { + return Tuple6<A&, B&, C&, D&, E&, F&>(a, b, c, d, e, f); +} + +template <class A, class B, class C, class D, class E, class F, class G> +inline Tuple7<A&, B&, C&, D&, E&, F&, G&> MakeRefTuple(A& a, B& b, C& c, D& d, + E& e, F& f, G& g) { + return Tuple7<A&, B&, C&, D&, E&, F&, G&>(a, b, c, d, e, f, g); +} + +template <class A, class B, class C, class D, class E, class F, class G, + class H> +inline Tuple8<A&, B&, C&, D&, E&, F&, G&, H&> MakeRefTuple(A& a, B& b, C& c, + D& d, E& e, F& f, + G& g, H& h) { + return Tuple8<A&, B&, C&, D&, E&, F&, G&, H&>(a, b, c, d, e, f, g, h); +} + +// Dispatchers ---------------------------------------------------------------- +// +// Helper functions that call the given method on an object, with the unpacked +// tuple arguments. Notice that they all have the same number of arguments, +// so you need only write: +// DispatchToMethod(object, &Object::method, args); +// This is very useful for templated dispatchers, since they don't need to know +// what type |args| is. + +// Non-Static Dispatchers with no out params. + +template <class ObjT, class Method> +inline void DispatchToMethod(ObjT* obj, + Method method, + const Tuple0& /* arg */) { + (obj->*method)(); +} + +template <class ObjT, class Method, class A> +inline void DispatchToMethod(ObjT* obj, Method method, const A& arg) { + (obj->*method)(arg); +} + +template <class ObjT, class Method, class A> +inline void DispatchToMethod(ObjT* obj, Method method, const Tuple1<A>& arg) { + (obj->*method)(arg.a); +} + +template<class ObjT, class Method, class A, class B> +inline void DispatchToMethod(ObjT* obj, + Method method, + const Tuple2<A, B>& arg) { + (obj->*method)(arg.a, arg.b); +} + +template<class ObjT, class Method, class A, class B, class C> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<A, B, C>& arg) { + (obj->*method)(arg.a, arg.b, arg.c); +} + +template<class ObjT, class Method, class A, class B, class C, class D> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<A, B, C, D>& arg) { + (obj->*method)(arg.a, arg.b, arg.c, arg.d); +} + +template<class ObjT, class Method, class A, class B, class C, class D, class E> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5<A, B, C, D, E>& arg) { + (obj->*method)(arg.a, arg.b, arg.c, arg.d, arg.e); +} + +template<class ObjT, class Method, class A, class B, class C, class D, class E, + class F> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple6<A, B, C, D, E, F>& arg) { + (obj->*method)(arg.a, arg.b, arg.c, arg.d, arg.e, arg.f); +} + +template<class ObjT, class Method, class A, class B, class C, class D, class E, + class F, class G> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple7<A, B, C, D, E, F, G>& arg) { + (obj->*method)(arg.a, arg.b, arg.c, arg.d, arg.e, arg.f, arg.g); +} + +template<class ObjT, class Method, class A, class B, class C, class D, class E, + class F, class G, class H> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple8<A, B, C, D, E, F, G, H>& arg) { + (obj->*method)(arg.a, arg.b, arg.c, arg.d, arg.e, arg.f, arg.g, arg.h); +} + +// Static Dispatchers with no out params. + +template <class Function> +inline void DispatchToFunction(Function function, const Tuple0& /* arg */) { + (*function)(); +} + +template <class Function, class A> +inline void DispatchToFunction(Function function, const A& arg) { + (*function)(arg); +} + +template <class Function, class A> +inline void DispatchToFunction(Function function, const Tuple1<A>& arg) { + (*function)(arg.a); +} + +template<class Function, class A, class B> +inline void DispatchToFunction(Function function, const Tuple2<A, B>& arg) { + (*function)(arg.a, arg.b); +} + +template<class Function, class A, class B, class C> +inline void DispatchToFunction(Function function, const Tuple3<A, B, C>& arg) { + (*function)(arg.a, arg.b, arg.c); +} + +template<class Function, class A, class B, class C, class D> +inline void DispatchToFunction(Function function, + const Tuple4<A, B, C, D>& arg) { + (*function)(arg.a, arg.b, arg.c, arg.d); +} + +template<class Function, class A, class B, class C, class D, class E> +inline void DispatchToFunction(Function function, + const Tuple5<A, B, C, D, E>& arg) { + (*function)(arg.a, arg.b, arg.c, arg.d, arg.e); +} + +template<class Function, class A, class B, class C, class D, class E, class F> +inline void DispatchToFunction(Function function, + const Tuple6<A, B, C, D, E, F>& arg) { + (*function)(arg.a, arg.b, arg.c, arg.d, arg.e, arg.f); +} + +template<class Function, class A, class B, class C, class D, class E, class F, + class G> +inline void DispatchToFunction(Function function, + const Tuple7<A, B, C, D, E, F, G>& arg) { + (*function)(arg.a, arg.b, arg.c, arg.d, arg.e, arg.f, arg.g); +} + +template<class Function, class A, class B, class C, class D, class E, class F, + class G, class H> +inline void DispatchToFunction(Function function, + const Tuple8<A, B, C, D, E, F, G, H>& arg) { + (*function)(arg.a, arg.b, arg.c, arg.d, arg.e, arg.f, arg.g, arg.h); +} + +// Dispatchers with 0 out param (as a Tuple0). + +template <class ObjT, class Method> +inline void DispatchToMethod(ObjT* obj, + Method method, + const Tuple0& /* arg */, + Tuple0*) { + (obj->*method)(); +} + +template <class ObjT, class Method, class A> +inline void DispatchToMethod(ObjT* obj, Method method, const A& arg, Tuple0*) { + (obj->*method)(arg); +} + +template <class ObjT, class Method, class A> +inline void DispatchToMethod(ObjT* obj, + Method method, + const Tuple1<A>& arg, Tuple0*) { + (obj->*method)(arg.a); +} + +template<class ObjT, class Method, class A, class B> +inline void DispatchToMethod(ObjT* obj, + Method method, + const Tuple2<A, B>& arg, Tuple0*) { + (obj->*method)(arg.a, arg.b); +} + +template<class ObjT, class Method, class A, class B, class C> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<A, B, C>& arg, Tuple0*) { + (obj->*method)(arg.a, arg.b, arg.c); +} + +template<class ObjT, class Method, class A, class B, class C, class D> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<A, B, C, D>& arg, Tuple0*) { + (obj->*method)(arg.a, arg.b, arg.c, arg.d); +} + +template<class ObjT, class Method, class A, class B, class C, class D, class E> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5<A, B, C, D, E>& arg, Tuple0*) { + (obj->*method)(arg.a, arg.b, arg.c, arg.d, arg.e); +} + +template<class ObjT, class Method, class A, class B, class C, class D, class E, + class F> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple6<A, B, C, D, E, F>& arg, Tuple0*) { + (obj->*method)(arg.a, arg.b, arg.c, arg.d, arg.e, arg.f); +} + +// Dispatchers with 1 out param. + +template <class ObjT, class Method, class OutA> +inline void DispatchToMethod(ObjT* obj, + Method method, + const Tuple0& /* in */, + Tuple1<OutA>* out) { + (obj->*method)(&out->a); +} + +template<class ObjT, class Method, class InA, + class OutA> +inline void DispatchToMethod(ObjT* obj, Method method, + const InA& in, + Tuple1<OutA>* out) { + (obj->*method)(in, &out->a); +} + +template<class ObjT, class Method, class InA, + class OutA> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<InA>& in, + Tuple1<OutA>* out) { + (obj->*method)(in.a, &out->a); +} + +template<class ObjT, class Method, class InA, class InB, + class OutA> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<InA, InB>& in, + Tuple1<OutA>* out) { + (obj->*method)(in.a, in.b, &out->a); +} + +template<class ObjT, class Method, class InA, class InB, class InC, + class OutA> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<InA, InB, InC>& in, + Tuple1<OutA>* out) { + (obj->*method)(in.a, in.b, in.c, &out->a); +} + +template<class ObjT, class Method, class InA, class InB, class InC, class InD, + class OutA> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<InA, InB, InC, InD>& in, + Tuple1<OutA>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, &out->a); +} + +template<class ObjT, class Method, class InA, class InB, class InC, class InD, + class InE, class OutA> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5<InA, InB, InC, InD, InE>& in, + Tuple1<OutA>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, in.e, &out->a); +} + +template<class ObjT, class Method, + class InA, class InB, class InC, class InD, class InE, class InF, + class OutA> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple6<InA, InB, InC, InD, InE, InF>& in, + Tuple1<OutA>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, in.e, in.f, &out->a); +} + +// Dispatchers with 2 out params. + +template <class ObjT, class Method, class OutA, class OutB> +inline void DispatchToMethod(ObjT* obj, + Method method, + const Tuple0& /* in */, + Tuple2<OutA, OutB>* out) { + (obj->*method)(&out->a, &out->b); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB> +inline void DispatchToMethod(ObjT* obj, Method method, + const InA& in, + Tuple2<OutA, OutB>* out) { + (obj->*method)(in, &out->a, &out->b); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<InA>& in, + Tuple2<OutA, OutB>* out) { + (obj->*method)(in.a, &out->a, &out->b); +} + +template<class ObjT, class Method, class InA, class InB, + class OutA, class OutB> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<InA, InB>& in, + Tuple2<OutA, OutB>* out) { + (obj->*method)(in.a, in.b, &out->a, &out->b); +} + +template<class ObjT, class Method, class InA, class InB, class InC, + class OutA, class OutB> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<InA, InB, InC>& in, + Tuple2<OutA, OutB>* out) { + (obj->*method)(in.a, in.b, in.c, &out->a, &out->b); +} + +template<class ObjT, class Method, class InA, class InB, class InC, class InD, + class OutA, class OutB> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<InA, InB, InC, InD>& in, + Tuple2<OutA, OutB>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, &out->a, &out->b); +} + +template<class ObjT, class Method, + class InA, class InB, class InC, class InD, class InE, + class OutA, class OutB> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5<InA, InB, InC, InD, InE>& in, + Tuple2<OutA, OutB>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, in.e, &out->a, &out->b); +} + +template<class ObjT, class Method, + class InA, class InB, class InC, class InD, class InE, class InF, + class OutA, class OutB> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple6<InA, InB, InC, InD, InE, InF>& in, + Tuple2<OutA, OutB>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, in.e, in.f, &out->a, &out->b); +} + +// Dispatchers with 3 out params. + +template <class ObjT, class Method, class OutA, class OutB, class OutC> +inline void DispatchToMethod(ObjT* obj, + Method method, + const Tuple0& /* in */, + Tuple3<OutA, OutB, OutC>* out) { + (obj->*method)(&out->a, &out->b, &out->c); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB, class OutC> +inline void DispatchToMethod(ObjT* obj, Method method, + const InA& in, + Tuple3<OutA, OutB, OutC>* out) { + (obj->*method)(in, &out->a, &out->b, &out->c); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB, class OutC> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<InA>& in, + Tuple3<OutA, OutB, OutC>* out) { + (obj->*method)(in.a, &out->a, &out->b, &out->c); +} + +template<class ObjT, class Method, class InA, class InB, + class OutA, class OutB, class OutC> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<InA, InB>& in, + Tuple3<OutA, OutB, OutC>* out) { + (obj->*method)(in.a, in.b, &out->a, &out->b, &out->c); +} + +template<class ObjT, class Method, class InA, class InB, class InC, + class OutA, class OutB, class OutC> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<InA, InB, InC>& in, + Tuple3<OutA, OutB, OutC>* out) { + (obj->*method)(in.a, in.b, in.c, &out->a, &out->b, &out->c); +} + +template<class ObjT, class Method, class InA, class InB, class InC, class InD, + class OutA, class OutB, class OutC> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<InA, InB, InC, InD>& in, + Tuple3<OutA, OutB, OutC>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, &out->a, &out->b, &out->c); +} + +template<class ObjT, class Method, + class InA, class InB, class InC, class InD, class InE, + class OutA, class OutB, class OutC> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5<InA, InB, InC, InD, InE>& in, + Tuple3<OutA, OutB, OutC>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, in.e, &out->a, &out->b, &out->c); +} + +template<class ObjT, class Method, + class InA, class InB, class InC, class InD, class InE, class InF, + class OutA, class OutB, class OutC> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple6<InA, InB, InC, InD, InE, InF>& in, + Tuple3<OutA, OutB, OutC>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, in.e, in.f, &out->a, &out->b, &out->c); +} + +// Dispatchers with 4 out params. + +template <class ObjT, + class Method, + class OutA, + class OutB, + class OutC, + class OutD> +inline void DispatchToMethod(ObjT* obj, + Method method, + const Tuple0& /* in */, + Tuple4<OutA, OutB, OutC, OutD>* out) { + (obj->*method)(&out->a, &out->b, &out->c, &out->d); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB, class OutC, class OutD> +inline void DispatchToMethod(ObjT* obj, Method method, + const InA& in, + Tuple4<OutA, OutB, OutC, OutD>* out) { + (obj->*method)(in, &out->a, &out->b, &out->c, &out->d); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB, class OutC, class OutD> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<InA>& in, + Tuple4<OutA, OutB, OutC, OutD>* out) { + (obj->*method)(in.a, &out->a, &out->b, &out->c, &out->d); +} + +template<class ObjT, class Method, class InA, class InB, + class OutA, class OutB, class OutC, class OutD> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<InA, InB>& in, + Tuple4<OutA, OutB, OutC, OutD>* out) { + (obj->*method)(in.a, in.b, &out->a, &out->b, &out->c, &out->d); +} + +template<class ObjT, class Method, class InA, class InB, class InC, + class OutA, class OutB, class OutC, class OutD> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<InA, InB, InC>& in, + Tuple4<OutA, OutB, OutC, OutD>* out) { + (obj->*method)(in.a, in.b, in.c, &out->a, &out->b, &out->c, &out->d); +} + +template<class ObjT, class Method, class InA, class InB, class InC, class InD, + class OutA, class OutB, class OutC, class OutD> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<InA, InB, InC, InD>& in, + Tuple4<OutA, OutB, OutC, OutD>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, &out->a, &out->b, &out->c, &out->d); +} + +template<class ObjT, class Method, + class InA, class InB, class InC, class InD, class InE, + class OutA, class OutB, class OutC, class OutD> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5<InA, InB, InC, InD, InE>& in, + Tuple4<OutA, OutB, OutC, OutD>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, in.e, + &out->a, &out->b, &out->c, &out->d); +} + +template<class ObjT, class Method, + class InA, class InB, class InC, class InD, class InE, class InF, + class OutA, class OutB, class OutC, class OutD> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple6<InA, InB, InC, InD, InE, InF>& in, + Tuple4<OutA, OutB, OutC, OutD>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, in.e, in.f, + &out->a, &out->b, &out->c, &out->d); +} + +// Dispatchers with 5 out params. + +template <class ObjT, + class Method, + class OutA, + class OutB, + class OutC, + class OutD, + class OutE> +inline void DispatchToMethod(ObjT* obj, + Method method, + const Tuple0& /* in */, + Tuple5<OutA, OutB, OutC, OutD, OutE>* out) { + (obj->*method)(&out->a, &out->b, &out->c, &out->d, &out->e); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB, class OutC, class OutD, class OutE> +inline void DispatchToMethod(ObjT* obj, Method method, + const InA& in, + Tuple5<OutA, OutB, OutC, OutD, OutE>* out) { + (obj->*method)(in, &out->a, &out->b, &out->c, &out->d, &out->e); +} + +template<class ObjT, class Method, class InA, + class OutA, class OutB, class OutC, class OutD, class OutE> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<InA>& in, + Tuple5<OutA, OutB, OutC, OutD, OutE>* out) { + (obj->*method)(in.a, &out->a, &out->b, &out->c, &out->d, &out->e); +} + +template<class ObjT, class Method, class InA, class InB, + class OutA, class OutB, class OutC, class OutD, class OutE> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<InA, InB>& in, + Tuple5<OutA, OutB, OutC, OutD, OutE>* out) { + (obj->*method)(in.a, in.b, &out->a, &out->b, &out->c, &out->d, &out->e); +} + +template<class ObjT, class Method, class InA, class InB, class InC, + class OutA, class OutB, class OutC, class OutD, class OutE> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<InA, InB, InC>& in, + Tuple5<OutA, OutB, OutC, OutD, OutE>* out) { + (obj->*method)(in.a, in.b, in.c, &out->a, &out->b, &out->c, &out->d, &out->e); +} + +template<class ObjT, class Method, class InA, class InB, class InC, class InD, + class OutA, class OutB, class OutC, class OutD, class OutE> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<InA, InB, InC, InD>& in, + Tuple5<OutA, OutB, OutC, OutD, OutE>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, &out->a, &out->b, &out->c, &out->d, + &out->e); +} + +template<class ObjT, class Method, + class InA, class InB, class InC, class InD, class InE, + class OutA, class OutB, class OutC, class OutD, class OutE> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple5<InA, InB, InC, InD, InE>& in, + Tuple5<OutA, OutB, OutC, OutD, OutE>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, in.e, + &out->a, &out->b, &out->c, &out->d, &out->e); +} + +template<class ObjT, class Method, + class InA, class InB, class InC, class InD, class InE, class InF, + class OutA, class OutB, class OutC, class OutD, class OutE> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple6<InA, InB, InC, InD, InE, InF>& in, + Tuple5<OutA, OutB, OutC, OutD, OutE>* out) { + (obj->*method)(in.a, in.b, in.c, in.d, in.e, in.f, + &out->a, &out->b, &out->c, &out->d, &out->e); +} + +#endif // BASE_TUPLE_H__
diff --git a/src/base/tuple_unittest.cc b/src/base/tuple_unittest.cc new file mode 100644 index 0000000..402394c --- /dev/null +++ b/src/base/tuple_unittest.cc
@@ -0,0 +1,128 @@ +// Copyright (c) 2006-2008 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/tuple.h" + +#include "base/compiler_specific.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +void DoAdd(int a, int b, int c, int* res) { + *res = a + b + c; +} + +struct Addy { + Addy() { } + void DoAdd(int a, int b, int c, int d, int* res) { + *res = a + b + c + d; + } +}; + +struct Addz { + Addz() { } + void DoAdd(int a, int b, int c, int d, int e, int* res) { + *res = a + b + c + d + e; + } +}; + +} // namespace + +TEST(TupleTest, Basic) { + Tuple0 t0 ALLOW_UNUSED = MakeTuple(); + Tuple1<int> t1(1); + Tuple2<int, const char*> t2 = MakeTuple(1, static_cast<const char*>("wee")); + Tuple3<int, int, int> t3(1, 2, 3); + Tuple4<int, int, int, int*> t4(1, 2, 3, &t1.a); + Tuple5<int, int, int, int, int*> t5(1, 2, 3, 4, &t4.a); + Tuple6<int, int, int, int, int, int*> t6(1, 2, 3, 4, 5, &t4.a); + + EXPECT_EQ(1, t1.a); + EXPECT_EQ(1, t2.a); + EXPECT_EQ(1, t3.a); + EXPECT_EQ(2, t3.b); + EXPECT_EQ(3, t3.c); + EXPECT_EQ(1, t4.a); + EXPECT_EQ(2, t4.b); + EXPECT_EQ(3, t4.c); + EXPECT_EQ(1, t5.a); + EXPECT_EQ(2, t5.b); + EXPECT_EQ(3, t5.c); + EXPECT_EQ(4, t5.d); + EXPECT_EQ(1, t6.a); + EXPECT_EQ(2, t6.b); + EXPECT_EQ(3, t6.c); + EXPECT_EQ(4, t6.d); + EXPECT_EQ(5, t6.e); + + EXPECT_EQ(1, t1.a); + DispatchToFunction(&DoAdd, t4); + EXPECT_EQ(6, t1.a); + + int res = 0; + DispatchToFunction(&DoAdd, MakeTuple(9, 8, 7, &res)); + EXPECT_EQ(24, res); + + Addy addy; + EXPECT_EQ(1, t4.a); + DispatchToMethod(&addy, &Addy::DoAdd, t5); + EXPECT_EQ(10, t4.a); + + Addz addz; + EXPECT_EQ(10, t4.a); + DispatchToMethod(&addz, &Addz::DoAdd, t6); + EXPECT_EQ(15, t4.a); +} + +namespace { + +struct CopyLogger { + CopyLogger() { ++TimesConstructed; } + CopyLogger(const CopyLogger& tocopy) { ++TimesConstructed; ++TimesCopied; } + ~CopyLogger() { } + + static int TimesCopied; + static int TimesConstructed; +}; + +void SomeLoggerMethRef(const CopyLogger& logy, const CopyLogger* ptr, bool* b) { + *b = &logy == ptr; +} + +void SomeLoggerMethCopy(CopyLogger logy, const CopyLogger* ptr, bool* b) { + *b = &logy == ptr; +} + +int CopyLogger::TimesCopied = 0; +int CopyLogger::TimesConstructed = 0; + +} // namespace + +TEST(TupleTest, Copying) { + CopyLogger logger; + EXPECT_EQ(0, CopyLogger::TimesCopied); + EXPECT_EQ(1, CopyLogger::TimesConstructed); + + bool res = false; + + // Creating the tuple should copy the class to store internally in the tuple. + Tuple3<CopyLogger, CopyLogger*, bool*> tuple(logger, &logger, &res); + tuple.b = &tuple.a; + EXPECT_EQ(2, CopyLogger::TimesConstructed); + EXPECT_EQ(1, CopyLogger::TimesCopied); + + // Our internal Logger and the one passed to the function should be the same. + res = false; + DispatchToFunction(&SomeLoggerMethRef, tuple); + EXPECT_TRUE(res); + EXPECT_EQ(2, CopyLogger::TimesConstructed); + EXPECT_EQ(1, CopyLogger::TimesCopied); + + // Now they should be different, since the function call will make a copy. + res = false; + DispatchToFunction(&SomeLoggerMethCopy, tuple); + EXPECT_FALSE(res); + EXPECT_EQ(3, CopyLogger::TimesConstructed); + EXPECT_EQ(2, CopyLogger::TimesCopied); +}
diff --git a/src/base/utf_offset_string_conversions.cc b/src/base/utf_offset_string_conversions.cc new file mode 100644 index 0000000..8405303 --- /dev/null +++ b/src/base/utf_offset_string_conversions.cc
@@ -0,0 +1,167 @@ +// 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/utf_offset_string_conversions.h" + +#include <algorithm> + +#include "base/memory/scoped_ptr.h" +#include "base/string_piece.h" +#include "base/utf_string_conversion_utils.h" + +using base::PrepareForUTF16Or32Output; +using base::PrepareForUTF8Output; +using base::ReadUnicodeCharacter; +using base::WriteUnicodeCharacter; + +// Converts the given source Unicode character type to the given destination +// Unicode character type as a STL string. The given input buffer and size +// determine the source, and the given output STL string will be replaced by +// the result. +template<typename SrcChar, typename DestStdString> +bool ConvertUnicode(const SrcChar* src, + size_t src_len, + DestStdString* output, + std::vector<size_t>* offsets_for_adjustment) { + if (offsets_for_adjustment) { + std::for_each(offsets_for_adjustment->begin(), + offsets_for_adjustment->end(), + LimitOffset<DestStdString>(src_len)); + } + + // ICU requires 32-bit numbers. + bool success = true; + OffsetAdjuster offset_adjuster(offsets_for_adjustment); + int32 src_len32 = static_cast<int32>(src_len); + for (int32 i = 0; i < src_len32; i++) { + uint32 code_point; + size_t original_i = i; + size_t chars_written = 0; + if (ReadUnicodeCharacter(src, src_len32, &i, &code_point)) { + chars_written = WriteUnicodeCharacter(code_point, output); + } else { + chars_written = WriteUnicodeCharacter(0xFFFD, output); + success = false; + } + if (offsets_for_adjustment) { + // NOTE: ReadUnicodeCharacter() adjusts |i| to point _at_ the last + // character read, not after it (so that incrementing it in the loop + // increment will place it at the right location), so we need to account + // for that in determining the amount that was read. + offset_adjuster.Add(OffsetAdjuster::Adjustment(original_i, + i - original_i + 1, chars_written)); + } + } + return success; +} + +bool UTF8ToUTF16AndAdjustOffset(const char* src, + size_t src_len, + string16* output, + size_t* offset_for_adjustment) { + std::vector<size_t> offsets; + if (offset_for_adjustment) + offsets.push_back(*offset_for_adjustment); + PrepareForUTF16Or32Output(src, src_len, output); + bool ret = ConvertUnicode(src, src_len, output, &offsets); + if (offset_for_adjustment) + *offset_for_adjustment = offsets[0]; + return ret; +} + +bool UTF8ToUTF16AndAdjustOffsets(const char* src, + size_t src_len, + string16* output, + std::vector<size_t>* offsets_for_adjustment) { + PrepareForUTF16Or32Output(src, src_len, output); + return ConvertUnicode(src, src_len, output, offsets_for_adjustment); +} + +string16 UTF8ToUTF16AndAdjustOffset(const base::StringPiece& utf8, + size_t* offset_for_adjustment) { + std::vector<size_t> offsets; + if (offset_for_adjustment) + offsets.push_back(*offset_for_adjustment); + string16 result; + UTF8ToUTF16AndAdjustOffsets(utf8.data(), utf8.length(), &result, + &offsets); + if (offset_for_adjustment) + *offset_for_adjustment = offsets[0]; + return result; +} + +string16 UTF8ToUTF16AndAdjustOffsets( + const base::StringPiece& utf8, + std::vector<size_t>* offsets_for_adjustment) { + string16 result; + UTF8ToUTF16AndAdjustOffsets(utf8.data(), utf8.length(), &result, + offsets_for_adjustment); + return result; +} + +std::string UTF16ToUTF8AndAdjustOffset( + const base::StringPiece16& utf16, + size_t* offset_for_adjustment) { + std::vector<size_t> offsets; + if (offset_for_adjustment) + offsets.push_back(*offset_for_adjustment); + std::string result = UTF16ToUTF8AndAdjustOffsets(utf16, &offsets); + if (offset_for_adjustment) + *offset_for_adjustment = offsets[0]; + return result; +} + +std::string UTF16ToUTF8AndAdjustOffsets( + const base::StringPiece16& utf16, + std::vector<size_t>* offsets_for_adjustment) { + std::string result; + PrepareForUTF8Output(utf16.data(), utf16.length(), &result); + ConvertUnicode(utf16.data(), utf16.length(), &result, offsets_for_adjustment); + return result; +} + +OffsetAdjuster::Adjustment::Adjustment(size_t original_offset, + size_t original_length, + size_t output_length) + : original_offset(original_offset), + original_length(original_length), + output_length(output_length) { +} + +OffsetAdjuster::OffsetAdjuster(std::vector<size_t>* offsets_for_adjustment) + : offsets_for_adjustment_(offsets_for_adjustment) { +} + +OffsetAdjuster::~OffsetAdjuster() { + if (!offsets_for_adjustment_ || adjustments_.empty()) + return; + for (std::vector<size_t>::iterator i(offsets_for_adjustment_->begin()); + i != offsets_for_adjustment_->end(); ++i) + AdjustOffset(i); +} + +void OffsetAdjuster::Add(const Adjustment& adjustment) { + adjustments_.push_back(adjustment); +} + +void OffsetAdjuster::AdjustOffset(std::vector<size_t>::iterator offset) { + if (*offset == string16::npos) + return; + size_t adjustment = 0; + for (std::vector<Adjustment>::const_iterator i = adjustments_.begin(); + i != adjustments_.end(); ++i) { + if (*offset == i->original_offset && i->output_length == 0) { + *offset = string16::npos; + return; + } + if (*offset <= i->original_offset) + break; + if (*offset < (i->original_offset + i->original_length)) { + *offset = string16::npos; + return; + } + adjustment += (i->original_length - i->output_length); + } + *offset -= adjustment; +}
diff --git a/src/base/utf_offset_string_conversions.h b/src/base/utf_offset_string_conversions.h new file mode 100644 index 0000000..e9cb558 --- /dev/null +++ b/src/base/utf_offset_string_conversions.h
@@ -0,0 +1,89 @@ +// 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. + +#ifndef BASE_UTF_OFFSET_STRING_CONVERSIONS_H_ +#define BASE_UTF_OFFSET_STRING_CONVERSIONS_H_ + +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/string16.h" +#include "base/string_piece.h" + +// Like the conversions in utf_string_conversions.h, but also takes one or more +// offsets (|offset[s]_for_adjustment|) into the source strings, each offset +// will be adjusted to point at the same logical place in the result strings. +// If this isn't possible because an offset points past the end of the source +// strings or into the middle of a multibyte sequence, the offending offset will +// be set to string16::npos. |offset[s]_for_adjustment| may be NULL. +BASE_EXPORT bool UTF8ToUTF16AndAdjustOffset(const char* src, + size_t src_len, + string16* output, + size_t* offset_for_adjustment); +BASE_EXPORT bool UTF8ToUTF16AndAdjustOffsets( + const char* src, + size_t src_len, + string16* output, + std::vector<size_t>* offsets_for_adjustment); + +BASE_EXPORT string16 UTF8ToUTF16AndAdjustOffset(const base::StringPiece& utf8, + size_t* offset_for_adjustment); +BASE_EXPORT string16 UTF8ToUTF16AndAdjustOffsets( + const base::StringPiece& utf8, + std::vector<size_t>* offsets_for_adjustment); + +BASE_EXPORT std::string UTF16ToUTF8AndAdjustOffset( + const base::StringPiece16& utf16, + size_t* offset_for_adjustment); +BASE_EXPORT std::string UTF16ToUTF8AndAdjustOffsets( + const base::StringPiece16& utf16, + std::vector<size_t>* offsets_for_adjustment); + +// Limiting function callable by std::for_each which will replace any value +// which is equal to or greater than |limit| with npos. +template <typename T> +struct LimitOffset { + explicit LimitOffset(size_t limit) + : limit_(limit) {} + + void operator()(size_t& offset) { + if (offset >= limit_) + offset = T::npos; + } + + size_t limit_; +}; + +// Stack object which, on destruction, will update a vector of offsets based on +// any supplied adjustments. To use, declare one of these, providing the +// address of the offset vector to adjust. Then Add() any number of Adjustments +// (each Adjustment gives the |original_offset| of a substring and the lengths +// of the substring before and after transforming). When the OffsetAdjuster +// goes out of scope, all the offsets in the provided vector will be updated. +class BASE_EXPORT OffsetAdjuster { + public: + struct BASE_EXPORT Adjustment { + Adjustment(size_t original_offset, + size_t original_length, + size_t output_length); + + size_t original_offset; + size_t original_length; + size_t output_length; + }; + + explicit OffsetAdjuster(std::vector<size_t>* offsets_for_adjustment); + ~OffsetAdjuster(); + + void Add(const Adjustment& adjustment); + + private: + void AdjustOffset(std::vector<size_t>::iterator offset); + + std::vector<size_t>* offsets_for_adjustment_; + std::vector<Adjustment> adjustments_; +}; + +#endif // BASE_UTF_OFFSET_STRING_CONVERSIONS_H_
diff --git a/src/base/utf_offset_string_conversions_unittest.cc b/src/base/utf_offset_string_conversions_unittest.cc new file mode 100644 index 0000000..ff03a75 --- /dev/null +++ b/src/base/utf_offset_string_conversions_unittest.cc
@@ -0,0 +1,154 @@ +// 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 <algorithm> + +#include "base/logging.h" +#include "base/string_piece.h" +#include "base/utf_offset_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +static const size_t kNpos = string16::npos; + +} // namespace + +TEST(UTFOffsetStringConversionsTest, AdjustOffset) { + struct UTF8ToUTF16Case { + const char* utf8; + size_t input_offset; + size_t output_offset; + } utf8_to_utf16_cases[] = { + {"", 0, kNpos}, + {"\xe4\xbd\xa0\xe5\xa5\xbd", 1, kNpos}, + {"\xe4\xbd\xa0\xe5\xa5\xbd", 3, 1}, + {"\xed\xb0\x80z", 3, 1}, + {"A\xF0\x90\x8C\x80z", 1, 1}, + {"A\xF0\x90\x8C\x80z", 2, kNpos}, + {"A\xF0\x90\x8C\x80z", 5, 3}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(utf8_to_utf16_cases); ++i) { + size_t offset = utf8_to_utf16_cases[i].input_offset; + UTF8ToUTF16AndAdjustOffset(utf8_to_utf16_cases[i].utf8, &offset); + EXPECT_EQ(utf8_to_utf16_cases[i].output_offset, offset); + } + + struct UTF16ToUTF8Case { + char16 utf16[10]; + size_t input_offset; + size_t output_offset; + } utf16_to_utf8_cases[] = { + {{}, 0, kNpos}, + // Converted to 3-byte utf-8 sequences + {{0x5909, 0x63DB}, 2, kNpos}, + {{0x5909, 0x63DB}, 1, 3}, + // Converted to 2-byte utf-8 sequences + {{'A', 0x00bc, 0x00be, 'z'}, 1, 1}, + {{'A', 0x00bc, 0x00be, 'z'}, 2, 3}, + {{'A', 0x00bc, 0x00be, 'z'}, 3, 5}, + // Surrogate pair + {{'A', 0xd800, 0xdf00, 'z'}, 1, 1}, + {{'A', 0xd800, 0xdf00, 'z'}, 2, kNpos}, + {{'A', 0xd800, 0xdf00, 'z'}, 3, 5}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(utf16_to_utf8_cases); ++i) { + size_t offset = utf16_to_utf8_cases[i].input_offset; + UTF16ToUTF8AndAdjustOffset(utf16_to_utf8_cases[i].utf16, &offset); + EXPECT_EQ(utf16_to_utf8_cases[i].output_offset, offset); + } +} + +TEST(UTFOffsetStringConversionsTest, LimitOffsets) { + const size_t kLimit = 10; + const size_t kItems = 20; + std::vector<size_t> size_ts; + for (size_t t = 0; t < kItems; ++t) + size_ts.push_back(t); + std::for_each(size_ts.begin(), size_ts.end(), + LimitOffset<string16>(kLimit)); + size_t unlimited_count = 0; + for (std::vector<size_t>::iterator ti = size_ts.begin(); ti != size_ts.end(); + ++ti) { + if (*ti < kLimit && *ti != kNpos) + ++unlimited_count; + } + EXPECT_EQ(10U, unlimited_count); + + // Reverse the values in the vector and try again. + size_ts.clear(); + for (size_t t = kItems; t > 0; --t) + size_ts.push_back(t - 1); + std::for_each(size_ts.begin(), size_ts.end(), + LimitOffset<string16>(kLimit)); + unlimited_count = 0; + for (std::vector<size_t>::iterator ti = size_ts.begin(); ti != size_ts.end(); + ++ti) { + if (*ti < kLimit && *ti != kNpos) + ++unlimited_count; + } + EXPECT_EQ(10U, unlimited_count); +} + +TEST(UTFOffsetStringConversionsTest, AdjustOffsets) { + // Imagine we have strings as shown in the following cases where the + // X's represent encoded characters. + // 1: abcXXXdef ==> abcXdef + { + std::vector<size_t> offsets; + for (size_t t = 0; t < 9; ++t) + offsets.push_back(t); + { + OffsetAdjuster offset_adjuster(&offsets); + offset_adjuster.Add(OffsetAdjuster::Adjustment(3, 3, 1)); + } + size_t expected_1[] = {0, 1, 2, 3, kNpos, kNpos, 4, 5, 6}; + EXPECT_EQ(offsets.size(), arraysize(expected_1)); + for (size_t i = 0; i < arraysize(expected_1); ++i) + EXPECT_EQ(expected_1[i], offsets[i]); + } + + // 2: XXXaXXXXbcXXXXXXXdefXXX ==> XaXXbcXXXXdefX + { + std::vector<size_t> offsets; + for (size_t t = 0; t < 23; ++t) + offsets.push_back(t); + { + OffsetAdjuster offset_adjuster(&offsets); + offset_adjuster.Add(OffsetAdjuster::Adjustment(0, 3, 1)); + offset_adjuster.Add(OffsetAdjuster::Adjustment(4, 4, 2)); + offset_adjuster.Add(OffsetAdjuster::Adjustment(10, 7, 4)); + offset_adjuster.Add(OffsetAdjuster::Adjustment(20, 3, 1)); + } + size_t expected_2[] = {0, kNpos, kNpos, 1, 2, kNpos, kNpos, kNpos, 4, 5, 6, + kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, 10, 11, 12, + 13, kNpos, kNpos}; + EXPECT_EQ(offsets.size(), arraysize(expected_2)); + for (size_t i = 0; i < arraysize(expected_2); ++i) + EXPECT_EQ(expected_2[i], offsets[i]); + } + + // 3: XXXaXXXXbcdXXXeXX ==> aXXXXbcdXXXe + { + std::vector<size_t> offsets; + for (size_t t = 0; t < 17; ++t) + offsets.push_back(t); + { + OffsetAdjuster offset_adjuster(&offsets); + offset_adjuster.Add(OffsetAdjuster::Adjustment(0, 3, 0)); + offset_adjuster.Add(OffsetAdjuster::Adjustment(4, 4, 4)); + offset_adjuster.Add(OffsetAdjuster::Adjustment(11, 3, 3)); + offset_adjuster.Add(OffsetAdjuster::Adjustment(15, 2, 0)); + } + size_t expected_3[] = {kNpos, kNpos, kNpos, 0, 1, kNpos, kNpos, kNpos, 5, 6, + 7, 8, kNpos, kNpos, 11, kNpos, kNpos}; + EXPECT_EQ(offsets.size(), arraysize(expected_3)); + for (size_t i = 0; i < arraysize(expected_3); ++i) + EXPECT_EQ(expected_3[i], offsets[i]); + } +} + +} // namaspace base
diff --git a/src/base/utf_string_conversion_utils.cc b/src/base/utf_string_conversion_utils.cc new file mode 100644 index 0000000..019df50 --- /dev/null +++ b/src/base/utf_string_conversion_utils.cc
@@ -0,0 +1,152 @@ +// Copyright (c) 2009 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/utf_string_conversion_utils.h" + +#include "base/third_party/icu/icu_utf.h" + +namespace base { + +// ReadUnicodeCharacter -------------------------------------------------------- + +bool ReadUnicodeCharacter(const char* src, + int32 src_len, + int32* char_index, + uint32* code_point_out) { + // U8_NEXT expects to be able to use -1 to signal an error, so we must + // use a signed type for code_point. But this function returns false + // on error anyway, so code_point_out is unsigned. + int32 code_point; + CBU8_NEXT(src, *char_index, src_len, code_point); + *code_point_out = static_cast<uint32>(code_point); + + // The ICU macro above moves to the next char, we want to point to the last + // char consumed. + (*char_index)--; + + // Validate the decoded value. + return IsValidCodepoint(code_point); +} + +bool ReadUnicodeCharacter(const char16* src, + int32 src_len, + int32* char_index, + uint32* code_point) { + if (CBU16_IS_SURROGATE(src[*char_index])) { + if (!CBU16_IS_SURROGATE_LEAD(src[*char_index]) || + *char_index + 1 >= src_len || + !CBU16_IS_TRAIL(src[*char_index + 1])) { + // Invalid surrogate pair. + return false; + } + + // Valid surrogate pair. + *code_point = CBU16_GET_SUPPLEMENTARY(src[*char_index], + src[*char_index + 1]); + (*char_index)++; + } else { + // Not a surrogate, just one 16-bit word. + *code_point = src[*char_index]; + } + + return IsValidCodepoint(*code_point); +} + +#if defined(WCHAR_T_IS_UTF32) +bool ReadUnicodeCharacter(const wchar_t* src, + int32 src_len, + int32* char_index, + uint32* code_point) { + // Conversion is easy since the source is 32-bit. + *code_point = src[*char_index]; + + // Validate the value. + return IsValidCodepoint(*code_point); +} +#endif // defined(WCHAR_T_IS_UTF32) + +// WriteUnicodeCharacter ------------------------------------------------------- + +size_t WriteUnicodeCharacter(uint32 code_point, std::string* output) { + if (code_point <= 0x7f) { + // Fast path the common case of one byte. + output->push_back(code_point); + return 1; + } + + + // CBU8_APPEND_UNSAFE can append up to 4 bytes. + size_t char_offset = output->length(); + size_t original_char_offset = char_offset; + output->resize(char_offset + CBU8_MAX_LENGTH); + + CBU8_APPEND_UNSAFE(&(*output)[0], char_offset, code_point); + + // CBU8_APPEND_UNSAFE will advance our pointer past the inserted character, so + // it will represent the new length of the string. + output->resize(char_offset); + return char_offset - original_char_offset; +} + +size_t WriteUnicodeCharacter(uint32 code_point, string16* output) { + if (CBU16_LENGTH(code_point) == 1) { + // Thie code point is in the Basic Multilingual Plane (BMP). + output->push_back(static_cast<char16>(code_point)); + return 1; + } + // Non-BMP characters use a double-character encoding. + size_t char_offset = output->length(); + output->resize(char_offset + CBU16_MAX_LENGTH); + CBU16_APPEND_UNSAFE(&(*output)[0], char_offset, code_point); + return CBU16_MAX_LENGTH; +} + +// Generalized Unicode converter ----------------------------------------------- + +template<typename CHAR> +void PrepareForUTF8Output(const CHAR* src, + size_t src_len, + std::string* output) { + output->clear(); + if (src_len == 0) + return; + if (src[0] < 0x80) { + // Assume that the entire input will be ASCII. + output->reserve(src_len); + } else { + // Assume that the entire input is non-ASCII and will have 3 bytes per char. + output->reserve(src_len * 3); + } +} + +// Instantiate versions we know callers will need. +template void PrepareForUTF8Output(const wchar_t*, size_t, std::string*); +#if defined(WCHAR_T_IS_UTF32) +template void PrepareForUTF8Output(const char16*, size_t, std::string*); +#endif + +template<typename STRING> +void PrepareForUTF16Or32Output(const char* src, + size_t src_len, + STRING* output) { + output->clear(); + if (src_len == 0) + return; + if (static_cast<unsigned char>(src[0]) < 0x80) { + // Assume the input is all ASCII, which means 1:1 correspondence. + output->reserve(src_len); + } else { + // Otherwise assume that the UTF-8 sequences will have 2 bytes for each + // character. + output->reserve(src_len / 2); + } +} + +// Instantiate versions we know callers will need. +template void PrepareForUTF16Or32Output(const char*, size_t, std::wstring*); +#if defined(WCHAR_T_IS_UTF32) +template void PrepareForUTF16Or32Output(const char*, size_t, string16*); +#endif + +} // namespace base
diff --git a/src/base/utf_string_conversion_utils.h b/src/base/utf_string_conversion_utils.h new file mode 100644 index 0000000..17e04f1 --- /dev/null +++ b/src/base/utf_string_conversion_utils.h
@@ -0,0 +1,97 @@ +// 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. + +#ifndef BASE_UTF_STRING_CONVERSION_UTILS_H_ +#define BASE_UTF_STRING_CONVERSION_UTILS_H_ + +// This should only be used by the various UTF string conversion files. + +#include "base/base_export.h" +#include "base/string16.h" + +namespace base { + +inline bool IsValidCodepoint(uint32 code_point) { + // Excludes the surrogate code points ([0xD800, 0xDFFF]) and + // codepoints larger than 0x10FFFF (the highest codepoint allowed). + // Non-characters and unassigned codepoints are allowed. + return code_point < 0xD800u || + (code_point >= 0xE000u && code_point <= 0x10FFFFu); +} + +inline bool IsValidCharacter(uint32 code_point) { + // Excludes non-characters (U+FDD0..U+FDEF, and all codepoints ending in + // 0xFFFE or 0xFFFF) from the set of valid code points. + return code_point < 0xD800u || (code_point >= 0xE000u && + code_point < 0xFDD0u) || (code_point > 0xFDEFu && + code_point <= 0x10FFFFu && (code_point & 0xFFFEu) != 0xFFFEu); +} + +// ReadUnicodeCharacter -------------------------------------------------------- + +// Reads a UTF-8 stream, placing the next code point into the given output +// |*code_point|. |src| represents the entire string to read, and |*char_index| +// is the character offset within the string to start reading at. |*char_index| +// will be updated to index the last character read, such that incrementing it +// (as in a for loop) will take the reader to the next character. +// +// Returns true on success. On false, |*code_point| will be invalid. +BASE_EXPORT bool ReadUnicodeCharacter(const char* src, + int32 src_len, + int32* char_index, + uint32* code_point_out); + +// Reads a UTF-16 character. The usage is the same as the 8-bit version above. +BASE_EXPORT bool ReadUnicodeCharacter(const char16* src, + int32 src_len, + int32* char_index, + uint32* code_point); + +#if defined(WCHAR_T_IS_UTF32) +// Reads UTF-32 character. The usage is the same as the 8-bit version above. +BASE_EXPORT bool ReadUnicodeCharacter(const wchar_t* src, + int32 src_len, + int32* char_index, + uint32* code_point); +#endif // defined(WCHAR_T_IS_UTF32) + +// WriteUnicodeCharacter ------------------------------------------------------- + +// Appends a UTF-8 character to the given 8-bit string. Returns the number of +// bytes written. +// TODO(brettw) Bug 79631: This function should not be exposed. +BASE_EXPORT size_t WriteUnicodeCharacter(uint32 code_point, + std::string* output); + +// Appends the given code point as a UTF-16 character to the given 16-bit +// string. Returns the number of 16-bit values written. +BASE_EXPORT size_t WriteUnicodeCharacter(uint32 code_point, string16* output); + +#if defined(WCHAR_T_IS_UTF32) +// Appends the given UTF-32 character to the given 32-bit string. Returns the +// number of 32-bit values written. +inline size_t WriteUnicodeCharacter(uint32 code_point, std::wstring* output) { + // This is the easy case, just append the character. + output->push_back(code_point); + return 1; +} +#endif // defined(WCHAR_T_IS_UTF32) + +// Generalized Unicode converter ----------------------------------------------- + +// Guesses the length of the output in UTF-8 in bytes, clears that output +// string, and reserves that amount of space. We assume that the input +// character types are unsigned, which will be true for UTF-16 and -32 on our +// systems. +template<typename CHAR> +void PrepareForUTF8Output(const CHAR* src, size_t src_len, std::string* output); + +// Prepares an output buffer (containing either UTF-16 or -32 data) given some +// UTF-8 input that will be converted to it. See PrepareForUTF8Output(). +template<typename STRING> +void PrepareForUTF16Or32Output(const char* src, size_t src_len, STRING* output); + +} // namespace base + +#endif // BASE_UTF_STRING_CONVERSION_UTILS_H_
diff --git a/src/base/utf_string_conversions.cc b/src/base/utf_string_conversions.cc new file mode 100644 index 0000000..7b73696 --- /dev/null +++ b/src/base/utf_string_conversions.cc
@@ -0,0 +1,186 @@ +// Copyright (c) 2010 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/utf_string_conversions.h" + +#include "base/string_piece.h" +#include "base/string_util.h" +#include "base/utf_string_conversion_utils.h" + +using base::PrepareForUTF8Output; +using base::PrepareForUTF16Or32Output; +using base::ReadUnicodeCharacter; +using base::WriteUnicodeCharacter; + +namespace { + +// Generalized Unicode converter ----------------------------------------------- + +// Converts the given source Unicode character type to the given destination +// Unicode character type as a STL string. The given input buffer and size +// determine the source, and the given output STL string will be replaced by +// the result. +template<typename SRC_CHAR, typename DEST_STRING> +bool ConvertUnicode(const SRC_CHAR* src, + size_t src_len, + DEST_STRING* output) { + // ICU requires 32-bit numbers. + bool success = true; + int32 src_len32 = static_cast<int32>(src_len); + for (int32 i = 0; i < src_len32; i++) { + uint32 code_point; + if (ReadUnicodeCharacter(src, src_len32, &i, &code_point)) { + WriteUnicodeCharacter(code_point, output); + } else { + WriteUnicodeCharacter(0xFFFD, output); + success = false; + } + } + + return success; +} + +} // namespace + +// UTF-8 <-> Wide -------------------------------------------------------------- + +bool WideToUTF8(const wchar_t* src, size_t src_len, std::string* output) { + PrepareForUTF8Output(src, src_len, output); + return ConvertUnicode(src, src_len, output); +} + +std::string WideToUTF8(const std::wstring& wide) { + std::string ret; + // Ignore the success flag of this call, it will do the best it can for + // invalid input, which is what we want here. + WideToUTF8(wide.data(), wide.length(), &ret); + return ret; +} + +bool UTF8ToWide(const char* src, size_t src_len, std::wstring* output) { + PrepareForUTF16Or32Output(src, src_len, output); + return ConvertUnicode(src, src_len, output); +} + +std::wstring UTF8ToWide(const base::StringPiece& utf8) { + std::wstring ret; + UTF8ToWide(utf8.data(), utf8.length(), &ret); + return ret; +} + +// UTF-16 <-> Wide ------------------------------------------------------------- + +#if defined(WCHAR_T_IS_UTF16) + +// When wide == UTF-16, then conversions are a NOP. +bool WideToUTF16(const wchar_t* src, size_t src_len, string16* output) { + output->assign(src, src_len); + return true; +} + +string16 WideToUTF16(const std::wstring& wide) { + return wide; +} + +bool UTF16ToWide(const char16* src, size_t src_len, std::wstring* output) { + output->assign(src, src_len); + return true; +} + +std::wstring UTF16ToWide(const string16& utf16) { + return utf16; +} + +#elif defined(WCHAR_T_IS_UTF32) + +bool WideToUTF16(const wchar_t* src, size_t src_len, string16* output) { + output->clear(); + // Assume that normally we won't have any non-BMP characters so the counts + // will be the same. + output->reserve(src_len); + return ConvertUnicode(src, src_len, output); +} + +string16 WideToUTF16(const std::wstring& wide) { + string16 ret; + WideToUTF16(wide.data(), wide.length(), &ret); + return ret; +} + +bool UTF16ToWide(const char16* src, size_t src_len, std::wstring* output) { + output->clear(); + // Assume that normally we won't have any non-BMP characters so the counts + // will be the same. + output->reserve(src_len); + return ConvertUnicode(src, src_len, output); +} + +std::wstring UTF16ToWide(const string16& utf16) { + std::wstring ret; + UTF16ToWide(utf16.data(), utf16.length(), &ret); + return ret; +} + +#endif // defined(WCHAR_T_IS_UTF32) + +// UTF16 <-> UTF8 -------------------------------------------------------------- + +#if defined(WCHAR_T_IS_UTF32) + +bool UTF8ToUTF16(const char* src, size_t src_len, string16* output) { + PrepareForUTF16Or32Output(src, src_len, output); + return ConvertUnicode(src, src_len, output); +} + +string16 UTF8ToUTF16(const base::StringPiece& utf8) { + string16 ret; + // Ignore the success flag of this call, it will do the best it can for + // invalid input, which is what we want here. + UTF8ToUTF16(utf8.data(), utf8.length(), &ret); + return ret; +} + +bool UTF16ToUTF8(const char16* src, size_t src_len, std::string* output) { + PrepareForUTF8Output(src, src_len, output); + return ConvertUnicode(src, src_len, output); +} + +std::string UTF16ToUTF8(const string16& utf16) { + std::string ret; + // Ignore the success flag of this call, it will do the best it can for + // invalid input, which is what we want here. + UTF16ToUTF8(utf16.data(), utf16.length(), &ret); + return ret; +} + +#elif defined(WCHAR_T_IS_UTF16) +// Easy case since we can use the "wide" versions we already wrote above. + +bool UTF8ToUTF16(const char* src, size_t src_len, string16* output) { + return UTF8ToWide(src, src_len, output); +} + +string16 UTF8ToUTF16(const base::StringPiece& utf8) { + return UTF8ToWide(utf8); +} + +bool UTF16ToUTF8(const char16* src, size_t src_len, std::string* output) { + return WideToUTF8(src, src_len, output); +} + +std::string UTF16ToUTF8(const string16& utf16) { + return WideToUTF8(utf16); +} + +#endif + +std::wstring ASCIIToWide(const base::StringPiece& ascii) { + DCHECK(IsStringASCII(ascii)) << ascii; + return std::wstring(ascii.begin(), ascii.end()); +} + +string16 ASCIIToUTF16(const base::StringPiece& ascii) { + DCHECK(IsStringASCII(ascii)) << ascii; + return string16(ascii.begin(), ascii.end()); +}
diff --git a/src/base/utf_string_conversions.h b/src/base/utf_string_conversions.h new file mode 100644 index 0000000..5a391fa --- /dev/null +++ b/src/base/utf_string_conversions.h
@@ -0,0 +1,58 @@ +// 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. + +#ifndef BASE_UTF_STRING_CONVERSIONS_H_ +#define BASE_UTF_STRING_CONVERSIONS_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/string16.h" +#include "base/string_piece.h" + +// These convert between UTF-8, -16, and -32 strings. They are potentially slow, +// so avoid unnecessary conversions. The low-level versions return a boolean +// indicating whether the conversion was 100% valid. In this case, it will still +// do the best it can and put the result in the output buffer. The versions that +// return strings ignore this error and just return the best conversion +// possible. +BASE_EXPORT bool WideToUTF8(const wchar_t* src, size_t src_len, + std::string* output); +BASE_EXPORT std::string WideToUTF8(const std::wstring& wide); +BASE_EXPORT bool UTF8ToWide(const char* src, size_t src_len, + std::wstring* output); +BASE_EXPORT std::wstring UTF8ToWide(const base::StringPiece& utf8); + +BASE_EXPORT bool WideToUTF16(const wchar_t* src, size_t src_len, + string16* output); +BASE_EXPORT string16 WideToUTF16(const std::wstring& wide); +BASE_EXPORT bool UTF16ToWide(const char16* src, size_t src_len, + std::wstring* output); +BASE_EXPORT std::wstring UTF16ToWide(const string16& utf16); + +BASE_EXPORT bool UTF8ToUTF16(const char* src, size_t src_len, string16* output); +BASE_EXPORT string16 UTF8ToUTF16(const base::StringPiece& utf8); +BASE_EXPORT bool UTF16ToUTF8(const char16* src, size_t src_len, + std::string* output); +BASE_EXPORT std::string UTF16ToUTF8(const string16& utf16); + +// We are trying to get rid of wstring as much as possible, but it's too big +// a mess to do it all at once. These conversions should be used when we +// really should just be passing a string16 around, but we haven't finished +// porting whatever module uses wstring and the conversion is being used as a +// stopcock. This makes it easy to grep for the ones that should be removed. +#if defined(OS_WIN) +# define WideToUTF16Hack +# define UTF16ToWideHack +#else +# define WideToUTF16Hack WideToUTF16 +# define UTF16ToWideHack UTF16ToWide +#endif + +// These convert an ASCII string, typically a hardcoded constant, to a +// UTF16/Wide string. +BASE_EXPORT std::wstring ASCIIToWide(const base::StringPiece& ascii); +BASE_EXPORT string16 ASCIIToUTF16(const base::StringPiece& ascii); + +#endif // BASE_UTF_STRING_CONVERSIONS_H_
diff --git a/src/base/utf_string_conversions_unittest.cc b/src/base/utf_string_conversions_unittest.cc new file mode 100644 index 0000000..7c180a6 --- /dev/null +++ b/src/base/utf_string_conversions_unittest.cc
@@ -0,0 +1,211 @@ +// Copyright (c) 2010 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/logging.h" +#include "base/string_piece.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +const wchar_t* const kConvertRoundtripCases[] = { + L"Google Video", + // "网页 图片 资讯更多 »" + L"\x7f51\x9875\x0020\x56fe\x7247\x0020\x8d44\x8baf\x66f4\x591a\x0020\x00bb", + // "Παγκόσμιος Ιστός" + L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9" + L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2", + // "Поиск страниц на русском" + L"\x041f\x043e\x0438\x0441\x043a\x0020\x0441\x0442" + L"\x0440\x0430\x043d\x0438\x0446\x0020\x043d\x0430" + L"\x0020\x0440\x0443\x0441\x0441\x043a\x043e\x043c", + // "전체서비스" + L"\xc804\xccb4\xc11c\xbe44\xc2a4", + + // Test characters that take more than 16 bits. This will depend on whether + // wchar_t is 16 or 32 bits. +#if defined(WCHAR_T_IS_UTF16) + L"\xd800\xdf00", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44", +#elif defined(WCHAR_T_IS_UTF32) + L"\x10300", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\x11d40\x11d41\x11d42\x11d43\x11d44", +#endif +}; + +} // namespace + +TEST(UTFStringConversionsTest, ConvertUTF8AndWide) { + // we round-trip all the wide strings through UTF-8 to make sure everything + // agrees on the conversion. This uses the stream operators to test them + // simultaneously. + for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + std::ostringstream utf8; + utf8 << WideToUTF8(kConvertRoundtripCases[i]); + std::wostringstream wide; + wide << UTF8ToWide(utf8.str()); + + EXPECT_EQ(kConvertRoundtripCases[i], wide.str()); + } +} + +TEST(UTFStringConversionsTest, ConvertUTF8AndWideEmptyString) { + // An empty std::wstring should be converted to an empty std::string, + // and vice versa. + std::wstring wempty; + std::string empty; + EXPECT_EQ(empty, WideToUTF8(wempty)); + EXPECT_EQ(wempty, UTF8ToWide(empty)); +} + +TEST(UTFStringConversionsTest, ConvertUTF8ToWide) { + struct UTF8ToWideCase { + const char* utf8; + const wchar_t* wide; + bool success; + } convert_cases[] = { + // Regular UTF-8 input. + {"\xe4\xbd\xa0\xe5\xa5\xbd", L"\x4f60\x597d", true}, + // Non-character is passed through. + {"\xef\xbf\xbfHello", L"\xffffHello", true}, + // Truncated UTF-8 sequence. + {"\xe4\xa0\xe5\xa5\xbd", L"\xfffd\x597d", false}, + // Truncated off the end. + {"\xe5\xa5\xbd\xe4\xa0", L"\x597d\xfffd", false}, + // Non-shortest-form UTF-8. + {"\xf0\x84\xbd\xa0\xe5\xa5\xbd", L"\xfffd\x597d", false}, + // This UTF-8 character decodes to a UTF-16 surrogate, which is illegal. + {"\xed\xb0\x80", L"\xfffd", false}, + // Non-BMP characters. The second is a non-character regarded as valid. + // The result will either be in UTF-16 or UTF-32. +#if defined(WCHAR_T_IS_UTF16) + {"A\xF0\x90\x8C\x80z", L"A\xd800\xdf00z", true}, + {"A\xF4\x8F\xBF\xBEz", L"A\xdbff\xdffez", true}, +#elif defined(WCHAR_T_IS_UTF32) + {"A\xF0\x90\x8C\x80z", L"A\x10300z", true}, + {"A\xF4\x8F\xBF\xBEz", L"A\x10fffez", true}, +#endif + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(convert_cases); i++) { + std::wstring converted; + EXPECT_EQ(convert_cases[i].success, + UTF8ToWide(convert_cases[i].utf8, + strlen(convert_cases[i].utf8), + &converted)); + std::wstring expected(convert_cases[i].wide); + EXPECT_EQ(expected, converted); + } + + // Manually test an embedded NULL. + std::wstring converted; + EXPECT_TRUE(UTF8ToWide("\00Z\t", 3, &converted)); + ASSERT_EQ(3U, converted.length()); + EXPECT_EQ(static_cast<wchar_t>(0), converted[0]); + EXPECT_EQ('Z', converted[1]); + EXPECT_EQ('\t', converted[2]); + + // Make sure that conversion replaces, not appends. + EXPECT_TRUE(UTF8ToWide("B", 1, &converted)); + ASSERT_EQ(1U, converted.length()); + EXPECT_EQ('B', converted[0]); +} + +#if defined(WCHAR_T_IS_UTF16) +// This test is only valid when wchar_t == UTF-16. +TEST(UTFStringConversionsTest, ConvertUTF16ToUTF8) { + struct WideToUTF8Case { + const wchar_t* utf16; + const char* utf8; + bool success; + } convert_cases[] = { + // Regular UTF-16 input. + {L"\x4f60\x597d", "\xe4\xbd\xa0\xe5\xa5\xbd", true}, + // Test a non-BMP character. + {L"\xd800\xdf00", "\xF0\x90\x8C\x80", true}, + // Non-characters are passed through. + {L"\xffffHello", "\xEF\xBF\xBFHello", true}, + {L"\xdbff\xdffeHello", "\xF4\x8F\xBF\xBEHello", true}, + // The first character is a truncated UTF-16 character. + {L"\xd800\x597d", "\xef\xbf\xbd\xe5\xa5\xbd", false}, + // Truncated at the end. + {L"\x597d\xd800", "\xe5\xa5\xbd\xef\xbf\xbd", false}, + }; + + for (int i = 0; i < arraysize(convert_cases); i++) { + std::string converted; + EXPECT_EQ(convert_cases[i].success, + WideToUTF8(convert_cases[i].utf16, + wcslen(convert_cases[i].utf16), + &converted)); + std::string expected(convert_cases[i].utf8); + EXPECT_EQ(expected, converted); + } +} + +#elif defined(WCHAR_T_IS_UTF32) +// This test is only valid when wchar_t == UTF-32. +TEST(UTFStringConversionsTest, ConvertUTF32ToUTF8) { + struct WideToUTF8Case { + const wchar_t* utf32; + const char* utf8; + bool success; + } convert_cases[] = { + // Regular 16-bit input. + {L"\x4f60\x597d", "\xe4\xbd\xa0\xe5\xa5\xbd", true}, + // Test a non-BMP character. + {L"A\x10300z", "A\xF0\x90\x8C\x80z", true}, + // Non-characters are passed through. + {L"\xffffHello", "\xEF\xBF\xBFHello", true}, + {L"\x10fffeHello", "\xF4\x8F\xBF\xBEHello", true}, + // Invalid Unicode code points. + {L"\xfffffffHello", "\xEF\xBF\xBDHello", false}, + // The first character is a truncated UTF-16 character. + {L"\xd800\x597d", "\xef\xbf\xbd\xe5\xa5\xbd", false}, + {L"\xdc01Hello", "\xef\xbf\xbdHello", false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(convert_cases); i++) { + std::string converted; + EXPECT_EQ(convert_cases[i].success, + WideToUTF8(convert_cases[i].utf32, + wcslen(convert_cases[i].utf32), + &converted)); + std::string expected(convert_cases[i].utf8); + EXPECT_EQ(expected, converted); + } +} +#endif // defined(WCHAR_T_IS_UTF32) + +TEST(UTFStringConversionsTest, ConvertMultiString) { + static wchar_t wmulti[] = { + L'f', L'o', L'o', L'\0', + L'b', L'a', L'r', L'\0', + L'b', L'a', L'z', L'\0', + L'\0' + }; + static char multi[] = { + 'f', 'o', 'o', '\0', + 'b', 'a', 'r', '\0', + 'b', 'a', 'z', '\0', + '\0' + }; + std::wstring wmultistring; + memcpy(WriteInto(&wmultistring, arraysize(wmulti)), wmulti, sizeof(wmulti)); + EXPECT_EQ(arraysize(wmulti) - 1, wmultistring.length()); + std::string expected; + memcpy(WriteInto(&expected, arraysize(multi)), multi, sizeof(multi)); + EXPECT_EQ(arraysize(multi) - 1, expected.length()); + const std::string& converted = WideToUTF8(wmultistring); + EXPECT_EQ(arraysize(multi) - 1, converted.length()); + EXPECT_EQ(expected, converted); +} + +} // base
diff --git a/src/base/value_conversions.cc b/src/base/value_conversions.cc new file mode 100644 index 0000000..6bde1ae --- /dev/null +++ b/src/base/value_conversions.cc
@@ -0,0 +1,46 @@ +// 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/value_conversions.h" + +#include "base/file_path.h" +#include "base/string_number_conversions.h" +#include "base/time.h" +#include "base/values.h" + +namespace base { + +// |Value| internally stores strings in UTF-8, so we have to convert from the +// system native code to UTF-8 and back. +StringValue* CreateFilePathValue(const FilePath& in_value) { + return new StringValue(in_value.AsUTF8Unsafe()); +} + +bool GetValueAsFilePath(const Value& value, FilePath* file_path) { + std::string str; + if (!value.GetAsString(&str)) + return false; + if (file_path) + *file_path = FilePath::FromUTF8Unsafe(str); + return true; +} + +// |Value| does not support 64-bit integers, and doubles do not have enough +// precision, so we store the 64-bit time value as a string instead. +StringValue* CreateTimeDeltaValue(const TimeDelta& time) { + std::string string_value = base::Int64ToString(time.ToInternalValue()); + return new StringValue(string_value); +} + +bool GetValueAsTimeDelta(const Value& value, TimeDelta* time) { + std::string str; + int64 int_value; + if (!value.GetAsString(&str) || !base::StringToInt64(str, &int_value)) + return false; + if (time) + *time = TimeDelta::FromInternalValue(int_value); + return true; +} + +} // namespace base
diff --git a/src/base/value_conversions.h b/src/base/value_conversions.h new file mode 100644 index 0000000..99cd514 --- /dev/null +++ b/src/base/value_conversions.h
@@ -0,0 +1,29 @@ +// 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_VALUE_CONVERSIONS_H_ +#define BASE_VALUE_CONVERSIONS_H_ + +// This file contains methods to convert things to a |Value| and back. + +#include "base/base_export.h" + +class FilePath; + +namespace base { + +class TimeDelta; +class StringValue; +class Value; + +// The caller takes ownership of the returned value. +BASE_EXPORT StringValue* CreateFilePathValue(const FilePath& in_value); +BASE_EXPORT bool GetValueAsFilePath(const Value& value, FilePath* file_path); + +BASE_EXPORT StringValue* CreateTimeDeltaValue(const TimeDelta& time); +BASE_EXPORT bool GetValueAsTimeDelta(const Value& value, TimeDelta* time); + +} // namespace + +#endif // BASE_VALUE_CONVERSIONS_H_
diff --git a/src/base/values.cc b/src/base/values.cc new file mode 100644 index 0000000..459d56f --- /dev/null +++ b/src/base/values.cc
@@ -0,0 +1,1144 @@ +// 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/values.h" + +#include <algorithm> +#include <ostream> + +#include "base/float_util.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" + +namespace { + +// Make a deep copy of |node|, but don't include empty lists or dictionaries +// in the copy. It's possible for this function to return NULL and it +// expects |node| to always be non-NULL. +Value* CopyWithoutEmptyChildren(Value* node) { + DCHECK(node); + switch (node->GetType()) { + case Value::TYPE_LIST: { + ListValue* list = static_cast<ListValue*>(node); + ListValue* copy = new ListValue; + for (ListValue::const_iterator it = list->begin(); it != list->end(); + ++it) { + Value* child_copy = CopyWithoutEmptyChildren(*it); + if (child_copy) + copy->Append(child_copy); + } + if (!copy->empty()) + return copy; + + delete copy; + return NULL; + } + + case Value::TYPE_DICTIONARY: { + DictionaryValue* dict = static_cast<DictionaryValue*>(node); + DictionaryValue* copy = new DictionaryValue; + for (DictionaryValue::key_iterator it = dict->begin_keys(); + it != dict->end_keys(); ++it) { + Value* child = NULL; + bool rv = dict->GetWithoutPathExpansion(*it, &child); + DCHECK(rv); + Value* child_copy = CopyWithoutEmptyChildren(child); + if (child_copy) + copy->SetWithoutPathExpansion(*it, child_copy); + } + if (!copy->empty()) + return copy; + + delete copy; + return NULL; + } + + default: + // For everything else, just make a copy. + return node->DeepCopy(); + } +} + +// A small functor for comparing Values for std::find_if and similar. +class ValueEquals { + public: + // Pass the value against which all consecutive calls of the () operator will + // compare their argument to. This Value object must not be destroyed while + // the ValueEquals is in use. + ValueEquals(const Value* first) : first_(first) { } + + bool operator ()(const Value* second) const { + return first_->Equals(second); + } + + private: + const Value* first_; +}; + +} // namespace + +namespace base { + +///////////////////// Value //////////////////// + +Value::~Value() { +} + +// static +Value* Value::CreateNullValue() { + return new Value(TYPE_NULL); +} + +// static +FundamentalValue* Value::CreateBooleanValue(bool in_value) { + return new FundamentalValue(in_value); +} + +// static +FundamentalValue* Value::CreateIntegerValue(int in_value) { + return new FundamentalValue(in_value); +} + +// static +FundamentalValue* Value::CreateDoubleValue(double in_value) { + return new FundamentalValue(in_value); +} + +// static +StringValue* Value::CreateStringValue(const std::string& in_value) { + return new StringValue(in_value); +} + +// static +StringValue* Value::CreateStringValue(const string16& in_value) { + return new StringValue(in_value); +} + +bool Value::GetAsBoolean(bool* out_value) const { + return false; +} + +bool Value::GetAsInteger(int* out_value) const { + return false; +} + +bool Value::GetAsDouble(double* out_value) const { + return false; +} + +bool Value::GetAsString(std::string* out_value) const { + return false; +} + +bool Value::GetAsString(string16* out_value) const { + return false; +} + +bool Value::GetAsList(ListValue** out_value) { + return false; +} + +bool Value::GetAsList(const ListValue** out_value) const { + return false; +} + +bool Value::GetAsDictionary(DictionaryValue** out_value) { + return false; +} + +bool Value::GetAsDictionary(const DictionaryValue** out_value) const { + return false; +} + +Value* Value::DeepCopy() const { + // This method should only be getting called for null Values--all subclasses + // need to provide their own implementation;. + DCHECK(IsType(TYPE_NULL)); + return CreateNullValue(); +} + +bool Value::Equals(const Value* other) const { + // This method should only be getting called for null Values--all subclasses + // need to provide their own implementation;. + DCHECK(IsType(TYPE_NULL)); + return other->IsType(TYPE_NULL); +} + +// static +bool Value::Equals(const Value* a, const Value* b) { + if ((a == NULL) && (b == NULL)) return true; + if ((a == NULL) ^ (b == NULL)) return false; + return a->Equals(b); +} + +Value::Value(Type type) : type_(type) {} + +Value::Value(const Value& that) : type_(that.type_) {} + +Value& Value::operator=(const Value& that) { + type_ = that.type_; + return *this; +} + +///////////////////// FundamentalValue //////////////////// + +FundamentalValue::FundamentalValue(bool in_value) + : Value(TYPE_BOOLEAN), boolean_value_(in_value) { +} + +FundamentalValue::FundamentalValue(int in_value) + : Value(TYPE_INTEGER), integer_value_(in_value) { +} + +FundamentalValue::FundamentalValue(double in_value) + : Value(TYPE_DOUBLE), double_value_(in_value) { + if (!IsFinite(double_value_)) { + NOTREACHED() << "Non-finite (i.e. NaN or positive/negative infinity) " + << "values cannot be represented in JSON"; + double_value_ = 0.0; + } +} + +FundamentalValue::~FundamentalValue() { +} + +bool FundamentalValue::GetAsBoolean(bool* out_value) const { + if (out_value && IsType(TYPE_BOOLEAN)) + *out_value = boolean_value_; + return (IsType(TYPE_BOOLEAN)); +} + +bool FundamentalValue::GetAsInteger(int* out_value) const { + if (out_value && IsType(TYPE_INTEGER)) + *out_value = integer_value_; + return (IsType(TYPE_INTEGER)); +} + +bool FundamentalValue::GetAsDouble(double* out_value) const { + if (out_value && IsType(TYPE_DOUBLE)) + *out_value = double_value_; + else if (out_value && IsType(TYPE_INTEGER)) + *out_value = integer_value_; + return (IsType(TYPE_DOUBLE) || IsType(TYPE_INTEGER)); +} + +FundamentalValue* FundamentalValue::DeepCopy() const { + switch (GetType()) { + case TYPE_BOOLEAN: + return CreateBooleanValue(boolean_value_); + + case TYPE_INTEGER: + return CreateIntegerValue(integer_value_); + + case TYPE_DOUBLE: + return CreateDoubleValue(double_value_); + + default: + NOTREACHED(); + return NULL; + } +} + +bool FundamentalValue::Equals(const Value* other) const { + if (other->GetType() != GetType()) + return false; + + switch (GetType()) { + case TYPE_BOOLEAN: { + bool lhs, rhs; + return GetAsBoolean(&lhs) && other->GetAsBoolean(&rhs) && lhs == rhs; + } + case TYPE_INTEGER: { + int lhs, rhs; + return GetAsInteger(&lhs) && other->GetAsInteger(&rhs) && lhs == rhs; + } + case TYPE_DOUBLE: { + double lhs, rhs; + return GetAsDouble(&lhs) && other->GetAsDouble(&rhs) && lhs == rhs; + } + default: + NOTREACHED(); + return false; + } +} + +///////////////////// StringValue //////////////////// + +StringValue::StringValue(const std::string& in_value) + : Value(TYPE_STRING), + value_(in_value) { + DCHECK(IsStringUTF8(in_value)); +} + +StringValue::StringValue(const string16& in_value) + : Value(TYPE_STRING), + value_(UTF16ToUTF8(in_value)) { +} + +StringValue::~StringValue() { +} + +bool StringValue::GetAsString(std::string* out_value) const { + if (out_value) + *out_value = value_; + return true; +} + +bool StringValue::GetAsString(string16* out_value) const { + if (out_value) + *out_value = UTF8ToUTF16(value_); + return true; +} + +StringValue* StringValue::DeepCopy() const { + return CreateStringValue(value_); +} + +bool StringValue::Equals(const Value* other) const { + if (other->GetType() != GetType()) + return false; + std::string lhs, rhs; + return GetAsString(&lhs) && other->GetAsString(&rhs) && lhs == rhs; +} + +///////////////////// BinaryValue //////////////////// + +BinaryValue::~BinaryValue() { + DCHECK(buffer_); + if (buffer_) + delete[] buffer_; +} + +// static +BinaryValue* BinaryValue::Create(char* buffer, size_t size) { + if (!buffer) + return NULL; + + return new BinaryValue(buffer, size); +} + +// static +BinaryValue* BinaryValue::CreateWithCopiedBuffer(const char* buffer, + size_t size) { + if (!buffer) + return NULL; + + char* buffer_copy = new char[size]; + memcpy(buffer_copy, buffer, size); + return new BinaryValue(buffer_copy, size); +} + +BinaryValue* BinaryValue::DeepCopy() const { + return CreateWithCopiedBuffer(buffer_, size_); +} + +bool BinaryValue::Equals(const Value* other) const { + if (other->GetType() != GetType()) + return false; + const BinaryValue* other_binary = static_cast<const BinaryValue*>(other); + if (other_binary->size_ != size_) + return false; + return !memcmp(buffer_, other_binary->buffer_, size_); +} + +BinaryValue::BinaryValue(char* buffer, size_t size) + : Value(TYPE_BINARY), + buffer_(buffer), + size_(size) { + DCHECK(buffer_); +} + +///////////////////// DictionaryValue //////////////////// + +DictionaryValue::DictionaryValue() + : Value(TYPE_DICTIONARY) { +} + +DictionaryValue::~DictionaryValue() { + Clear(); +} + +bool DictionaryValue::GetAsDictionary(DictionaryValue** out_value) { + if (out_value) + *out_value = this; + return true; +} + +bool DictionaryValue::GetAsDictionary(const DictionaryValue** out_value) const { + if (out_value) + *out_value = this; + return true; +} + +bool DictionaryValue::HasKey(const std::string& key) const { + DCHECK(IsStringUTF8(key)); + ValueMap::const_iterator current_entry = dictionary_.find(key); + DCHECK((current_entry == dictionary_.end()) || current_entry->second); + return current_entry != dictionary_.end(); +} + +void DictionaryValue::Clear() { + ValueMap::iterator dict_iterator = dictionary_.begin(); + while (dict_iterator != dictionary_.end()) { + delete dict_iterator->second; + ++dict_iterator; + } + + dictionary_.clear(); +} + +void DictionaryValue::Set(const std::string& path, Value* in_value) { + DCHECK(IsStringUTF8(path)); + DCHECK(in_value); + + std::string current_path(path); + DictionaryValue* current_dictionary = this; + for (size_t delimiter_position = current_path.find('.'); + delimiter_position != std::string::npos; + delimiter_position = current_path.find('.')) { + // Assume that we're indexing into a dictionary. + std::string key(current_path, 0, delimiter_position); + DictionaryValue* child_dictionary = NULL; + if (!current_dictionary->GetDictionary(key, &child_dictionary)) { + child_dictionary = new DictionaryValue; + current_dictionary->SetWithoutPathExpansion(key, child_dictionary); + } + + current_dictionary = child_dictionary; + current_path.erase(0, delimiter_position + 1); + } + + current_dictionary->SetWithoutPathExpansion(current_path, in_value); +} + +void DictionaryValue::SetBoolean(const std::string& path, bool in_value) { + Set(path, CreateBooleanValue(in_value)); +} + +void DictionaryValue::SetInteger(const std::string& path, int in_value) { + Set(path, CreateIntegerValue(in_value)); +} + +void DictionaryValue::SetDouble(const std::string& path, double in_value) { + Set(path, CreateDoubleValue(in_value)); +} + +void DictionaryValue::SetString(const std::string& path, + const std::string& in_value) { + Set(path, CreateStringValue(in_value)); +} + +void DictionaryValue::SetString(const std::string& path, + const string16& in_value) { + Set(path, CreateStringValue(in_value)); +} + +void DictionaryValue::SetWithoutPathExpansion(const std::string& key, + Value* in_value) { + // If there's an existing value here, we need to delete it, because + // we own all our children. + std::pair<ValueMap::iterator, bool> ins_res = + dictionary_.insert(std::make_pair(key, in_value)); + if (!ins_res.second) { + DCHECK_NE(ins_res.first->second, in_value); // This would be bogus + delete ins_res.first->second; + ins_res.first->second = in_value; + } +} + +void DictionaryValue::SetBooleanWithoutPathExpansion( + const std::string& path, bool in_value) { + SetWithoutPathExpansion(path, CreateBooleanValue(in_value)); +} + +void DictionaryValue::SetIntegerWithoutPathExpansion( + const std::string& path, int in_value) { + SetWithoutPathExpansion(path, CreateIntegerValue(in_value)); +} + +void DictionaryValue::SetDoubleWithoutPathExpansion( + const std::string& path, double in_value) { + SetWithoutPathExpansion(path, CreateDoubleValue(in_value)); +} + +void DictionaryValue::SetStringWithoutPathExpansion( + const std::string& path, const std::string& in_value) { + SetWithoutPathExpansion(path, CreateStringValue(in_value)); +} + +void DictionaryValue::SetStringWithoutPathExpansion( + const std::string& path, const string16& in_value) { + SetWithoutPathExpansion(path, CreateStringValue(in_value)); +} + +bool DictionaryValue::Get( + const std::string& path, const Value** out_value) const { + DCHECK(IsStringUTF8(path)); + std::string current_path(path); + const DictionaryValue* current_dictionary = this; + for (size_t delimiter_position = current_path.find('.'); + delimiter_position != std::string::npos; + delimiter_position = current_path.find('.')) { + const DictionaryValue* child_dictionary = NULL; + if (!current_dictionary->GetDictionary( + current_path.substr(0, delimiter_position), &child_dictionary)) + return false; + + current_dictionary = child_dictionary; + current_path.erase(0, delimiter_position + 1); + } + + return current_dictionary->GetWithoutPathExpansion(current_path, out_value); +} + +bool DictionaryValue::Get(const std::string& path, Value** out_value) { + return static_cast<const DictionaryValue&>(*this).Get( + path, + const_cast<const Value**>(out_value)); +} + +bool DictionaryValue::GetBoolean(const std::string& path, + bool* bool_value) const { + const Value* value; + if (!Get(path, &value)) + return false; + + return value->GetAsBoolean(bool_value); +} + +bool DictionaryValue::GetInteger(const std::string& path, + int* out_value) const { + const Value* value; + if (!Get(path, &value)) + return false; + + return value->GetAsInteger(out_value); +} + +bool DictionaryValue::GetDouble(const std::string& path, + double* out_value) const { + const Value* value; + if (!Get(path, &value)) + return false; + + return value->GetAsDouble(out_value); +} + +bool DictionaryValue::GetString(const std::string& path, + std::string* out_value) const { + const Value* value; + if (!Get(path, &value)) + return false; + + return value->GetAsString(out_value); +} + +bool DictionaryValue::GetString(const std::string& path, + string16* out_value) const { + const Value* value; + if (!Get(path, &value)) + return false; + + return value->GetAsString(out_value); +} + +bool DictionaryValue::GetStringASCII(const std::string& path, + std::string* out_value) const { + std::string out; + if (!GetString(path, &out)) + return false; + + if (!IsStringASCII(out)) { + NOTREACHED(); + return false; + } + + out_value->assign(out); + return true; +} + +bool DictionaryValue::GetBinary(const std::string& path, + const BinaryValue** out_value) const { + const Value* value; + bool result = Get(path, &value); + if (!result || !value->IsType(TYPE_BINARY)) + return false; + + if (out_value) + *out_value = static_cast<const BinaryValue*>(value); + + return true; +} + +bool DictionaryValue::GetBinary(const std::string& path, + BinaryValue** out_value) { + return static_cast<const DictionaryValue&>(*this).GetBinary( + path, + const_cast<const BinaryValue**>(out_value)); +} + +bool DictionaryValue::GetDictionary(const std::string& path, + const DictionaryValue** out_value) const { + const Value* value; + bool result = Get(path, &value); + if (!result || !value->IsType(TYPE_DICTIONARY)) + return false; + + if (out_value) + *out_value = static_cast<const DictionaryValue*>(value); + + return true; +} + +bool DictionaryValue::GetDictionary(const std::string& path, + DictionaryValue** out_value) { + return static_cast<const DictionaryValue&>(*this).GetDictionary( + path, + const_cast<const DictionaryValue**>(out_value)); +} + +bool DictionaryValue::GetList(const std::string& path, + const ListValue** out_value) const { + const Value* value; + bool result = Get(path, &value); + if (!result || !value->IsType(TYPE_LIST)) + return false; + + if (out_value) + *out_value = static_cast<const ListValue*>(value); + + return true; +} + +bool DictionaryValue::GetList(const std::string& path, ListValue** out_value) { + return static_cast<const DictionaryValue&>(*this).GetList( + path, + const_cast<const ListValue**>(out_value)); +} + +bool DictionaryValue::GetWithoutPathExpansion(const std::string& key, + const Value** out_value) const { + DCHECK(IsStringUTF8(key)); + ValueMap::const_iterator entry_iterator = dictionary_.find(key); + if (entry_iterator == dictionary_.end()) + return false; + + const Value* entry = entry_iterator->second; + if (out_value) + *out_value = entry; + return true; +} + +bool DictionaryValue::GetWithoutPathExpansion(const std::string& key, + Value** out_value) { + return static_cast<const DictionaryValue&>(*this).GetWithoutPathExpansion( + key, + const_cast<const Value**>(out_value)); +} + +bool DictionaryValue::GetBooleanWithoutPathExpansion(const std::string& key, + bool* out_value) const { + const Value* value; + if (!GetWithoutPathExpansion(key, &value)) + return false; + + return value->GetAsBoolean(out_value); +} + +bool DictionaryValue::GetIntegerWithoutPathExpansion(const std::string& key, + int* out_value) const { + const Value* value; + if (!GetWithoutPathExpansion(key, &value)) + return false; + + return value->GetAsInteger(out_value); +} + +bool DictionaryValue::GetDoubleWithoutPathExpansion(const std::string& key, + double* out_value) const { + const Value* value; + if (!GetWithoutPathExpansion(key, &value)) + return false; + + return value->GetAsDouble(out_value); +} + +bool DictionaryValue::GetStringWithoutPathExpansion( + const std::string& key, + std::string* out_value) const { + const Value* value; + if (!GetWithoutPathExpansion(key, &value)) + return false; + + return value->GetAsString(out_value); +} + +bool DictionaryValue::GetStringWithoutPathExpansion(const std::string& key, + string16* out_value) const { + const Value* value; + if (!GetWithoutPathExpansion(key, &value)) + return false; + + return value->GetAsString(out_value); +} + +bool DictionaryValue::GetDictionaryWithoutPathExpansion( + const std::string& key, + const DictionaryValue** out_value) const { + const Value* value; + bool result = GetWithoutPathExpansion(key, &value); + if (!result || !value->IsType(TYPE_DICTIONARY)) + return false; + + if (out_value) + *out_value = static_cast<const DictionaryValue*>(value); + + return true; +} + +bool DictionaryValue::GetDictionaryWithoutPathExpansion( + const std::string& key, + DictionaryValue** out_value) { + const DictionaryValue& const_this = + static_cast<const DictionaryValue&>(*this); + return const_this.GetDictionaryWithoutPathExpansion( + key, + const_cast<const DictionaryValue**>(out_value)); +} + +bool DictionaryValue::GetListWithoutPathExpansion( + const std::string& key, + const ListValue** out_value) const { + const Value* value; + bool result = GetWithoutPathExpansion(key, &value); + if (!result || !value->IsType(TYPE_LIST)) + return false; + + if (out_value) + *out_value = static_cast<const ListValue*>(value); + + return true; +} + +bool DictionaryValue::GetListWithoutPathExpansion(const std::string& key, + ListValue** out_value) { + return + static_cast<const DictionaryValue&>(*this).GetListWithoutPathExpansion( + key, + const_cast<const ListValue**>(out_value)); +} + +bool DictionaryValue::Remove(const std::string& path, Value** out_value) { + DCHECK(IsStringUTF8(path)); + std::string current_path(path); + DictionaryValue* current_dictionary = this; + size_t delimiter_position = current_path.rfind('.'); + if (delimiter_position != std::string::npos) { + if (!GetDictionary(current_path.substr(0, delimiter_position), + ¤t_dictionary)) + return false; + current_path.erase(0, delimiter_position + 1); + } + + return current_dictionary->RemoveWithoutPathExpansion(current_path, + out_value); +} + +bool DictionaryValue::RemoveWithoutPathExpansion(const std::string& key, + Value** out_value) { + DCHECK(IsStringUTF8(key)); + ValueMap::iterator entry_iterator = dictionary_.find(key); + if (entry_iterator == dictionary_.end()) + return false; + + Value* entry = entry_iterator->second; + if (out_value) + *out_value = entry; + else + delete entry; + dictionary_.erase(entry_iterator); + return true; +} + +DictionaryValue* DictionaryValue::DeepCopyWithoutEmptyChildren() { + Value* copy = CopyWithoutEmptyChildren(this); + return copy ? static_cast<DictionaryValue*>(copy) : new DictionaryValue; +} + +void DictionaryValue::MergeDictionary(const DictionaryValue* dictionary) { + for (DictionaryValue::key_iterator key(dictionary->begin_keys()); + key != dictionary->end_keys(); ++key) { + const Value* merge_value; + if (dictionary->GetWithoutPathExpansion(*key, &merge_value)) { + // Check whether we have to merge dictionaries. + if (merge_value->IsType(Value::TYPE_DICTIONARY)) { + DictionaryValue* sub_dict; + if (GetDictionaryWithoutPathExpansion(*key, &sub_dict)) { + sub_dict->MergeDictionary( + static_cast<const DictionaryValue*>(merge_value)); + continue; + } + } + // All other cases: Make a copy and hook it up. + SetWithoutPathExpansion(*key, merge_value->DeepCopy()); + } + } +} + +void DictionaryValue::Swap(DictionaryValue* other) { + dictionary_.swap(other->dictionary_); +} + +DictionaryValue::key_iterator::key_iterator(ValueMap::const_iterator itr) { + itr_ = itr; +} + +DictionaryValue::key_iterator::key_iterator(const key_iterator& rhs) { + itr_ = rhs.itr_; +} + +DictionaryValue::Iterator::Iterator(const DictionaryValue& target) + : target_(target), + it_(target.dictionary_.begin()) {} + +DictionaryValue* DictionaryValue::DeepCopy() const { + DictionaryValue* result = new DictionaryValue; + + for (ValueMap::const_iterator current_entry(dictionary_.begin()); + current_entry != dictionary_.end(); ++current_entry) { + result->SetWithoutPathExpansion(current_entry->first, + current_entry->second->DeepCopy()); + } + + return result; +} + +bool DictionaryValue::Equals(const Value* other) const { + if (other->GetType() != GetType()) + return false; + + const DictionaryValue* other_dict = + static_cast<const DictionaryValue*>(other); + key_iterator lhs_it(begin_keys()); + key_iterator rhs_it(other_dict->begin_keys()); + while (lhs_it != end_keys() && rhs_it != other_dict->end_keys()) { + const Value* lhs; + const Value* rhs; + if (*lhs_it != *rhs_it || + !GetWithoutPathExpansion(*lhs_it, &lhs) || + !other_dict->GetWithoutPathExpansion(*rhs_it, &rhs) || + !lhs->Equals(rhs)) { + return false; + } + ++lhs_it; + ++rhs_it; + } + if (lhs_it != end_keys() || rhs_it != other_dict->end_keys()) + return false; + + return true; +} + +///////////////////// ListValue //////////////////// + +ListValue::ListValue() : Value(TYPE_LIST) { +} + +ListValue::~ListValue() { + Clear(); +} + +void ListValue::Clear() { + for (ValueVector::iterator i(list_.begin()); i != list_.end(); ++i) + delete *i; + list_.clear(); +} + +bool ListValue::Set(size_t index, Value* in_value) { + if (!in_value) + return false; + + if (index >= list_.size()) { + // Pad out any intermediate indexes with null settings + while (index > list_.size()) + Append(CreateNullValue()); + Append(in_value); + } else { + DCHECK(list_[index] != in_value); + delete list_[index]; + list_[index] = in_value; + } + return true; +} + +bool ListValue::Get(size_t index, const Value** out_value) const { + if (index >= list_.size()) + return false; + + if (out_value) + *out_value = list_[index]; + + return true; +} + +bool ListValue::Get(size_t index, Value** out_value) { + return static_cast<const ListValue&>(*this).Get( + index, + const_cast<const Value**>(out_value)); +} + +bool ListValue::GetBoolean(size_t index, bool* bool_value) const { + const Value* value; + if (!Get(index, &value)) + return false; + + return value->GetAsBoolean(bool_value); +} + +bool ListValue::GetInteger(size_t index, int* out_value) const { + const Value* value; + if (!Get(index, &value)) + return false; + + return value->GetAsInteger(out_value); +} + +bool ListValue::GetDouble(size_t index, double* out_value) const { + const Value* value; + if (!Get(index, &value)) + return false; + + return value->GetAsDouble(out_value); +} + +bool ListValue::GetString(size_t index, std::string* out_value) const { + const Value* value; + if (!Get(index, &value)) + return false; + + return value->GetAsString(out_value); +} + +bool ListValue::GetString(size_t index, string16* out_value) const { + const Value* value; + if (!Get(index, &value)) + return false; + + return value->GetAsString(out_value); +} + +bool ListValue::GetBinary(size_t index, const BinaryValue** out_value) const { + const Value* value; + bool result = Get(index, &value); + if (!result || !value->IsType(TYPE_BINARY)) + return false; + + if (out_value) + *out_value = static_cast<const BinaryValue*>(value); + + return true; +} + +bool ListValue::GetBinary(size_t index, BinaryValue** out_value) { + return static_cast<const ListValue&>(*this).GetBinary( + index, + const_cast<const BinaryValue**>(out_value)); +} + +bool ListValue::GetDictionary(size_t index, + const DictionaryValue** out_value) const { + const Value* value; + bool result = Get(index, &value); + if (!result || !value->IsType(TYPE_DICTIONARY)) + return false; + + if (out_value) + *out_value = static_cast<const DictionaryValue*>(value); + + return true; +} + +bool ListValue::GetDictionary(size_t index, DictionaryValue** out_value) { + return static_cast<const ListValue&>(*this).GetDictionary( + index, + const_cast<const DictionaryValue**>(out_value)); +} + +bool ListValue::GetList(size_t index, const ListValue** out_value) const { + const Value* value; + bool result = Get(index, &value); + if (!result || !value->IsType(TYPE_LIST)) + return false; + + if (out_value) + *out_value = static_cast<const ListValue*>(value); + + return true; +} + +bool ListValue::GetList(size_t index, ListValue** out_value) { + return static_cast<const ListValue&>(*this).GetList( + index, + const_cast<const ListValue**>(out_value)); +} + +bool ListValue::Remove(size_t index, Value** out_value) { + if (index >= list_.size()) + return false; + + if (out_value) + *out_value = list_[index]; + else + delete list_[index]; + + list_.erase(list_.begin() + index); + return true; +} + +bool ListValue::Remove(const Value& value, size_t* index) { + for (ValueVector::iterator i(list_.begin()); i != list_.end(); ++i) { + if ((*i)->Equals(&value)) { + size_t previous_index = i - list_.begin(); + delete *i; + list_.erase(i); + + if (index) + *index = previous_index; + return true; + } + } + return false; +} + +void ListValue::Erase(iterator iter, Value** out_value) { + if (out_value) + *out_value = *iter; + else + delete *iter; + + list_.erase(iter); +} + +void ListValue::Append(Value* in_value) { + DCHECK(in_value); + list_.push_back(in_value); +} + +void ListValue::AppendBoolean(bool in_value) { + Append(CreateBooleanValue(in_value)); +} + +void ListValue::AppendInteger(int in_value) { + Append(CreateIntegerValue(in_value)); +} + +void ListValue::AppendDouble(double in_value) { + Append(CreateDoubleValue(in_value)); +} + +void ListValue::AppendString(const std::string& in_value) { + Append(CreateStringValue(in_value)); +} + +void ListValue::AppendString(const string16& in_value) { + Append(CreateStringValue(in_value)); +} + +void ListValue::AppendStrings(const std::vector<std::string>& in_values) { + for (std::vector<std::string>::const_iterator it = in_values.begin(); + it != in_values.end(); ++it) { + AppendString(*it); + } +} + +void ListValue::AppendStrings(const std::vector<string16>& in_values) { + for (std::vector<string16>::const_iterator it = in_values.begin(); + it != in_values.end(); ++it) { + AppendString(*it); + } +} + +bool ListValue::AppendIfNotPresent(Value* in_value) { + DCHECK(in_value); + for (ValueVector::const_iterator i(list_.begin()); i != list_.end(); ++i) { + if ((*i)->Equals(in_value)) { + delete in_value; + return false; + } + } + list_.push_back(in_value); + return true; +} + +bool ListValue::Insert(size_t index, Value* in_value) { + DCHECK(in_value); + if (index > list_.size()) + return false; + + list_.insert(list_.begin() + index, in_value); + return true; +} + +ListValue::const_iterator ListValue::Find(const Value& value) const { + return std::find_if(list_.begin(), list_.end(), ValueEquals(&value)); +} + +void ListValue::Swap(ListValue* other) { + list_.swap(other->list_); +} + +bool ListValue::GetAsList(ListValue** out_value) { + if (out_value) + *out_value = this; + return true; +} + +bool ListValue::GetAsList(const ListValue** out_value) const { + if (out_value) + *out_value = this; + return true; +} + +ListValue* ListValue::DeepCopy() const { + ListValue* result = new ListValue; + + for (ValueVector::const_iterator i(list_.begin()); i != list_.end(); ++i) + result->Append((*i)->DeepCopy()); + + return result; +} + +bool ListValue::Equals(const Value* other) const { + if (other->GetType() != GetType()) + return false; + + const ListValue* other_list = + static_cast<const ListValue*>(other); + const_iterator lhs_it, rhs_it; + for (lhs_it = begin(), rhs_it = other_list->begin(); + lhs_it != end() && rhs_it != other_list->end(); + ++lhs_it, ++rhs_it) { + if (!(*lhs_it)->Equals(*rhs_it)) + return false; + } + if (lhs_it != end() || rhs_it != other_list->end()) + return false; + + return true; +} + +ValueSerializer::~ValueSerializer() { +} + +std::ostream& operator<<(std::ostream& out, const Value& value) { + std::string json; + JSONWriter::WriteWithOptions(&value, + JSONWriter::OPTIONS_PRETTY_PRINT, + &json); + return out << json; +} + +} // namespace base
diff --git a/src/base/values.h b/src/base/values.h new file mode 100644 index 0000000..e105818 --- /dev/null +++ b/src/base/values.h
@@ -0,0 +1,531 @@ +// 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. + +// This file specifies a recursive data storage class called Value intended for +// storing setting and other persistable data. It includes the ability to +// specify (recursive) lists and dictionaries, so it's fairly expressive. +// However, the API is optimized for the common case, namely storing a +// hierarchical tree of simple values. Given a DictionaryValue root, you can +// easily do things like: +// +// root->SetString("global.pages.homepage", "http://goateleporter.com"); +// std::string homepage = "http://google.com"; // default/fallback value +// root->GetString("global.pages.homepage", &homepage); +// +// where "global" and "pages" are also DictionaryValues, and "homepage" is a +// string setting. If some elements of the path didn't exist yet, the +// SetString() method would create the missing elements and attach them to root +// before attaching the homepage value. + +#ifndef BASE_VALUES_H_ +#define BASE_VALUES_H_ + +#include <iterator> +#include <map> +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/string16.h" + +// This file declares "using base::Value", etc. at the bottom, so that +// current code can use these classes without the base namespace. In +// new code, please always use base::Value, etc. or add your own +// "using" declaration. +// http://crbug.com/88666 +namespace base { + +class BinaryValue; +class DictionaryValue; +class FundamentalValue; +class ListValue; +class StringValue; +class Value; + +typedef std::vector<Value*> ValueVector; +typedef std::map<std::string, Value*> ValueMap; + +// The Value class is the base class for Values. A Value can be instantiated +// via the Create*Value() factory methods, or by directly creating instances of +// the subclasses. +class BASE_EXPORT Value { + public: + enum Type { + TYPE_NULL = 0, + TYPE_BOOLEAN, + TYPE_INTEGER, + TYPE_DOUBLE, + TYPE_STRING, + TYPE_BINARY, + TYPE_DICTIONARY, + TYPE_LIST + }; + + virtual ~Value(); + + static Value* CreateNullValue(); + // DEPRECATED: Do not use the following 5 functions. Instead, use + // new FundamentalValue or new StringValue. + static FundamentalValue* CreateBooleanValue(bool in_value); + static FundamentalValue* CreateIntegerValue(int in_value); + static FundamentalValue* CreateDoubleValue(double in_value); + static StringValue* CreateStringValue(const std::string& in_value); + static StringValue* CreateStringValue(const string16& in_value); + + // Returns the type of the value stored by the current Value object. + // Each type will be implemented by only one subclass of Value, so it's + // safe to use the Type to determine whether you can cast from + // Value* to (Implementing Class)*. Also, a Value object never changes + // its type after construction. + Type GetType() const { return type_; } + + // Returns true if the current object represents a given type. + bool IsType(Type type) const { return type == type_; } + + // These methods allow the convenient retrieval of settings. + // If the current setting object can be converted into the given type, + // the value is returned through the |out_value| parameter and true is + // returned; otherwise, false is returned and |out_value| is unchanged. + virtual bool GetAsBoolean(bool* out_value) const; + virtual bool GetAsInteger(int* out_value) const; + virtual bool GetAsDouble(double* out_value) const; + virtual bool GetAsString(std::string* out_value) const; + virtual bool GetAsString(string16* out_value) const; + virtual bool GetAsList(ListValue** out_value); + virtual bool GetAsList(const ListValue** out_value) const; + virtual bool GetAsDictionary(DictionaryValue** out_value); + virtual bool GetAsDictionary(const DictionaryValue** out_value) const; + + // This creates a deep copy of the entire Value tree, and returns a pointer + // to the copy. The caller gets ownership of the copy, of course. + // + // Subclasses return their own type directly in their overrides; + // this works because C++ supports covariant return types. + virtual Value* DeepCopy() const; + + // Compares if two Value objects have equal contents. + virtual bool Equals(const Value* other) const; + + // Compares if two Value objects have equal contents. Can handle NULLs. + // NULLs are considered equal but different from Value::CreateNullValue(). + static bool Equals(const Value* a, const Value* b); + + protected: + // These aren't safe for end-users, but they are useful for subclasses. + explicit Value(Type type); + Value(const Value& that); + Value& operator=(const Value& that); + + private: + Type type_; +}; + +// FundamentalValue represents the simple fundamental types of values. +class BASE_EXPORT FundamentalValue : public Value { + public: + explicit FundamentalValue(bool in_value); + explicit FundamentalValue(int in_value); + explicit FundamentalValue(double in_value); + virtual ~FundamentalValue(); + + // Overridden from Value: + virtual bool GetAsBoolean(bool* out_value) const OVERRIDE; + virtual bool GetAsInteger(int* out_value) const OVERRIDE; + virtual bool GetAsDouble(double* out_value) const OVERRIDE; + virtual FundamentalValue* DeepCopy() const OVERRIDE; + virtual bool Equals(const Value* other) const OVERRIDE; + + private: + union { + bool boolean_value_; + int integer_value_; + double double_value_; + }; +}; + +class BASE_EXPORT StringValue : public Value { + public: + // Initializes a StringValue with a UTF-8 narrow character string. + explicit StringValue(const std::string& in_value); + + // Initializes a StringValue with a string16. + explicit StringValue(const string16& in_value); + + virtual ~StringValue(); + + // Overridden from Value: + virtual bool GetAsString(std::string* out_value) const OVERRIDE; + virtual bool GetAsString(string16* out_value) const OVERRIDE; + virtual StringValue* DeepCopy() const OVERRIDE; + virtual bool Equals(const Value* other) const OVERRIDE; + + private: + std::string value_; +}; + +class BASE_EXPORT BinaryValue: public Value { + public: + virtual ~BinaryValue(); + + // Creates a Value to represent a binary buffer. The new object takes + // ownership of the pointer passed in, if successful. + // Returns NULL if buffer is NULL. + static BinaryValue* Create(char* buffer, size_t size); + + // For situations where you want to keep ownership of your buffer, this + // factory method creates a new BinaryValue by copying the contents of the + // buffer that's passed in. + // Returns NULL if buffer is NULL. + static BinaryValue* CreateWithCopiedBuffer(const char* buffer, size_t size); + + size_t GetSize() const { return size_; } + char* GetBuffer() { return buffer_; } + const char* GetBuffer() const { return buffer_; } + + // Overridden from Value: + virtual BinaryValue* DeepCopy() const OVERRIDE; + virtual bool Equals(const Value* other) const OVERRIDE; + + private: + // Constructor is private so that only objects with valid buffer pointers + // and size values can be created. + BinaryValue(char* buffer, size_t size); + + char* buffer_; + size_t size_; + + DISALLOW_COPY_AND_ASSIGN(BinaryValue); +}; + +// DictionaryValue provides a key-value dictionary with (optional) "path" +// parsing for recursive access; see the comment at the top of the file. Keys +// are |std::string|s and should be UTF-8 encoded. +class BASE_EXPORT DictionaryValue : public Value { + public: + DictionaryValue(); + virtual ~DictionaryValue(); + + // Overridden from Value: + virtual bool GetAsDictionary(DictionaryValue** out_value) OVERRIDE; + virtual bool GetAsDictionary( + const DictionaryValue** out_value) const OVERRIDE; + + // Returns true if the current dictionary has a value for the given key. + bool HasKey(const std::string& key) const; + + // Returns the number of Values in this dictionary. + size_t size() const { return dictionary_.size(); } + + // Returns whether the dictionary is empty. + bool empty() const { return dictionary_.empty(); } + + // Clears any current contents of this dictionary. + void Clear(); + + // Sets the Value associated with the given path starting from this object. + // A path has the form "<key>" or "<key>.<key>.[...]", where "." indexes + // into the next DictionaryValue down. Obviously, "." can't be used + // within a key, but there are no other restrictions on keys. + // If the key at any step of the way doesn't exist, or exists but isn't + // a DictionaryValue, a new DictionaryValue will be created and attached + // to the path in that location. + // Note that the dictionary takes ownership of the value referenced by + // |in_value|, and therefore |in_value| must be non-NULL. + void Set(const std::string& path, Value* in_value); + + // Convenience forms of Set(). These methods will replace any existing + // value at that path, even if it has a different type. + void SetBoolean(const std::string& path, bool in_value); + void SetInteger(const std::string& path, int in_value); + void SetDouble(const std::string& path, double in_value); + void SetString(const std::string& path, const std::string& in_value); + void SetString(const std::string& path, const string16& in_value); + + // Like Set(), but without special treatment of '.'. This allows e.g. URLs to + // be used as paths. + void SetWithoutPathExpansion(const std::string& key, Value* in_value); + + // Convenience forms of SetWithoutPathExpansion(). + void SetBooleanWithoutPathExpansion(const std::string& path, bool in_value); + void SetIntegerWithoutPathExpansion(const std::string& path, int in_value); + void SetDoubleWithoutPathExpansion(const std::string& path, double in_value); + void SetStringWithoutPathExpansion(const std::string& path, + const std::string& in_value); + void SetStringWithoutPathExpansion(const std::string& path, + const string16& in_value); + + // Gets the Value associated with the given path starting from this object. + // A path has the form "<key>" or "<key>.<key>.[...]", where "." indexes + // into the next DictionaryValue down. If the path can be resolved + // successfully, the value for the last key in the path will be returned + // through the |out_value| parameter, and the function will return true. + // Otherwise, it will return false and |out_value| will be untouched. + // Note that the dictionary always owns the value that's returned. + bool Get(const std::string& path, const Value** out_value) const; + bool Get(const std::string& path, Value** out_value); + + // These are convenience forms of Get(). The value will be retrieved + // and the return value will be true if the path is valid and the value at + // the end of the path can be returned in the form specified. + bool GetBoolean(const std::string& path, bool* out_value) const; + bool GetInteger(const std::string& path, int* out_value) const; + bool GetDouble(const std::string& path, double* out_value) const; + bool GetString(const std::string& path, std::string* out_value) const; + bool GetString(const std::string& path, string16* out_value) const; + bool GetStringASCII(const std::string& path, std::string* out_value) const; + bool GetBinary(const std::string& path, const BinaryValue** out_value) const; + bool GetBinary(const std::string& path, BinaryValue** out_value); + bool GetDictionary(const std::string& path, + const DictionaryValue** out_value) const; + bool GetDictionary(const std::string& path, DictionaryValue** out_value); + bool GetList(const std::string& path, const ListValue** out_value) const; + bool GetList(const std::string& path, ListValue** out_value); + + // Like Get(), but without special treatment of '.'. This allows e.g. URLs to + // be used as paths. + bool GetWithoutPathExpansion(const std::string& key, + const Value** out_value) const; + bool GetWithoutPathExpansion(const std::string& key, Value** out_value); + bool GetBooleanWithoutPathExpansion(const std::string& key, + bool* out_value) const; + bool GetIntegerWithoutPathExpansion(const std::string& key, + int* out_value) const; + bool GetDoubleWithoutPathExpansion(const std::string& key, + double* out_value) const; + bool GetStringWithoutPathExpansion(const std::string& key, + std::string* out_value) const; + bool GetStringWithoutPathExpansion(const std::string& key, + string16* out_value) const; + bool GetDictionaryWithoutPathExpansion( + const std::string& key, + const DictionaryValue** out_value) const; + bool GetDictionaryWithoutPathExpansion(const std::string& key, + DictionaryValue** out_value); + bool GetListWithoutPathExpansion(const std::string& key, + const ListValue** out_value) const; + bool GetListWithoutPathExpansion(const std::string& key, + ListValue** out_value); + + // Removes the Value with the specified path from this dictionary (or one + // of its child dictionaries, if the path is more than just a local key). + // If |out_value| is non-NULL, the removed Value AND ITS OWNERSHIP will be + // passed out via out_value. If |out_value| is NULL, the removed value will + // be deleted. This method returns true if |path| is a valid path; otherwise + // it will return false and the DictionaryValue object will be unchanged. + virtual bool Remove(const std::string& path, Value** out_value); + + // Like Remove(), but without special treatment of '.'. This allows e.g. URLs + // to be used as paths. + virtual bool RemoveWithoutPathExpansion(const std::string& key, + 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(); + + // Merge |dictionary| into this dictionary. This is done recursively, i.e. any + // sub-dictionaries will be merged as well. In case of key collisions, the + // passed in dictionary takes precedence and data already present will be + // replaced. Values within |dictionary| are deep-copied, so |dictionary| may + // be freed any time after this call. + void MergeDictionary(const DictionaryValue* dictionary); + + // Swaps contents with the |other| dictionary. + virtual void Swap(DictionaryValue* other); + + // This class provides an iterator for the keys in the dictionary. + // It can't be used to modify the dictionary. + // + // YOU SHOULD ALWAYS USE THE XXXWithoutPathExpansion() APIs WITH THESE, NOT + // THE NORMAL XXX() APIs. This makes sure things will work correctly if any + // keys have '.'s in them. + class BASE_EXPORT key_iterator + : private std::iterator<std::input_iterator_tag, const std::string> { + public: + explicit key_iterator(ValueMap::const_iterator itr); + // Not explicit, because this is a copy constructor. + key_iterator(const key_iterator& rhs); + key_iterator operator++() { + ++itr_; + return *this; + } + const std::string& operator*() { return itr_->first; } + bool operator!=(const key_iterator& other) { return itr_ != other.itr_; } + bool operator==(const key_iterator& other) { return itr_ == other.itr_; } + + private: + ValueMap::const_iterator itr_; + }; + + key_iterator begin_keys() const { return key_iterator(dictionary_.begin()); } + key_iterator end_keys() const { return key_iterator(dictionary_.end()); } + + // This class provides an iterator over both keys and values in the + // dictionary. It can't be used to modify the dictionary. + class BASE_EXPORT Iterator { + public: + explicit Iterator(const DictionaryValue& target); + + bool HasNext() const { return it_ != target_.dictionary_.end(); } + void Advance() { ++it_; } + + const std::string& key() const { return it_->first; } + const Value& value() const { return *it_->second; } + + private: + const DictionaryValue& target_; + ValueMap::const_iterator it_; + }; + + // Overridden from Value: + virtual DictionaryValue* DeepCopy() const OVERRIDE; + virtual bool Equals(const Value* other) const OVERRIDE; + + private: + ValueMap dictionary_; + + DISALLOW_COPY_AND_ASSIGN(DictionaryValue); +}; + +// This type of Value represents a list of other Value values. +class BASE_EXPORT ListValue : public Value { + public: + typedef ValueVector::iterator iterator; + typedef ValueVector::const_iterator const_iterator; + + ListValue(); + virtual ~ListValue(); + + // Clears the contents of this ListValue + void Clear(); + + // Returns the number of Values in this list. + size_t GetSize() const { return list_.size(); } + + // Returns whether the list is empty. + bool empty() const { return list_.empty(); } + + // Sets the list item at the given index to be the Value specified by + // the value given. If the index beyond the current end of the list, null + // Values will be used to pad out the list. + // Returns true if successful, or false if the index was negative or + // the value is a null pointer. + bool Set(size_t index, Value* in_value); + + // Gets the Value at the given index. Modifies |out_value| (and returns true) + // only if the index falls within the current list range. + // Note that the list always owns the Value passed out via |out_value|. + bool Get(size_t index, const Value** out_value) const; + bool Get(size_t index, Value** out_value); + + // Convenience forms of Get(). Modifies |out_value| (and returns true) + // only if the index is valid and the Value at that index can be returned + // in the specified form. + bool GetBoolean(size_t index, bool* out_value) const; + bool GetInteger(size_t index, int* out_value) const; + bool GetDouble(size_t index, double* out_value) const; + bool GetString(size_t index, std::string* out_value) const; + bool GetString(size_t index, string16* out_value) const; + bool GetBinary(size_t index, const BinaryValue** out_value) const; + bool GetBinary(size_t index, BinaryValue** out_value); + bool GetDictionary(size_t index, const DictionaryValue** out_value) const; + bool GetDictionary(size_t index, DictionaryValue** out_value); + bool GetList(size_t index, const ListValue** out_value) const; + bool GetList(size_t index, ListValue** out_value); + + // Removes the Value with the specified index from this list. + // If |out_value| is non-NULL, the removed Value AND ITS OWNERSHIP will be + // passed out via |out_value|. If |out_value| is NULL, the removed value will + // be deleted. This method returns true if |index| is valid; otherwise + // it will return false and the ListValue object will be unchanged. + virtual bool Remove(size_t index, Value** out_value); + + // Removes the first instance of |value| found in the list, if any, and + // deletes it. |index| is the location where |value| was found. Returns false + // if not found. + bool Remove(const Value& value, size_t* index); + + // Removes the element at |iter|. If |out_value| is NULL, the value will be + // deleted, otherwise ownership of the value is passed back to the caller. + void Erase(iterator iter, Value** out_value); + + // Appends a Value to the end of the list. + void Append(Value* in_value); + + // Convenience forms of Append. + void AppendBoolean(bool in_value); + void AppendInteger(int in_value); + void AppendDouble(double in_value); + void AppendString(const std::string& in_value); + void AppendString(const string16& in_value); + void AppendStrings(const std::vector<std::string>& in_values); + void AppendStrings(const std::vector<string16>& in_values); + + // Appends a Value if it's not already present. Takes ownership of the + // |in_value|. Returns true if successful, or false if the value was already + // present. If the value was already present the |in_value| is deleted. + bool AppendIfNotPresent(Value* in_value); + + // Insert a Value at index. + // Returns true if successful, or false if the index was out of range. + bool Insert(size_t index, Value* in_value); + + // Searches for the first instance of |value| in the list using the Equals + // method of the Value type. + // Returns a const_iterator to the found item or to end() if none exists. + const_iterator Find(const Value& value) const; + + // Swaps contents with the |other| list. + virtual void Swap(ListValue* other); + + // Iteration. + iterator begin() { return list_.begin(); } + iterator end() { return list_.end(); } + + const_iterator begin() const { return list_.begin(); } + const_iterator end() const { return list_.end(); } + + // Overridden from Value: + virtual bool GetAsList(ListValue** out_value) OVERRIDE; + virtual bool GetAsList(const ListValue** out_value) const OVERRIDE; + virtual ListValue* DeepCopy() const OVERRIDE; + virtual bool Equals(const Value* other) const OVERRIDE; + + private: + ValueVector list_; + + DISALLOW_COPY_AND_ASSIGN(ListValue); +}; + +// This interface is implemented by classes that know how to serialize and +// deserialize Value objects. +class BASE_EXPORT ValueSerializer { + public: + virtual ~ValueSerializer(); + + virtual bool Serialize(const Value& root) = 0; + + // This method deserializes the subclass-specific format into a Value object. + // If the return value is non-NULL, the caller takes ownership of returned + // Value. If the return value is NULL, and if error_code is non-NULL, + // error_code will be set with the underlying error. + // If |error_message| is non-null, it will be filled in with a formatted + // error message including the location of the error if appropriate. + virtual Value* Deserialize(int* error_code, std::string* error_str) = 0; +}; + +// Stream operator so Values can be used in assertion statements. +BASE_EXPORT std::ostream& operator<<(std::ostream& out, const Value& value); + +} // namespace base + +// http://crbug.com/88666 +using base::DictionaryValue; +using base::ListValue; +using base::StringValue; +using base::Value; + +#endif // BASE_VALUES_H_
diff --git a/src/base/values_unittest.cc b/src/base/values_unittest.cc new file mode 100644 index 0000000..8a42f78 --- /dev/null +++ b/src/base/values_unittest.cc
@@ -0,0 +1,786 @@ +// 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 <limits> + +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(ValuesTest, Basic) { + // Test basic dictionary getting/setting + DictionaryValue settings; + std::string homepage = "http://google.com"; + ASSERT_FALSE(settings.GetString("global.homepage", &homepage)); + ASSERT_EQ(std::string("http://google.com"), homepage); + + ASSERT_FALSE(settings.Get("global", NULL)); + settings.Set("global", new FundamentalValue(true)); + ASSERT_TRUE(settings.Get("global", NULL)); + settings.SetString("global.homepage", "http://scurvy.com"); + ASSERT_TRUE(settings.Get("global", NULL)); + homepage = "http://google.com"; + ASSERT_TRUE(settings.GetString("global.homepage", &homepage)); + ASSERT_EQ(std::string("http://scurvy.com"), homepage); + + // Test storing a dictionary in a list. + ListValue* toolbar_bookmarks; + ASSERT_FALSE( + settings.GetList("global.toolbar.bookmarks", &toolbar_bookmarks)); + + toolbar_bookmarks = new ListValue; + settings.Set("global.toolbar.bookmarks", toolbar_bookmarks); + ASSERT_TRUE(settings.GetList("global.toolbar.bookmarks", &toolbar_bookmarks)); + + DictionaryValue* new_bookmark = new DictionaryValue; + new_bookmark->SetString("name", "Froogle"); + new_bookmark->SetString("url", "http://froogle.com"); + toolbar_bookmarks->Append(new_bookmark); + + ListValue* bookmark_list; + ASSERT_TRUE(settings.GetList("global.toolbar.bookmarks", &bookmark_list)); + DictionaryValue* bookmark; + ASSERT_EQ(1U, bookmark_list->GetSize()); + ASSERT_TRUE(bookmark_list->GetDictionary(0, &bookmark)); + std::string bookmark_name = "Unnamed"; + ASSERT_TRUE(bookmark->GetString("name", &bookmark_name)); + ASSERT_EQ(std::string("Froogle"), bookmark_name); + std::string bookmark_url; + ASSERT_TRUE(bookmark->GetString("url", &bookmark_url)); + ASSERT_EQ(std::string("http://froogle.com"), bookmark_url); +} + +TEST(ValuesTest, List) { + scoped_ptr<ListValue> mixed_list(new ListValue()); + mixed_list->Set(0, new FundamentalValue(true)); + mixed_list->Set(1, new FundamentalValue(42)); + mixed_list->Set(2, new FundamentalValue(88.8)); + mixed_list->Set(3, new StringValue("foo")); + ASSERT_EQ(4u, mixed_list->GetSize()); + + Value *value = NULL; + bool bool_value = false; + int int_value = 0; + double double_value = 0.0; + std::string string_value; + + ASSERT_FALSE(mixed_list->Get(4, &value)); + + ASSERT_FALSE(mixed_list->GetInteger(0, &int_value)); + ASSERT_EQ(0, int_value); + ASSERT_FALSE(mixed_list->GetBoolean(1, &bool_value)); + ASSERT_FALSE(bool_value); + ASSERT_FALSE(mixed_list->GetString(2, &string_value)); + ASSERT_EQ("", string_value); + ASSERT_FALSE(mixed_list->GetInteger(2, &int_value)); + ASSERT_EQ(0, int_value); + ASSERT_FALSE(mixed_list->GetBoolean(3, &bool_value)); + ASSERT_FALSE(bool_value); + + ASSERT_TRUE(mixed_list->GetBoolean(0, &bool_value)); + ASSERT_TRUE(bool_value); + ASSERT_TRUE(mixed_list->GetInteger(1, &int_value)); + ASSERT_EQ(42, int_value); + // implicit conversion from Integer to Double should be possible. + ASSERT_TRUE(mixed_list->GetDouble(1, &double_value)); + ASSERT_EQ(42, double_value); + ASSERT_TRUE(mixed_list->GetDouble(2, &double_value)); + ASSERT_EQ(88.8, double_value); + ASSERT_TRUE(mixed_list->GetString(3, &string_value)); + ASSERT_EQ("foo", string_value); + + // Try searching in the mixed list. + base::FundamentalValue sought_value(42); + base::FundamentalValue not_found_value(false); + + ASSERT_NE(mixed_list->end(), mixed_list->Find(sought_value)); + ASSERT_TRUE((*mixed_list->Find(sought_value))->GetAsInteger(&int_value)); + ASSERT_EQ(42, int_value); + ASSERT_EQ(mixed_list->end(), mixed_list->Find(not_found_value)); +} + +TEST(ValuesTest, BinaryValue) { + char* buffer = NULL; + // Passing a null buffer pointer doesn't yield a BinaryValue + scoped_ptr<BinaryValue> binary(BinaryValue::Create(buffer, 0)); + ASSERT_FALSE(binary.get()); + + // If you want to represent an empty binary value, use a zero-length buffer. + buffer = new char[1]; + ASSERT_TRUE(buffer); + binary.reset(BinaryValue::Create(buffer, 0)); + ASSERT_TRUE(binary.get()); + ASSERT_TRUE(binary->GetBuffer()); + ASSERT_EQ(buffer, binary->GetBuffer()); + ASSERT_EQ(0U, binary->GetSize()); + + // Test the common case of a non-empty buffer + buffer = new char[15]; + binary.reset(BinaryValue::Create(buffer, 15)); + ASSERT_TRUE(binary.get()); + ASSERT_TRUE(binary->GetBuffer()); + ASSERT_EQ(buffer, binary->GetBuffer()); + ASSERT_EQ(15U, binary->GetSize()); + + char stack_buffer[42]; + memset(stack_buffer, '!', 42); + binary.reset(BinaryValue::CreateWithCopiedBuffer(stack_buffer, 42)); + ASSERT_TRUE(binary.get()); + ASSERT_TRUE(binary->GetBuffer()); + ASSERT_NE(stack_buffer, binary->GetBuffer()); + ASSERT_EQ(42U, binary->GetSize()); + ASSERT_EQ(0, memcmp(stack_buffer, binary->GetBuffer(), binary->GetSize())); +} + +TEST(ValuesTest, StringValue) { + // Test overloaded CreateStringValue. + scoped_ptr<Value> narrow_value(new StringValue("narrow")); + ASSERT_TRUE(narrow_value.get()); + ASSERT_TRUE(narrow_value->IsType(Value::TYPE_STRING)); + scoped_ptr<Value> utf16_value(new StringValue(ASCIIToUTF16("utf16"))); + ASSERT_TRUE(utf16_value.get()); + ASSERT_TRUE(utf16_value->IsType(Value::TYPE_STRING)); + + // Test overloaded GetString. + std::string narrow = "http://google.com"; + string16 utf16 = ASCIIToUTF16("http://google.com"); + ASSERT_TRUE(narrow_value->GetAsString(&narrow)); + ASSERT_TRUE(narrow_value->GetAsString(&utf16)); + ASSERT_EQ(std::string("narrow"), narrow); + ASSERT_EQ(ASCIIToUTF16("narrow"), utf16); + + ASSERT_TRUE(utf16_value->GetAsString(&narrow)); + ASSERT_TRUE(utf16_value->GetAsString(&utf16)); + ASSERT_EQ(std::string("utf16"), narrow); + ASSERT_EQ(ASCIIToUTF16("utf16"), utf16); +} + +// This is a Value object that allows us to tell if it's been +// properly deleted by modifying the value of external flag on destruction. +class DeletionTestValue : public Value { + public: + explicit DeletionTestValue(bool* deletion_flag) : Value(TYPE_NULL) { + Init(deletion_flag); // Separate function so that we can use ASSERT_* + } + + void Init(bool* deletion_flag) { + ASSERT_TRUE(deletion_flag); + deletion_flag_ = deletion_flag; + *deletion_flag_ = false; + } + + virtual ~DeletionTestValue() { + *deletion_flag_ = true; + } + + private: + bool* deletion_flag_; +}; + +TEST(ValuesTest, ListDeletion) { + bool deletion_flag = true; + + { + ListValue list; + list.Append(new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + } + EXPECT_TRUE(deletion_flag); + + { + ListValue list; + list.Append(new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + list.Clear(); + EXPECT_TRUE(deletion_flag); + } + + { + ListValue list; + list.Append(new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + EXPECT_TRUE(list.Set(0, Value::CreateNullValue())); + EXPECT_TRUE(deletion_flag); + } +} + +TEST(ValuesTest, ListRemoval) { + bool deletion_flag = true; + Value* removed_item = NULL; + + { + ListValue list; + list.Append(new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + EXPECT_EQ(1U, list.GetSize()); + EXPECT_FALSE(list.Remove(std::numeric_limits<size_t>::max(), + &removed_item)); + EXPECT_FALSE(list.Remove(1, &removed_item)); + EXPECT_TRUE(list.Remove(0, &removed_item)); + ASSERT_TRUE(removed_item); + EXPECT_EQ(0U, list.GetSize()); + } + EXPECT_FALSE(deletion_flag); + delete removed_item; + removed_item = NULL; + EXPECT_TRUE(deletion_flag); + + { + ListValue list; + list.Append(new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + EXPECT_TRUE(list.Remove(0, NULL)); + EXPECT_TRUE(deletion_flag); + EXPECT_EQ(0U, list.GetSize()); + } + + { + ListValue list; + DeletionTestValue* value = new DeletionTestValue(&deletion_flag); + list.Append(value); + EXPECT_FALSE(deletion_flag); + size_t index = 0; + list.Remove(*value, &index); + EXPECT_EQ(0U, index); + EXPECT_TRUE(deletion_flag); + EXPECT_EQ(0U, list.GetSize()); + } +} + +TEST(ValuesTest, DictionaryDeletion) { + std::string key = "test"; + bool deletion_flag = true; + + { + DictionaryValue dict; + dict.Set(key, new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + } + EXPECT_TRUE(deletion_flag); + + { + DictionaryValue dict; + dict.Set(key, new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + dict.Clear(); + EXPECT_TRUE(deletion_flag); + } + + { + DictionaryValue dict; + dict.Set(key, new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + dict.Set(key, Value::CreateNullValue()); + EXPECT_TRUE(deletion_flag); + } +} + +TEST(ValuesTest, DictionaryRemoval) { + std::string key = "test"; + bool deletion_flag = true; + Value* removed_item = NULL; + + { + DictionaryValue dict; + dict.Set(key, new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + EXPECT_TRUE(dict.HasKey(key)); + EXPECT_FALSE(dict.Remove("absent key", &removed_item)); + EXPECT_TRUE(dict.Remove(key, &removed_item)); + EXPECT_FALSE(dict.HasKey(key)); + ASSERT_TRUE(removed_item); + } + EXPECT_FALSE(deletion_flag); + delete removed_item; + removed_item = NULL; + EXPECT_TRUE(deletion_flag); + + { + DictionaryValue dict; + dict.Set(key, new DeletionTestValue(&deletion_flag)); + EXPECT_FALSE(deletion_flag); + EXPECT_TRUE(dict.HasKey(key)); + EXPECT_TRUE(dict.Remove(key, NULL)); + EXPECT_TRUE(deletion_flag); + EXPECT_FALSE(dict.HasKey(key)); + } +} + +TEST(ValuesTest, DictionaryWithoutPathExpansion) { + DictionaryValue dict; + dict.Set("this.is.expanded", Value::CreateNullValue()); + dict.SetWithoutPathExpansion("this.isnt.expanded", Value::CreateNullValue()); + + EXPECT_FALSE(dict.HasKey("this.is.expanded")); + EXPECT_TRUE(dict.HasKey("this")); + Value* value1; + EXPECT_TRUE(dict.Get("this", &value1)); + DictionaryValue* value2; + ASSERT_TRUE(dict.GetDictionaryWithoutPathExpansion("this", &value2)); + EXPECT_EQ(value1, value2); + EXPECT_EQ(1U, value2->size()); + + EXPECT_TRUE(dict.HasKey("this.isnt.expanded")); + Value* value3; + EXPECT_FALSE(dict.Get("this.isnt.expanded", &value3)); + Value* value4; + ASSERT_TRUE(dict.GetWithoutPathExpansion("this.isnt.expanded", &value4)); + EXPECT_EQ(Value::TYPE_NULL, value4->GetType()); +} + +TEST(ValuesTest, DeepCopy) { + DictionaryValue original_dict; + Value* original_null = Value::CreateNullValue(); + original_dict.Set("null", original_null); + FundamentalValue* original_bool = new FundamentalValue(true); + original_dict.Set("bool", original_bool); + FundamentalValue* original_int = new FundamentalValue(42); + original_dict.Set("int", original_int); + FundamentalValue* original_double = new FundamentalValue(3.14); + original_dict.Set("double", original_double); + StringValue* original_string = new StringValue("hello"); + original_dict.Set("string", original_string); + StringValue* original_string16 = new StringValue(ASCIIToUTF16("hello16")); + original_dict.Set("string16", original_string16); + + char* original_buffer = new char[42]; + memset(original_buffer, '!', 42); + BinaryValue* original_binary = BinaryValue::Create(original_buffer, 42); + original_dict.Set("binary", original_binary); + + ListValue* original_list = new ListValue(); + FundamentalValue* original_list_element_0 = new FundamentalValue(0); + original_list->Append(original_list_element_0); + FundamentalValue* original_list_element_1 = new FundamentalValue(1); + original_list->Append(original_list_element_1); + original_dict.Set("list", original_list); + + DictionaryValue* original_nested_dictionary = new DictionaryValue(); + original_nested_dictionary->Set("key", new StringValue("value")); + original_dict.Set("dictionary", original_nested_dictionary); + + scoped_ptr<DictionaryValue> copy_dict(original_dict.DeepCopy()); + ASSERT_TRUE(copy_dict.get()); + ASSERT_NE(copy_dict.get(), &original_dict); + + Value* copy_null = NULL; + ASSERT_TRUE(copy_dict->Get("null", ©_null)); + ASSERT_TRUE(copy_null); + ASSERT_NE(copy_null, original_null); + ASSERT_TRUE(copy_null->IsType(Value::TYPE_NULL)); + + Value* copy_bool = NULL; + ASSERT_TRUE(copy_dict->Get("bool", ©_bool)); + ASSERT_TRUE(copy_bool); + ASSERT_NE(copy_bool, original_bool); + ASSERT_TRUE(copy_bool->IsType(Value::TYPE_BOOLEAN)); + bool copy_bool_value = false; + ASSERT_TRUE(copy_bool->GetAsBoolean(©_bool_value)); + ASSERT_TRUE(copy_bool_value); + + Value* copy_int = NULL; + ASSERT_TRUE(copy_dict->Get("int", ©_int)); + ASSERT_TRUE(copy_int); + ASSERT_NE(copy_int, original_int); + ASSERT_TRUE(copy_int->IsType(Value::TYPE_INTEGER)); + int copy_int_value = 0; + ASSERT_TRUE(copy_int->GetAsInteger(©_int_value)); + ASSERT_EQ(42, copy_int_value); + + Value* copy_double = NULL; + ASSERT_TRUE(copy_dict->Get("double", ©_double)); + ASSERT_TRUE(copy_double); + ASSERT_NE(copy_double, original_double); + ASSERT_TRUE(copy_double->IsType(Value::TYPE_DOUBLE)); + double copy_double_value = 0; + ASSERT_TRUE(copy_double->GetAsDouble(©_double_value)); + ASSERT_EQ(3.14, copy_double_value); + + Value* copy_string = NULL; + ASSERT_TRUE(copy_dict->Get("string", ©_string)); + ASSERT_TRUE(copy_string); + ASSERT_NE(copy_string, original_string); + ASSERT_TRUE(copy_string->IsType(Value::TYPE_STRING)); + std::string copy_string_value; + string16 copy_string16_value; + ASSERT_TRUE(copy_string->GetAsString(©_string_value)); + ASSERT_TRUE(copy_string->GetAsString(©_string16_value)); + ASSERT_EQ(std::string("hello"), copy_string_value); + ASSERT_EQ(ASCIIToUTF16("hello"), copy_string16_value); + + Value* copy_string16 = NULL; + ASSERT_TRUE(copy_dict->Get("string16", ©_string16)); + ASSERT_TRUE(copy_string16); + ASSERT_NE(copy_string16, original_string16); + ASSERT_TRUE(copy_string16->IsType(Value::TYPE_STRING)); + ASSERT_TRUE(copy_string16->GetAsString(©_string_value)); + ASSERT_TRUE(copy_string16->GetAsString(©_string16_value)); + ASSERT_EQ(std::string("hello16"), copy_string_value); + ASSERT_EQ(ASCIIToUTF16("hello16"), copy_string16_value); + + Value* copy_binary = NULL; + ASSERT_TRUE(copy_dict->Get("binary", ©_binary)); + ASSERT_TRUE(copy_binary); + ASSERT_NE(copy_binary, original_binary); + ASSERT_TRUE(copy_binary->IsType(Value::TYPE_BINARY)); + ASSERT_NE(original_binary->GetBuffer(), + static_cast<BinaryValue*>(copy_binary)->GetBuffer()); + ASSERT_EQ(original_binary->GetSize(), + static_cast<BinaryValue*>(copy_binary)->GetSize()); + ASSERT_EQ(0, memcmp(original_binary->GetBuffer(), + static_cast<BinaryValue*>(copy_binary)->GetBuffer(), + original_binary->GetSize())); + + Value* copy_value = NULL; + ASSERT_TRUE(copy_dict->Get("list", ©_value)); + ASSERT_TRUE(copy_value); + ASSERT_NE(copy_value, original_list); + ASSERT_TRUE(copy_value->IsType(Value::TYPE_LIST)); + ListValue* copy_list = NULL; + ASSERT_TRUE(copy_value->GetAsList(©_list)); + ASSERT_TRUE(copy_list); + ASSERT_EQ(2U, copy_list->GetSize()); + + Value* copy_list_element_0; + ASSERT_TRUE(copy_list->Get(0, ©_list_element_0)); + ASSERT_TRUE(copy_list_element_0); + ASSERT_NE(copy_list_element_0, original_list_element_0); + int copy_list_element_0_value; + ASSERT_TRUE(copy_list_element_0->GetAsInteger(©_list_element_0_value)); + ASSERT_EQ(0, copy_list_element_0_value); + + Value* copy_list_element_1; + ASSERT_TRUE(copy_list->Get(1, ©_list_element_1)); + ASSERT_TRUE(copy_list_element_1); + ASSERT_NE(copy_list_element_1, original_list_element_1); + int copy_list_element_1_value; + ASSERT_TRUE(copy_list_element_1->GetAsInteger(©_list_element_1_value)); + ASSERT_EQ(1, copy_list_element_1_value); + + copy_value = NULL; + ASSERT_TRUE(copy_dict->Get("dictionary", ©_value)); + ASSERT_TRUE(copy_value); + ASSERT_NE(copy_value, original_nested_dictionary); + ASSERT_TRUE(copy_value->IsType(Value::TYPE_DICTIONARY)); + DictionaryValue* copy_nested_dictionary = NULL; + ASSERT_TRUE(copy_value->GetAsDictionary(©_nested_dictionary)); + ASSERT_TRUE(copy_nested_dictionary); + EXPECT_TRUE(copy_nested_dictionary->HasKey("key")); +} + +TEST(ValuesTest, Equals) { + Value* null1 = Value::CreateNullValue(); + Value* null2 = Value::CreateNullValue(); + EXPECT_NE(null1, null2); + EXPECT_TRUE(null1->Equals(null2)); + + Value* boolean = new FundamentalValue(false); + EXPECT_FALSE(null1->Equals(boolean)); + delete null1; + delete null2; + delete boolean; + + DictionaryValue dv; + dv.SetBoolean("a", false); + dv.SetInteger("b", 2); + dv.SetDouble("c", 2.5); + dv.SetString("d1", "string"); + dv.SetString("d2", ASCIIToUTF16("http://google.com")); + dv.Set("e", Value::CreateNullValue()); + + scoped_ptr<DictionaryValue> copy; + copy.reset(dv.DeepCopy()); + EXPECT_TRUE(dv.Equals(copy.get())); + + ListValue* list = new ListValue; + list->Append(Value::CreateNullValue()); + list->Append(new DictionaryValue); + dv.Set("f", list); + + EXPECT_FALSE(dv.Equals(copy.get())); + copy->Set("f", list->DeepCopy()); + EXPECT_TRUE(dv.Equals(copy.get())); + + list->Append(new FundamentalValue(true)); + EXPECT_FALSE(dv.Equals(copy.get())); + + // Check if Equals detects differences in only the keys. + copy.reset(dv.DeepCopy()); + EXPECT_TRUE(dv.Equals(copy.get())); + copy->Remove("a", NULL); + copy->SetBoolean("aa", false); + EXPECT_FALSE(dv.Equals(copy.get())); +} + +TEST(ValuesTest, StaticEquals) { + scoped_ptr<Value> null1(Value::CreateNullValue()); + scoped_ptr<Value> null2(Value::CreateNullValue()); + EXPECT_TRUE(Value::Equals(null1.get(), null2.get())); + EXPECT_TRUE(Value::Equals(NULL, NULL)); + + scoped_ptr<Value> i42(new FundamentalValue(42)); + scoped_ptr<Value> j42(new FundamentalValue(42)); + scoped_ptr<Value> i17(new FundamentalValue(17)); + EXPECT_TRUE(Value::Equals(i42.get(), i42.get())); + EXPECT_TRUE(Value::Equals(j42.get(), i42.get())); + EXPECT_TRUE(Value::Equals(i42.get(), j42.get())); + EXPECT_FALSE(Value::Equals(i42.get(), i17.get())); + EXPECT_FALSE(Value::Equals(i42.get(), NULL)); + EXPECT_FALSE(Value::Equals(NULL, i42.get())); + + // NULL and Value::CreateNullValue() are intentionally different: We need + // support for NULL as a return value for "undefined" without caring for + // ownership of the pointer. + EXPECT_FALSE(Value::Equals(null1.get(), NULL)); + EXPECT_FALSE(Value::Equals(NULL, null1.get())); +} + +TEST(ValuesTest, DeepCopyCovariantReturnTypes) { + DictionaryValue original_dict; + Value* original_null = Value::CreateNullValue(); + original_dict.Set("null", original_null); + FundamentalValue* original_bool = new FundamentalValue(true); + original_dict.Set("bool", original_bool); + FundamentalValue* original_int = new FundamentalValue(42); + original_dict.Set("int", original_int); + FundamentalValue* original_double = new FundamentalValue(3.14); + original_dict.Set("double", original_double); + StringValue* original_string = new StringValue("hello"); + original_dict.Set("string", original_string); + StringValue* original_string16 = new StringValue(ASCIIToUTF16("hello16")); + original_dict.Set("string16", original_string16); + + char* original_buffer = new char[42]; + memset(original_buffer, '!', 42); + BinaryValue* original_binary = BinaryValue::Create(original_buffer, 42); + original_dict.Set("binary", original_binary); + + ListValue* original_list = new ListValue(); + FundamentalValue* original_list_element_0 = new FundamentalValue(0); + original_list->Append(original_list_element_0); + FundamentalValue* original_list_element_1 = new FundamentalValue(1); + original_list->Append(original_list_element_1); + original_dict.Set("list", original_list); + + Value* original_dict_value = &original_dict; + Value* original_bool_value = original_bool; + Value* original_int_value = original_int; + Value* original_double_value = original_double; + Value* original_string_value = original_string; + Value* original_string16_value = original_string16; + Value* original_binary_value = original_binary; + Value* original_list_value = original_list; + + scoped_ptr<Value> copy_dict_value(original_dict_value->DeepCopy()); + scoped_ptr<Value> copy_bool_value(original_bool_value->DeepCopy()); + scoped_ptr<Value> copy_int_value(original_int_value->DeepCopy()); + scoped_ptr<Value> copy_double_value(original_double_value->DeepCopy()); + scoped_ptr<Value> copy_string_value(original_string_value->DeepCopy()); + scoped_ptr<Value> copy_string16_value(original_string16_value->DeepCopy()); + scoped_ptr<Value> copy_binary_value(original_binary_value->DeepCopy()); + scoped_ptr<Value> copy_list_value(original_list_value->DeepCopy()); + + EXPECT_TRUE(original_dict_value->Equals(copy_dict_value.get())); + EXPECT_TRUE(original_bool_value->Equals(copy_bool_value.get())); + EXPECT_TRUE(original_int_value->Equals(copy_int_value.get())); + EXPECT_TRUE(original_double_value->Equals(copy_double_value.get())); + EXPECT_TRUE(original_string_value->Equals(copy_string_value.get())); + EXPECT_TRUE(original_string16_value->Equals(copy_string16_value.get())); + EXPECT_TRUE(original_binary_value->Equals(copy_binary_value.get())); + EXPECT_TRUE(original_list_value->Equals(copy_list_value.get())); +} + +TEST(ValuesTest, RemoveEmptyChildren) { + scoped_ptr<DictionaryValue> root(new DictionaryValue); + // Remove empty lists and dictionaries. + root->Set("empty_dict", new DictionaryValue); + root->Set("empty_list", new ListValue); + root->SetWithoutPathExpansion("a.b.c.d.e", new DictionaryValue); + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_TRUE(root->empty()); + + // Make sure we don't prune too much. + root->SetBoolean("bool", true); + root->Set("empty_dict", new DictionaryValue); + root->SetString("empty_string", ""); + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_EQ(2U, root->size()); + + // Should do nothing. + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_EQ(2U, root->size()); + + // Nested test cases. These should all reduce back to the bool and string + // set above. + { + root->Set("a.b.c.d.e", new DictionaryValue); + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_EQ(2U, root->size()); + } + { + DictionaryValue* inner = new DictionaryValue; + root->Set("dict_with_emtpy_children", inner); + inner->Set("empty_dict", new DictionaryValue); + inner->Set("empty_list", new ListValue); + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_EQ(2U, root->size()); + } + { + ListValue* inner = new ListValue; + root->Set("list_with_empty_children", inner); + inner->Append(new DictionaryValue); + inner->Append(new ListValue); + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_EQ(2U, root->size()); + } + + // Nested with siblings. + { + ListValue* inner = new ListValue; + root->Set("list_with_empty_children", inner); + inner->Append(new DictionaryValue); + inner->Append(new ListValue); + DictionaryValue* inner2 = new DictionaryValue; + root->Set("dict_with_empty_children", inner2); + inner2->Set("empty_dict", new DictionaryValue); + inner2->Set("empty_list", new ListValue); + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_EQ(2U, root->size()); + } + + // Make sure nested values don't get pruned. + { + ListValue* inner = new ListValue; + root->Set("list_with_empty_children", inner); + ListValue* inner2 = new ListValue; + inner->Append(new DictionaryValue); + inner->Append(inner2); + inner2->Append(new StringValue("hello")); + root.reset(root->DeepCopyWithoutEmptyChildren()); + EXPECT_EQ(3U, root->size()); + EXPECT_TRUE(root->GetList("list_with_empty_children", &inner)); + EXPECT_EQ(1U, inner->GetSize()); // Dictionary was pruned. + EXPECT_TRUE(inner->GetList(0, &inner2)); + EXPECT_EQ(1U, inner2->GetSize()); + } +} + +TEST(ValuesTest, MergeDictionary) { + scoped_ptr<DictionaryValue> base(new DictionaryValue); + base->SetString("base_key", "base_key_value_base"); + base->SetString("collide_key", "collide_key_value_base"); + DictionaryValue* base_sub_dict = new DictionaryValue; + base_sub_dict->SetString("sub_base_key", "sub_base_key_value_base"); + base_sub_dict->SetString("sub_collide_key", "sub_collide_key_value_base"); + base->Set("sub_dict_key", base_sub_dict); + + scoped_ptr<DictionaryValue> merge(new DictionaryValue); + merge->SetString("merge_key", "merge_key_value_merge"); + merge->SetString("collide_key", "collide_key_value_merge"); + DictionaryValue* merge_sub_dict = new DictionaryValue; + merge_sub_dict->SetString("sub_merge_key", "sub_merge_key_value_merge"); + merge_sub_dict->SetString("sub_collide_key", "sub_collide_key_value_merge"); + merge->Set("sub_dict_key", merge_sub_dict); + + base->MergeDictionary(merge.get()); + + EXPECT_EQ(4U, base->size()); + std::string base_key_value; + EXPECT_TRUE(base->GetString("base_key", &base_key_value)); + EXPECT_EQ("base_key_value_base", base_key_value); // Base value preserved. + std::string collide_key_value; + EXPECT_TRUE(base->GetString("collide_key", &collide_key_value)); + EXPECT_EQ("collide_key_value_merge", collide_key_value); // Replaced. + std::string merge_key_value; + EXPECT_TRUE(base->GetString("merge_key", &merge_key_value)); + EXPECT_EQ("merge_key_value_merge", merge_key_value); // Merged in. + + DictionaryValue* res_sub_dict; + EXPECT_TRUE(base->GetDictionary("sub_dict_key", &res_sub_dict)); + EXPECT_EQ(3U, res_sub_dict->size()); + std::string sub_base_key_value; + EXPECT_TRUE(res_sub_dict->GetString("sub_base_key", &sub_base_key_value)); + EXPECT_EQ("sub_base_key_value_base", sub_base_key_value); // Preserved. + std::string sub_collide_key_value; + EXPECT_TRUE(res_sub_dict->GetString("sub_collide_key", + &sub_collide_key_value)); + EXPECT_EQ("sub_collide_key_value_merge", sub_collide_key_value); // Replaced. + std::string sub_merge_key_value; + EXPECT_TRUE(res_sub_dict->GetString("sub_merge_key", &sub_merge_key_value)); + EXPECT_EQ("sub_merge_key_value_merge", sub_merge_key_value); // Merged in. +} + +TEST(ValuesTest, MergeDictionaryDeepCopy) { + DictionaryValue* child = new DictionaryValue; + child->SetString("test", "value"); + EXPECT_EQ(1U, child->size()); + + std::string value; + EXPECT_TRUE(child->GetString("test", &value)); + EXPECT_EQ("value", value); + + scoped_ptr<DictionaryValue> base(new DictionaryValue); + base->Set("dict", child); + EXPECT_EQ(1U, base->size()); + + DictionaryValue* ptr; + EXPECT_TRUE(base->GetDictionary("dict", &ptr)); + EXPECT_EQ(child, ptr); + + scoped_ptr<DictionaryValue> merged(new DictionaryValue); + merged->MergeDictionary(base.get()); + EXPECT_EQ(1U, merged->size()); + EXPECT_TRUE(merged->GetDictionary("dict", &ptr)); + EXPECT_NE(child, ptr); + EXPECT_TRUE(ptr->GetString("test", &value)); + EXPECT_EQ("value", value); + + child->SetString("test", "overwrite"); + base.reset(); + EXPECT_TRUE(ptr->GetString("test", &value)); + EXPECT_EQ("value", value); +} + +TEST(ValuesTest, DictionaryIterator) { + DictionaryValue dict; + for (DictionaryValue::Iterator it(dict); it.HasNext(); it.Advance()) { + ADD_FAILURE(); + } + + StringValue value1("value1"); + dict.Set("key1", value1.DeepCopy()); + bool seen1 = false; + for (DictionaryValue::Iterator it(dict); it.HasNext(); it.Advance()) { + EXPECT_FALSE(seen1); + EXPECT_EQ("key1", it.key()); + EXPECT_TRUE(value1.Equals(&it.value())); + seen1 = true; + } + EXPECT_TRUE(seen1); + + StringValue value2("value2"); + dict.Set("key2", value2.DeepCopy()); + bool seen2 = seen1 = false; + for (DictionaryValue::Iterator it(dict); it.HasNext(); it.Advance()) { + if (it.key() == "key1") { + EXPECT_FALSE(seen1); + EXPECT_TRUE(value1.Equals(&it.value())); + seen1 = true; + } else if (it.key() == "key2") { + EXPECT_FALSE(seen2); + EXPECT_TRUE(value2.Equals(&it.value())); + seen2 = true; + } else { + ADD_FAILURE(); + } + } + EXPECT_TRUE(seen1); + EXPECT_TRUE(seen2); +} + +} // namespace base
diff --git a/src/base/version.cc b/src/base/version.cc new file mode 100644 index 0000000..b7db4b5 --- /dev/null +++ b/src/base/version.cc
@@ -0,0 +1,172 @@ +// 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/version.h" + +#include <algorithm> + +#include "base/logging.h" +#include "base/string_number_conversions.h" +#include "base/string_split.h" +#include "base/string_util.h" + +namespace { + +// Parses the |numbers| vector representing the different numbers +// inside the version string and constructs a vector of valid integers. It stops +// when it reaches an invalid item (including the wildcard character). |parsed| +// is the resulting integer vector. Function returns true if all numbers were +// parsed successfully, false otherwise. +bool ParseVersionNumbers(const std::string& version_str, + std::vector<uint16>* parsed) { + std::vector<std::string> numbers; + base::SplitString(version_str, '.', &numbers); + if (numbers.empty()) + return false; + + for (std::vector<std::string>::const_iterator it = numbers.begin(); + it != numbers.end(); ++it) { + int num; + if (!base::StringToInt(*it, &num)) + return false; + + if (num < 0) + return false; + + const uint16 max = 0xFFFF; + if (num > max) + return false; + + // This throws out things like +3, or 032. + if (base::IntToString(num) != *it) + return false; + + parsed->push_back(static_cast<uint16>(num)); + } + return true; +} + +// Compares version components in |components1| with components in +// |components2|. Returns -1, 0 or 1 if |components1| is greater than, equal to, +// or less than |components2|, respectively. +int CompareVersionComponents(const std::vector<uint16>& components1, + const std::vector<uint16>& components2) { + const size_t count = std::min(components1.size(), components2.size()); + for (size_t i = 0; i < count; ++i) { + if (components1[i] > components2[i]) + return 1; + if (components1[i] < components2[i]) + return -1; + } + if (components1.size() > components2.size()) { + for (size_t i = count; i < components1.size(); ++i) { + if (components1[i] > 0) + return 1; + } + } else if (components1.size() < components2.size()) { + for (size_t i = count; i < components2.size(); ++i) { + if (components2[i] > 0) + return -1; + } + } + return 0; +} + +} // namespace + +Version::Version() { +} + +Version::~Version() { +} + +Version::Version(const std::string& version_str) { + std::vector<uint16> parsed; + if (!ParseVersionNumbers(version_str, &parsed)) + return; + + components_.swap(parsed); +} + +bool Version::IsValid() const { + return (!components_.empty()); +} + +// static +bool Version::IsValidWildcardString(const std::string& wildcard_string) { + std::string version_string = wildcard_string; + if (EndsWith(wildcard_string.c_str(), ".*", false)) + version_string = wildcard_string.substr(0, wildcard_string.size() - 2); + + Version version(version_string); + return version.IsValid(); +} + +bool Version::IsOlderThan(const std::string& version_str) const { + Version proposed_ver(version_str); + if (!proposed_ver.IsValid()) + return false; + return (CompareTo(proposed_ver) < 0); +} + +int Version::CompareToWildcardString(const std::string& wildcard_string) const { + DCHECK(IsValid()); + DCHECK(Version::IsValidWildcardString(wildcard_string)); + + // Default behavior if the string doesn't end with a wildcard. + if (!EndsWith(wildcard_string.c_str(), ".*", false)) { + Version version(wildcard_string); + DCHECK(version.IsValid()); + return CompareTo(version); + } + + std::vector<uint16> parsed; + const bool success = ParseVersionNumbers( + wildcard_string.substr(0, wildcard_string.length() - 2), &parsed); + DCHECK(success); + const int comparison = CompareVersionComponents(components_, parsed); + // If the version is smaller than the wildcard version's |parsed| vector, + // then the wildcard has no effect (e.g. comparing 1.2.3 and 1.3.*) and the + // version is still smaller. Same logic for equality (e.g. comparing 1.2.2 to + // 1.2.2.* is 0 regardless of the wildcard). Under this logic, + // 1.2.0.0.0.0 compared to 1.2.* is 0. + if (comparison == -1 || comparison == 0) + return comparison; + + // Catch the case where the digits of |parsed| are found in |components_|, + // which means that the two are equal since |parsed| has a trailing "*". + // (e.g. 1.2.3 vs. 1.2.* will return 0). All other cases return 1 since + // components is greater (e.g. 3.2.3 vs 1.*). + DCHECK_GT(parsed.size(), 0UL); + const size_t min_num_comp = std::min(components_.size(), parsed.size()); + for (size_t i = 0; i < min_num_comp; ++i) { + if (components_[i] != parsed[i]) + return 1; + } + return 0; +} + +bool Version::Equals(const Version& that) const { + DCHECK(IsValid()); + DCHECK(that.IsValid()); + return (CompareTo(that) == 0); +} + +int Version::CompareTo(const Version& other) const { + DCHECK(IsValid()); + DCHECK(other.IsValid()); + return CompareVersionComponents(components_, other.components_); +} + +const std::string Version::GetString() const { + DCHECK(IsValid()); + std::string version_str; + size_t count = components_.size(); + for (size_t i = 0; i < count - 1; ++i) { + version_str.append(base::IntToString(components_[i])); + version_str.append("."); + } + version_str.append(base::IntToString(components_[count - 1])); + return version_str; +}
diff --git a/src/base/version.h b/src/base/version.h new file mode 100644 index 0000000..ab7145e --- /dev/null +++ b/src/base/version.h
@@ -0,0 +1,64 @@ +// 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_VERSION_H_ +#define BASE_VERSION_H_ + +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" + +// Version represents a dotted version number, like "1.2.3.4", supporting +// parsing and comparison. +class BASE_EXPORT Version { + public: + // The only thing you can legally do to a default constructed + // Version object is assign to it. + Version(); + + ~Version(); + + // Initializes from a decimal dotted version number, like "0.1.1". + // Each component is limited to a uint16. Call IsValid() to learn + // the outcome. + explicit Version(const std::string& version_str); + + // Returns true if the object contains a valid version number. + bool IsValid() const; + + // Returns true if the version wildcard string is valid. The version wildcard + // string may end with ".*" (e.g. 1.2.*, 1.*). Any other arrangement with "*" + // is invalid (e.g. 1.*.3 or 1.2.3*). This functions defaults to standard + // Version behavior (IsValid) if no wildcard is present. + static bool IsValidWildcardString(const std::string& wildcard_string); + + // Commonly used pattern. Given a valid version object, compare if a + // |version_str| results in a newer version. Returns true if the + // string represents valid version and if the version is greater than + // than the version of this object. + bool IsOlderThan(const std::string& version_str) const; + + bool Equals(const Version& other) const; + + // Returns -1, 0, 1 for <, ==, >. + int CompareTo(const Version& other) const; + + // Given a valid version object, compare if a |wildcard_string| results in a + // newer version. This function will default to CompareTo if the string does + // not end in wildcard sequence ".*". IsValidWildcard(wildcard_string) must be + // true before using this function. + int CompareToWildcardString(const std::string& wildcard_string) const; + + // Return the string representation of this version. + const std::string GetString() const; + + const std::vector<uint16>& components() const { return components_; } + + private: + std::vector<uint16> components_; +}; + +#endif // BASE_VERSION_H_
diff --git a/src/base/version_unittest.cc b/src/base/version_unittest.cc new file mode 100644 index 0000000..2a2309e --- /dev/null +++ b/src/base/version_unittest.cc
@@ -0,0 +1,141 @@ +// 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/version.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +TEST(VersionTest, DefaultConstructor) { + Version v; + EXPECT_FALSE(v.IsValid()); +} + +TEST(VersionTest, ValueSemantics) { + Version v1("1.2.3.4"); + EXPECT_TRUE(v1.IsValid()); + Version v3; + EXPECT_FALSE(v3.IsValid()); + { + Version v2(v1); + v3 = v2; + EXPECT_TRUE(v2.IsValid()); + EXPECT_TRUE(v1.Equals(v2)); + } + EXPECT_TRUE(v3.Equals(v1)); +} + +TEST(VersionTest, GetVersionFromString) { + static const struct version_string { + const char* input; + size_t parts; + bool success; + } cases[] = { + {"", 0, false}, + {" ", 0, false}, + {"\t", 0, false}, + {"\n", 0, false}, + {" ", 0, false}, + {".", 0, false}, + {" . ", 0, false}, + {"0", 1, true}, + {"0.0", 2, true}, + {"65537.0", 0, false}, + {"-1.0", 0, false}, + {"1.-1.0", 0, false}, + {"+1.0", 0, false}, + {"1.+1.0", 0, false}, + {"1.0a", 0, false}, + {"1.2.3.4.5.6.7.8.9.0", 10, true}, + {"02.1", 0, false}, + {"f.1", 0, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + Version version(cases[i].input); + EXPECT_EQ(cases[i].success, version.IsValid()); + if (cases[i].success) + EXPECT_EQ(cases[i].parts, version.components().size()); + } +} + +TEST(VersionTest, Compare) { + static const struct version_compare { + const char* lhs; + const char* rhs; + int expected; + } cases[] = { + {"1.0", "1.0", 0}, + {"1.0", "0.0", 1}, + {"1.0", "2.0", -1}, + {"1.0", "1.1", -1}, + {"1.1", "1.0", 1}, + {"1.0", "1.0.1", -1}, + {"1.1", "1.0.1", 1}, + {"1.1", "1.0.1", 1}, + {"1.0.0", "1.0", 0}, + {"1.0.3", "1.0.20", -1}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + Version lhs(cases[i].lhs); + Version rhs(cases[i].rhs); + EXPECT_EQ(lhs.CompareTo(rhs), cases[i].expected) << + cases[i].lhs << " ? " << cases[i].rhs; + + EXPECT_EQ(lhs.IsOlderThan(cases[i].rhs), (cases[i].expected == -1)); + } +} + +TEST(VersionTest, CompareToWildcardString) { + static const struct version_compare { + const char* lhs; + const char* rhs; + int expected; + } cases[] = { + {"1.0", "1.*", 0}, + {"1.0", "0.*", 1}, + {"1.0", "2.*", -1}, + {"1.2.3", "1.2.3.*", 0}, + {"10.0", "1.0.*", 1}, + {"1.0", "3.0.*", -1}, + {"1.4", "1.3.0.*", 1}, + {"1.3.9", "1.3.*", 0}, + {"1.4.1", "1.3.*", 1}, + {"1.3", "1.4.5.*", -1}, + {"1.5", "1.4.5.*", 1}, + {"1.3.9", "1.3.*", 0}, + {"1.2.0.0.0.0", "1.2.*", 0}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + const Version version(cases[i].lhs); + const int result = version.CompareToWildcardString(cases[i].rhs); + EXPECT_EQ(result, cases[i].expected) << cases[i].lhs << "?" << cases[i].rhs; + } +} + +TEST(VersionTest, IsValidWildcardString) { + static const struct version_compare { + const char* version; + bool expected; + } cases[] = { + {"1.0", true}, + {"", false}, + {"1.2.3.4.5.6", true}, + {"1.2.3.*", true}, + {"1.2.3.5*", false}, + {"1.2.3.56*", false}, + {"1.*.3", false}, + {"20.*", true}, + {"+2.*", false}, + {"*", false}, + {"*.2", false}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + EXPECT_EQ(Version::IsValidWildcardString(cases[i].version), + cases[i].expected) << cases[i].version << "?" << cases[i].expected; + } +} + +} // namespace
diff --git a/src/base/vlog.cc b/src/base/vlog.cc new file mode 100644 index 0000000..332de38 --- /dev/null +++ b/src/base/vlog.cc
@@ -0,0 +1,177 @@ +// Copyright (c) 2010 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/vlog.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/string_number_conversions.h" +#include "base/string_split.h" + +namespace logging { + +const int VlogInfo::kDefaultVlogLevel = 0; + +struct VlogInfo::VmodulePattern { + enum MatchTarget { MATCH_MODULE, MATCH_FILE }; + + explicit VmodulePattern(const std::string& pattern); + + VmodulePattern(); + + std::string pattern; + int vlog_level; + MatchTarget match_target; +}; + +VlogInfo::VmodulePattern::VmodulePattern(const std::string& pattern) + : pattern(pattern), + vlog_level(VlogInfo::kDefaultVlogLevel), + match_target(MATCH_MODULE) { + // If the pattern contains a {forward,back} slash, we assume that + // it's meant to be tested against the entire __FILE__ string. + std::string::size_type first_slash = pattern.find_first_of("\\/"); + if (first_slash != std::string::npos) + match_target = MATCH_FILE; +} + +VlogInfo::VmodulePattern::VmodulePattern() + : vlog_level(VlogInfo::kDefaultVlogLevel), + match_target(MATCH_MODULE) {} + +VlogInfo::VlogInfo(const std::string& v_switch, + const std::string& vmodule_switch, + int* min_log_level) + : min_log_level_(min_log_level) { + DCHECK(min_log_level != NULL); + + typedef std::pair<std::string, std::string> KVPair; + int vlog_level = 0; + if (!v_switch.empty()) { + if (base::StringToInt(v_switch, &vlog_level)) { + SetMaxVlogLevel(vlog_level); + } else { + DLOG(WARNING) << "Could not parse v switch \"" << v_switch << "\""; + } + } + + std::vector<KVPair> kv_pairs; + if (!base::SplitStringIntoKeyValuePairs( + vmodule_switch, '=', ',', &kv_pairs)) { + DLOG(WARNING) << "Could not fully parse vmodule switch \"" + << vmodule_switch << "\""; + } + for (std::vector<KVPair>::const_iterator it = kv_pairs.begin(); + it != kv_pairs.end(); ++it) { + VmodulePattern pattern(it->first); + if (!base::StringToInt(it->second, &pattern.vlog_level)) { + DLOG(WARNING) << "Parsed vlog level for \"" + << it->first << "=" << it->second + << "\" as " << pattern.vlog_level; + } + vmodule_levels_.push_back(pattern); + } +} + +VlogInfo::~VlogInfo() {} + +namespace { + +// Given a path, returns the basename with the extension chopped off +// (and any -inl suffix). We avoid using FilePath to minimize the +// number of dependencies the logging system has. +base::StringPiece GetModule(const base::StringPiece& file) { + base::StringPiece module(file); + base::StringPiece::size_type last_slash_pos = + module.find_last_of("\\/"); + if (last_slash_pos != base::StringPiece::npos) + module.remove_prefix(last_slash_pos + 1); + base::StringPiece::size_type extension_start = module.rfind('.'); + module = module.substr(0, extension_start); + static const char kInlSuffix[] = "-inl"; + static const int kInlSuffixLen = arraysize(kInlSuffix) - 1; + if (module.ends_with(kInlSuffix)) + module.remove_suffix(kInlSuffixLen); + return module; +} + +} // namespace + +int VlogInfo::GetVlogLevel(const base::StringPiece& file) const { + if (!vmodule_levels_.empty()) { + base::StringPiece module(GetModule(file)); + for (std::vector<VmodulePattern>::const_iterator it = + vmodule_levels_.begin(); it != vmodule_levels_.end(); ++it) { + base::StringPiece target( + (it->match_target == VmodulePattern::MATCH_FILE) ? file : module); + if (MatchVlogPattern(target, it->pattern)) + return it->vlog_level; + } + } + return GetMaxVlogLevel(); +} + +void VlogInfo::SetMaxVlogLevel(int level) { + // Log severity is the negative verbosity. + *min_log_level_ = -level; +} + +int VlogInfo::GetMaxVlogLevel() const { + return -*min_log_level_; +} + +bool MatchVlogPattern(const base::StringPiece& string, + const base::StringPiece& vlog_pattern) { + base::StringPiece p(vlog_pattern); + base::StringPiece s(string); + // Consume characters until the next star. + while (!p.empty() && !s.empty() && (p[0] != '*')) { + switch (p[0]) { + // A slash (forward or back) must match a slash (forward or back). + case '/': + case '\\': + if ((s[0] != '/') && (s[0] != '\\')) + return false; + break; + + // A '?' matches anything. + case '?': + break; + + // Anything else must match literally. + default: + if (p[0] != s[0]) + return false; + break; + } + p.remove_prefix(1), s.remove_prefix(1); + } + + // An empty pattern here matches only an empty string. + if (p.empty()) + return s.empty(); + + // Coalesce runs of consecutive stars. There should be at least + // one. + while (!p.empty() && (p[0] == '*')) + p.remove_prefix(1); + + // Since we moved past the stars, an empty pattern here matches + // anything. + if (p.empty()) + return true; + + // Since we moved past the stars and p is non-empty, if some + // non-empty substring of s matches p, then we ourselves match. + while (!s.empty()) { + if (MatchVlogPattern(s, p)) + return true; + s.remove_prefix(1); + } + + // Otherwise, we couldn't find a match. + return false; +} + +} // namespace
diff --git a/src/base/vlog.h b/src/base/vlog.h new file mode 100644 index 0000000..987730f --- /dev/null +++ b/src/base/vlog.h
@@ -0,0 +1,78 @@ +// 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. + +#ifndef BASE_VLOG_H_ +#define BASE_VLOG_H_ + +#include <cstddef> +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/string_piece.h" + +namespace logging { + +// A helper class containing all the settings for vlogging. +class BASE_EXPORT VlogInfo { + public: + static const int kDefaultVlogLevel; + + // |v_switch| gives the default maximal active V-logging level; 0 is + // the default. Normally positive values are used for V-logging + // levels. + // + // |vmodule_switch| gives the per-module maximal V-logging levels to + // override the value given by |v_switch|. + // E.g. "my_module=2,foo*=3" would change the logging level for all + // code in source files "my_module.*" and "foo*.*" ("-inl" suffixes + // are also disregarded for this matching). + // + // |log_severity| points to an int that stores the log level. If a valid + // |v_switch| is provided, it will set the log level, and the default + // vlog severity will be read from there.. + // + // Any pattern containing a forward or backward slash will be tested + // against the whole pathname and not just the module. E.g., + // "*/foo/bar/*=2" would change the logging level for all code in + // source files under a "foo/bar" directory. + VlogInfo(const std::string& v_switch, + const std::string& vmodule_switch, + int* min_log_level); + ~VlogInfo(); + + // Returns the vlog level for a given file (usually taken from + // __FILE__). + int GetVlogLevel(const base::StringPiece& file) const; + + private: + void SetMaxVlogLevel(int level); + int GetMaxVlogLevel() const; + + // VmodulePattern holds all the information for each pattern parsed + // from |vmodule_switch|. + struct VmodulePattern; + std::vector<VmodulePattern> vmodule_levels_; + int* min_log_level_; + + DISALLOW_COPY_AND_ASSIGN(VlogInfo); +}; + +// Returns true if the string passed in matches the vlog pattern. The +// vlog pattern string can contain wildcards like * and ?. ? matches +// exactly one character while * matches 0 or more characters. Also, +// as a special case, a / or \ character matches either / or \. +// +// Examples: +// "kh?n" matches "khan" but not "khn" or "khaan" +// "kh*n" matches "khn", "khan", or even "khaaaaan" +// "/foo\bar" matches "/foo/bar", "\foo\bar", or "/foo\bar" +// (disregarding C escaping rules) +BASE_EXPORT bool MatchVlogPattern(const base::StringPiece& string, + const base::StringPiece& vlog_pattern); + +} // namespace logging + +#endif // BASE_VLOG_H_
diff --git a/src/base/vlog_unittest.cc b/src/base/vlog_unittest.cc new file mode 100644 index 0000000..ef7247f --- /dev/null +++ b/src/base/vlog_unittest.cc
@@ -0,0 +1,118 @@ +// Copyright (c) 2010 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/vlog.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace logging { + +namespace { + +TEST(VlogTest, NoVmodule) { + int min_log_level = 0; + EXPECT_EQ(0, VlogInfo("", "", &min_log_level).GetVlogLevel("test1")); + EXPECT_EQ(0, VlogInfo("0", "", &min_log_level).GetVlogLevel("test2")); + EXPECT_EQ(0, VlogInfo("blah", "", &min_log_level).GetVlogLevel("test3")); + EXPECT_EQ(0, VlogInfo("0blah1", "", &min_log_level).GetVlogLevel("test4")); + EXPECT_EQ(1, VlogInfo("1", "", &min_log_level).GetVlogLevel("test5")); + EXPECT_EQ(5, VlogInfo("5", "", &min_log_level).GetVlogLevel("test6")); +} + +TEST(VlogTest, MatchVlogPattern) { + // Degenerate cases. + EXPECT_TRUE(MatchVlogPattern("", "")); + EXPECT_TRUE(MatchVlogPattern("", "****")); + EXPECT_FALSE(MatchVlogPattern("", "x")); + EXPECT_FALSE(MatchVlogPattern("x", "")); + + // Basic. + EXPECT_TRUE(MatchVlogPattern("blah", "blah")); + + // ? should match exactly one character. + EXPECT_TRUE(MatchVlogPattern("blah", "bl?h")); + EXPECT_FALSE(MatchVlogPattern("blh", "bl?h")); + EXPECT_FALSE(MatchVlogPattern("blaah", "bl?h")); + EXPECT_TRUE(MatchVlogPattern("blah", "?lah")); + EXPECT_FALSE(MatchVlogPattern("lah", "?lah")); + EXPECT_FALSE(MatchVlogPattern("bblah", "?lah")); + + // * can match any number (even 0) of characters. + EXPECT_TRUE(MatchVlogPattern("blah", "bl*h")); + EXPECT_TRUE(MatchVlogPattern("blabcdefh", "bl*h")); + EXPECT_TRUE(MatchVlogPattern("blh", "bl*h")); + EXPECT_TRUE(MatchVlogPattern("blah", "*blah")); + EXPECT_TRUE(MatchVlogPattern("ohblah", "*blah")); + EXPECT_TRUE(MatchVlogPattern("blah", "blah*")); + EXPECT_TRUE(MatchVlogPattern("blahhhh", "blah*")); + EXPECT_TRUE(MatchVlogPattern("blahhhh", "blah*")); + EXPECT_TRUE(MatchVlogPattern("blah", "*blah*")); + EXPECT_TRUE(MatchVlogPattern("blahhhh", "*blah*")); + EXPECT_TRUE(MatchVlogPattern("bbbblahhhh", "*blah*")); + + // Multiple *s should work fine. + EXPECT_TRUE(MatchVlogPattern("ballaah", "b*la*h")); + EXPECT_TRUE(MatchVlogPattern("blah", "b*la*h")); + EXPECT_TRUE(MatchVlogPattern("bbbblah", "b*la*h")); + EXPECT_TRUE(MatchVlogPattern("blaaah", "b*la*h")); + + // There should be no escaping going on. + EXPECT_TRUE(MatchVlogPattern("bl\\ah", "bl\\?h")); + EXPECT_FALSE(MatchVlogPattern("bl?h", "bl\\?h")); + EXPECT_TRUE(MatchVlogPattern("bl\\aaaah", "bl\\*h")); + EXPECT_FALSE(MatchVlogPattern("bl*h", "bl\\*h")); + + // Any slash matches any slash. + EXPECT_TRUE(MatchVlogPattern("/b\\lah", "/b\\lah")); + EXPECT_TRUE(MatchVlogPattern("\\b/lah", "/b\\lah")); +} + +TEST(VlogTest, VmoduleBasic) { + const char kVSwitch[] = "-1"; + const char kVModuleSwitch[] = + "foo=,bar=0,baz=blah,,qux=0blah1,quux=1,corge.ext=5"; + int min_log_level = 0; + VlogInfo vlog_info(kVSwitch, kVModuleSwitch, &min_log_level); + EXPECT_EQ(-1, vlog_info.GetVlogLevel("/path/to/grault.cc")); + EXPECT_EQ(0, vlog_info.GetVlogLevel("/path/to/foo.cc")); + EXPECT_EQ(0, vlog_info.GetVlogLevel("D:\\Path\\To\\bar-inl.mm")); + EXPECT_EQ(-1, vlog_info.GetVlogLevel("D:\\path\\to what/bar_unittest.m")); + EXPECT_EQ(0, vlog_info.GetVlogLevel("baz.h")); + EXPECT_EQ(0, vlog_info.GetVlogLevel("/another/path/to/qux.h")); + EXPECT_EQ(1, vlog_info.GetVlogLevel("/path/to/quux")); + EXPECT_EQ(5, vlog_info.GetVlogLevel("c:\\path/to/corge.ext.h")); +} + +TEST(VlogTest, VmoduleDirs) { + const char kVModuleSwitch[] = + "foo/bar.cc=1,baz\\*\\qux.cc=2,*quux/*=3,*/*-inl.h=4"; + int min_log_level = 0; + VlogInfo vlog_info("", kVModuleSwitch, &min_log_level); + EXPECT_EQ(0, vlog_info.GetVlogLevel("/foo/bar.cc")); + EXPECT_EQ(0, vlog_info.GetVlogLevel("bar.cc")); + EXPECT_EQ(1, vlog_info.GetVlogLevel("foo/bar.cc")); + + EXPECT_EQ(0, vlog_info.GetVlogLevel("baz/grault/qux.h")); + EXPECT_EQ(0, vlog_info.GetVlogLevel("/baz/grault/qux.cc")); + EXPECT_EQ(2, vlog_info.GetVlogLevel("baz/grault/qux.cc")); + EXPECT_EQ(2, vlog_info.GetVlogLevel("baz/grault/blah/qux.cc")); + EXPECT_EQ(2, vlog_info.GetVlogLevel("baz\\grault\\qux.cc")); + EXPECT_EQ(2, vlog_info.GetVlogLevel("baz\\grault//blah\\qux.cc")); + + EXPECT_EQ(0, vlog_info.GetVlogLevel("/foo/bar/baz/quux.cc")); + EXPECT_EQ(3, vlog_info.GetVlogLevel("/foo/bar/baz/quux/grault.cc")); + EXPECT_EQ(3, vlog_info.GetVlogLevel("/foo\\bar/baz\\quux/grault.cc")); + + EXPECT_EQ(0, vlog_info.GetVlogLevel("foo/bar/test-inl.cc")); + EXPECT_EQ(4, vlog_info.GetVlogLevel("foo/bar/test-inl.h")); + EXPECT_EQ(4, vlog_info.GetVlogLevel("foo/bar/baz/blah-inl.h")); +} + +} // namespace + +} // namespace logging
diff --git a/src/base/win/dllmain.cc b/src/base/win/dllmain.cc new file mode 100644 index 0000000..15437e0 --- /dev/null +++ b/src/base/win/dllmain.cc
@@ -0,0 +1,122 @@ +// 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. + +// Windows doesn't support pthread_key_create's destr_function, and in fact +// it's a bit tricky to get code to run when a thread exits. This is +// cargo-cult magic from http://www.codeproject.com/threads/tls.asp. +// We are trying to be compatible with both a LoadLibrary style invocation, as +// well as static linking. This code only needs to be included if we use +// LoadLibrary, but it hooks into the "standard" set of TLS callbacks that are +// provided for static linking. + +// This code is deliberately written to match the style of calls seen in +// base/threading/thread_local_storage_win.cc. Please keep the two in sync if +// coding conventions are changed. + +// WARNING: Do *NOT* try to include this in the construction of the base +// library, even though it potentially drives code in +// base/threading/thread_local_storage_win.cc. If you do, some users will end +// up getting duplicate definition of DllMain() in some of their later links. + +// Force a reference to _tls_used to make the linker create the TLS directory +// if it's not already there (that is, even if __declspec(thread) is not used). +// Force a reference to p_thread_callback_dllmain_typical_entry to prevent whole +// program optimization from discarding the variables. + +#include <windows.h> + +#include "base/compiler_specific.h" +#include "base/win/win_util.h" + +// Indicate if another service is scanning the callbacks. When this becomes +// set to true, then DllMain() will stop supporting the callback service. This +// value is set to true the first time any of our callbacks are called, as that +// shows that some other service is handling callbacks. +static bool linker_notifications_are_active = false; + +// This will be our mostly no-op callback that we'll list. We won't +// deliberately call it, and if it is called, that means we don't need to do any +// of the callbacks anymore. We expect such a call to arrive via a +// THREAD_ATTACH message, long before we'd have to perform our THREAD_DETACH +// callbacks. +static void NTAPI on_callback(PVOID h, DWORD reason, PVOID reserved); + +#ifdef _WIN64 + +#pragma comment(linker, "/INCLUDE:_tls_used") +#pragma comment(linker, "/INCLUDE:p_thread_callback_dllmain_typical_entry") + +#else // _WIN64 + +#pragma comment(linker, "/INCLUDE:__tls_used") +#pragma comment(linker, "/INCLUDE:_p_thread_callback_dllmain_typical_entry") + +#endif // _WIN64 + +// Explicitly depend on tlssup.cc variable to bracket the list of TLS callbacks. +extern "C" PIMAGE_TLS_CALLBACK __xl_a; +extern "C" PIMAGE_TLS_CALLBACK __xl_z; + +// extern "C" suppresses C++ name mangling so we know the symbol names for the +// linker /INCLUDE:symbol pragmas above. +extern "C" { +#ifdef _WIN64 + +// .CRT section is merged with .rdata on x64 so it must be constant data. +#pragma data_seg(push, old_seg) +// Use a typical possible name in the .CRT$XL? list of segments. +#pragma const_seg(".CRT$XLB") +// When defining a const variable, it must have external linkage to be sure the +// linker doesn't discard it. +extern const PIMAGE_TLS_CALLBACK p_thread_callback_dllmain_typical_entry; +const PIMAGE_TLS_CALLBACK p_thread_callback_dllmain_typical_entry = on_callback; +#pragma data_seg(pop, old_seg) + +#else // _WIN64 + +#pragma data_seg(push, old_seg) +// Use a typical possible name in the .CRT$XL? list of segments. +#pragma data_seg(".CRT$XLB") +PIMAGE_TLS_CALLBACK p_thread_callback_dllmain_typical_entry = on_callback; +#pragma data_seg(pop, old_seg) + +#endif // _WIN64 +} // extern "C" + +NOINLINE static void CrashOnProcessDetach() { + *((int*)0) = 0x356; +} + +// Make DllMain call the listed callbacks. This way any third parties that are +// linked in will also be called. +BOOL WINAPI DllMain(PVOID h, DWORD reason, PVOID reserved) { + if (DLL_PROCESS_DETACH == reason && base::win::ShouldCrashOnProcessDetach()) + CrashOnProcessDetach(); + + if (DLL_THREAD_DETACH != reason && DLL_PROCESS_DETACH != reason) + return true; // We won't service THREAD_ATTACH calls. + + if (linker_notifications_are_active) + return true; // Some other service is doing this work. + + for (PIMAGE_TLS_CALLBACK* it = &__xl_a; it < &__xl_z; ++it) { + if (*it == NULL || *it == on_callback) + continue; // Don't bother to call our own callback. + (*it)(h, reason, reserved); + } + return true; +} + +static void NTAPI on_callback(PVOID h, DWORD reason, PVOID reserved) { + // Do nothing. We were just a place holder in the list used to test that we + // call all items. + // If we are called, it means that some other system is scanning the callbacks + // and we don't need to do so in DllMain(). + linker_notifications_are_active = true; + // Note: If some other routine some how plays this same game... we could both + // decide not to do the scanning <sigh>, but this trick should suppress + // duplicate calls on Vista, where the runtime takes care of the callbacks, + // and allow us to do the callbacks on XP, where we are currently devoid of + // callbacks (due to an explicit LoadLibrary call). +}
diff --git a/src/base/win/enum_variant.cc b/src/base/win/enum_variant.cc new file mode 100644 index 0000000..2975560 --- /dev/null +++ b/src/base/win/enum_variant.cc
@@ -0,0 +1,83 @@ +// 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/win/enum_variant.h" + +#include <algorithm> + +#include "base/logging.h" + +namespace base { +namespace win { + +EnumVariant::EnumVariant(unsigned long count) + : items_(new VARIANT[count]), + count_(count), + current_index_(0) { +} + +EnumVariant::~EnumVariant() { +} + +VARIANT* EnumVariant::ItemAt(unsigned long index) { + DCHECK(index < count_); + return &items_[index]; +} + +ULONG STDMETHODCALLTYPE EnumVariant::AddRef() { + return IUnknownImpl::AddRef(); +} + +ULONG STDMETHODCALLTYPE EnumVariant::Release() { + return IUnknownImpl::Release(); +} + +STDMETHODIMP EnumVariant::QueryInterface(REFIID riid, void** ppv) { + if (riid == IID_IEnumVARIANT) { + *ppv = static_cast<IEnumVARIANT*>(this); + AddRef(); + return S_OK; + } + + return IUnknownImpl::QueryInterface(riid, ppv); +} + +STDMETHODIMP EnumVariant::Next(ULONG requested_count, + VARIANT* out_elements, + ULONG* out_elements_received) { + unsigned long count = std::min(requested_count, count_ - current_index_); + for (unsigned long i = 0; i < count; ++i) + out_elements[i] = items_[current_index_ + i]; + current_index_ += count; + *out_elements_received = count; + + return (count == requested_count ? S_OK : S_FALSE); +} + +STDMETHODIMP EnumVariant::Skip(ULONG skip_count) { + unsigned long count = skip_count; + if (current_index_ + count > count_) + count = count_ - current_index_; + + current_index_ += count; + return (count == skip_count ? S_OK : S_FALSE); +} + +STDMETHODIMP EnumVariant::Reset() { + current_index_ = 0; + return S_OK; +} + +STDMETHODIMP EnumVariant::Clone(IEnumVARIANT** out_cloned_object) { + EnumVariant* other = new EnumVariant(count_); + if (count_ > 0) + memcpy(other->ItemAt(0), &items_[0], count_ * sizeof(VARIANT)); + other->Skip(current_index_); + other->AddRef(); + *out_cloned_object = other; + return S_OK; +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/enum_variant.h b/src/base/win/enum_variant.h new file mode 100644 index 0000000..7cee91d --- /dev/null +++ b/src/base/win/enum_variant.h
@@ -0,0 +1,52 @@ +// 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. + +#ifndef BASE_WIN_ENUM_VARIANT_H_ +#define BASE_WIN_ENUM_VARIANT_H_ + +#include <unknwn.h> + +#include "base/memory/scoped_ptr.h" +#include "base/win/iunknown_impl.h" + +namespace base { +namespace win { + +// A simple implementation of IEnumVARIANT. +class BASE_EXPORT EnumVariant + : public IEnumVARIANT, + public IUnknownImpl { + public: + // The constructor allocates an array of size |count|. Then use + // ItemAt to set the value of each item in the array to initialize it. + explicit EnumVariant(unsigned long count); + + // Returns a mutable pointer to the item at position |index|. + VARIANT* ItemAt(unsigned long index); + + // IUnknown. + ULONG STDMETHODCALLTYPE AddRef() OVERRIDE; + ULONG STDMETHODCALLTYPE Release() OVERRIDE; + STDMETHODIMP QueryInterface(REFIID riid, void** ppv) OVERRIDE; + + // IEnumVARIANT. + STDMETHODIMP Next(ULONG requested_count, + VARIANT* out_elements, + ULONG* out_elements_received); + STDMETHODIMP Skip(ULONG skip_count); + STDMETHODIMP Reset(); + STDMETHODIMP Clone(IEnumVARIANT** out_cloned_object); + + private: + ~EnumVariant(); + + scoped_array<VARIANT> items_; + unsigned long count_; + unsigned long current_index_; +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_ENUM_VARIANT_H_
diff --git a/src/base/win/enum_variant_unittest.cc b/src/base/win/enum_variant_unittest.cc new file mode 100644 index 0000000..99645a2 --- /dev/null +++ b/src/base/win/enum_variant_unittest.cc
@@ -0,0 +1,118 @@ +// 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/win/enum_variant.h" + +#include "base/win/scoped_com_initializer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +TEST(EnumVariantTest, EmptyEnumVariant) { + ScopedCOMInitializer com_initializer; + + EnumVariant* ev = new EnumVariant(0); + ev->AddRef(); + + IUnknown* iunknown; + EXPECT_TRUE(SUCCEEDED( + ev->QueryInterface(IID_IUnknown, reinterpret_cast<void**>(&iunknown)))); + iunknown->Release(); + + IEnumVARIANT* ienumvariant; + EXPECT_TRUE(SUCCEEDED( + ev->QueryInterface(IID_IEnumVARIANT, + reinterpret_cast<void**>(&ienumvariant)))); + EXPECT_EQ(ev, ienumvariant); + ienumvariant->Release(); + + VARIANT out_element; + ULONG out_received = 0; + EXPECT_EQ(S_FALSE, ev->Next(1, &out_element, &out_received)); + EXPECT_EQ(0, out_received); + + EXPECT_EQ(S_FALSE, ev->Skip(1)); + + EXPECT_EQ(S_OK, ev->Reset()); + + IEnumVARIANT* ev2 = NULL; + EXPECT_EQ(S_OK, ev->Clone(&ev2)); + + EXPECT_NE(static_cast<IEnumVARIANT*>(NULL), ev2); + EXPECT_NE(ev, ev2); + EXPECT_EQ(S_FALSE, ev2->Skip(1)); + EXPECT_EQ(S_OK, ev2->Reset()); + + ULONG ev2_finalrefcount = ev2->Release(); + EXPECT_EQ(0, ev2_finalrefcount); + + ULONG ev_finalrefcount = ev->Release(); + EXPECT_EQ(0, ev_finalrefcount); +} + +TEST(EnumVariantTest, SimpleEnumVariant) { + ScopedCOMInitializer com_initializer; + + EnumVariant* ev = new EnumVariant(3); + ev->AddRef(); + ev->ItemAt(0)->vt = VT_I4; + ev->ItemAt(0)->lVal = 10; + ev->ItemAt(1)->vt = VT_I4; + ev->ItemAt(1)->lVal = 20; + ev->ItemAt(2)->vt = VT_I4; + ev->ItemAt(2)->lVal = 30; + + // Get elements one at a time. + VARIANT out_element; + ULONG out_received = 0; + EXPECT_EQ(S_OK, ev->Next(1, &out_element, &out_received)); + EXPECT_EQ(1, out_received); + EXPECT_EQ(VT_I4, out_element.vt); + EXPECT_EQ(10, out_element.lVal); + EXPECT_EQ(S_OK, ev->Skip(1)); + EXPECT_EQ(S_OK, ev->Next(1, &out_element, &out_received)); + EXPECT_EQ(1, out_received); + EXPECT_EQ(VT_I4, out_element.vt); + EXPECT_EQ(30, out_element.lVal); + EXPECT_EQ(S_FALSE, ev->Next(1, &out_element, &out_received)); + + // Reset and get all elements at once. + VARIANT out_elements[3]; + EXPECT_EQ(S_OK, ev->Reset()); + EXPECT_EQ(S_OK, ev->Next(3, out_elements, &out_received)); + EXPECT_EQ(3, out_received); + EXPECT_EQ(VT_I4, out_elements[0].vt); + EXPECT_EQ(10, out_elements[0].lVal); + EXPECT_EQ(VT_I4, out_elements[1].vt); + EXPECT_EQ(20, out_elements[1].lVal); + EXPECT_EQ(VT_I4, out_elements[2].vt); + EXPECT_EQ(30, out_elements[2].lVal); + EXPECT_EQ(S_FALSE, ev->Next(1, &out_element, &out_received)); + + // Clone it. + IEnumVARIANT* ev2 = NULL; + EXPECT_EQ(S_OK, ev->Clone(&ev2)); + EXPECT_TRUE(ev2 != NULL); + EXPECT_EQ(S_FALSE, ev->Next(1, &out_element, &out_received)); + EXPECT_EQ(S_OK, ev2->Reset()); + EXPECT_EQ(S_OK, ev2->Next(3, out_elements, &out_received)); + EXPECT_EQ(3, out_received); + EXPECT_EQ(VT_I4, out_elements[0].vt); + EXPECT_EQ(10, out_elements[0].lVal); + EXPECT_EQ(VT_I4, out_elements[1].vt); + EXPECT_EQ(20, out_elements[1].lVal); + EXPECT_EQ(VT_I4, out_elements[2].vt); + EXPECT_EQ(30, out_elements[2].lVal); + EXPECT_EQ(S_FALSE, ev2->Next(1, &out_element, &out_received)); + + ULONG ev2_finalrefcount = ev2->Release(); + EXPECT_EQ(0, ev2_finalrefcount); + + ULONG ev_finalrefcount = ev->Release(); + EXPECT_EQ(0, ev_finalrefcount); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/event_trace_consumer.h b/src/base/win/event_trace_consumer.h new file mode 100644 index 0000000..c1b42b4 --- /dev/null +++ b/src/base/win/event_trace_consumer.h
@@ -0,0 +1,148 @@ +// 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. +// +// Declaration of a Windows event trace consumer base class. +#ifndef BASE_WIN_EVENT_TRACE_CONSUMER_H_ +#define BASE_WIN_EVENT_TRACE_CONSUMER_H_ + +#include <windows.h> +#include <wmistr.h> +#include <evntrace.h> +#include <vector> +#include "base/basictypes.h" + +namespace base { +namespace win { + +// This class is a base class that makes it easier to consume events +// from realtime or file sessions. Concrete consumers need to sublass +// a specialization of this class and override the ProcessEvent and/or +// the ProcessBuffer methods to implement the event consumption logic. +// Usage might look like: +// class MyConsumer: public EtwTraceConsumerBase<MyConsumer, 1> { +// protected: +// static VOID WINAPI ProcessEvent(PEVENT_TRACE event); +// }; +// +// MyConsumer consumer; +// consumer.OpenFileSession(file_path); +// consumer.Consume(); +template <class ImplClass> +class EtwTraceConsumerBase { + public: + // Constructs a closed consumer. + EtwTraceConsumerBase() { + } + + ~EtwTraceConsumerBase() { + Close(); + } + + // Opens the named realtime session, which must be existent. + // Note: You can use OpenRealtimeSession or OpenFileSession + // to open as many as MAXIMUM_WAIT_OBJECTS (63) sessions at + // any one time, though only one of them may be a realtime + // session. + HRESULT OpenRealtimeSession(const wchar_t* session_name); + + // Opens the event trace log in "file_name", which must be a full or + // relative path to an existing event trace log file. + // Note: You can use OpenRealtimeSession or OpenFileSession + // to open as many as kNumSessions at any one time. + HRESULT OpenFileSession(const wchar_t* file_name); + + // Consume all open sessions from beginning to end. + HRESULT Consume(); + + // Close all open sessions. + HRESULT Close(); + + protected: + // Override in subclasses to handle events. + static void ProcessEvent(EVENT_TRACE* event) { + } + // Override in subclasses to handle buffers. + static bool ProcessBuffer(EVENT_TRACE_LOGFILE* buffer) { + return true; // keep going + } + + protected: + // Currently open sessions. + std::vector<TRACEHANDLE> trace_handles_; + + private: + // These delegate to ImplClass callbacks with saner signatures. + static void WINAPI ProcessEventCallback(EVENT_TRACE* event) { + ImplClass::ProcessEvent(event); + } + static ULONG WINAPI ProcessBufferCallback(PEVENT_TRACE_LOGFILE buffer) { + return ImplClass::ProcessBuffer(buffer); + } + + DISALLOW_COPY_AND_ASSIGN(EtwTraceConsumerBase); +}; + +template <class ImplClass> inline +HRESULT EtwTraceConsumerBase<ImplClass>::OpenRealtimeSession( + const wchar_t* session_name) { + EVENT_TRACE_LOGFILE logfile = {}; + logfile.LoggerName = const_cast<wchar_t*>(session_name); + logfile.LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + logfile.BufferCallback = &ProcessBufferCallback; + logfile.EventCallback = &ProcessEventCallback; + logfile.Context = this; + TRACEHANDLE trace_handle = ::OpenTrace(&logfile); + if (reinterpret_cast<TRACEHANDLE>(INVALID_HANDLE_VALUE) == trace_handle) + return HRESULT_FROM_WIN32(::GetLastError()); + + trace_handles_.push_back(trace_handle); + return S_OK; +} + +template <class ImplClass> inline +HRESULT EtwTraceConsumerBase<ImplClass>::OpenFileSession( + const wchar_t* file_name) { + EVENT_TRACE_LOGFILE logfile = {}; + logfile.LogFileName = const_cast<wchar_t*>(file_name); + logfile.BufferCallback = &ProcessBufferCallback; + logfile.EventCallback = &ProcessEventCallback; + logfile.Context = this; + TRACEHANDLE trace_handle = ::OpenTrace(&logfile); + if (reinterpret_cast<TRACEHANDLE>(INVALID_HANDLE_VALUE) == trace_handle) + return HRESULT_FROM_WIN32(::GetLastError()); + + trace_handles_.push_back(trace_handle); + return S_OK; +} + +template <class ImplClass> inline +HRESULT EtwTraceConsumerBase<ImplClass>::Consume() { + ULONG err = ::ProcessTrace(&trace_handles_[0], + trace_handles_.size(), + NULL, + NULL); + return HRESULT_FROM_WIN32(err); +} + +template <class ImplClass> inline +HRESULT EtwTraceConsumerBase<ImplClass>::Close() { + HRESULT hr = S_OK; + for (size_t i = 0; i < trace_handles_.size(); ++i) { + if (NULL != trace_handles_[i]) { + ULONG ret = ::CloseTrace(trace_handles_[i]); + trace_handles_[i] = NULL; + + if (FAILED(HRESULT_FROM_WIN32(ret))) + hr = HRESULT_FROM_WIN32(ret); + } + } + trace_handles_.clear(); + + return hr; +} + +} // namespace win +} // namespace base + +#endif // BASE_WIN_EVENT_TRACE_CONSUMER_H_
diff --git a/src/base/win/event_trace_consumer_unittest.cc b/src/base/win/event_trace_consumer_unittest.cc new file mode 100644 index 0000000..7af9452 --- /dev/null +++ b/src/base/win/event_trace_consumer_unittest.cc
@@ -0,0 +1,383 @@ +// 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. +// +// Unit tests for event trace consumer base class. +#include "base/win/event_trace_consumer.h" + +#include <list> + +#include <objbase.h> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/process.h" +#include "base/stringprintf.h" +#include "base/win/event_trace_controller.h" +#include "base/win/event_trace_provider.h" +#include "base/win/scoped_handle.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include <initguid.h> // NOLINT - has to be last + +namespace { + +using base::win::EtwMofEvent; +using base::win::EtwTraceController; +using base::win::EtwTraceConsumerBase; +using base::win::EtwTraceProperties; +using base::win::EtwTraceProvider; + +typedef std::list<EVENT_TRACE> EventQueue; + +class TestConsumer: public EtwTraceConsumerBase<TestConsumer> { + public: + TestConsumer() { + sank_event_.Set(::CreateEvent(NULL, TRUE, FALSE, NULL)); + ClearQueue(); + } + + ~TestConsumer() { + ClearQueue(); + sank_event_.Close(); + } + + void ClearQueue() { + EventQueue::const_iterator it(events_.begin()), end(events_.end()); + + for (; it != end; ++it) { + delete [] it->MofData; + } + + events_.clear(); + } + + static void EnqueueEvent(EVENT_TRACE* event) { + events_.push_back(*event); + EVENT_TRACE& back = events_.back(); + + if (NULL != event->MofData && 0 != event->MofLength) { + back.MofData = new char[event->MofLength]; + memcpy(back.MofData, event->MofData, event->MofLength); + } + } + + static void ProcessEvent(EVENT_TRACE* event) { + EnqueueEvent(event); + ::SetEvent(sank_event_.Get()); + } + + static base::win::ScopedHandle sank_event_; + static EventQueue events_; + + private: + DISALLOW_COPY_AND_ASSIGN(TestConsumer); +}; + +base::win::ScopedHandle TestConsumer::sank_event_; +EventQueue TestConsumer::events_; + +class EtwTraceConsumerBaseTest: public testing::Test { + public: + EtwTraceConsumerBaseTest() + : session_name_(base::StringPrintf(L"TestSession-%d", + base::Process::Current().pid())) { + } + + virtual void SetUp() { + // Cleanup any potentially dangling sessions. + EtwTraceProperties ignore; + EtwTraceController::Stop(session_name_.c_str(), &ignore); + + // Allocate a new GUID for each provider test. + ASSERT_HRESULT_SUCCEEDED(::CoCreateGuid(&test_provider_)); + } + + virtual void TearDown() { + // Cleanup any potentially danging sessions. + EtwTraceProperties ignore; + EtwTraceController::Stop(session_name_.c_str(), &ignore); + } + + protected: + GUID test_provider_; + std::wstring session_name_; +}; + +} // namespace + +TEST_F(EtwTraceConsumerBaseTest, Initialize) { + TestConsumer consumer_; +} + +TEST_F(EtwTraceConsumerBaseTest, OpenRealtimeSucceedsWhenNoSession) { + TestConsumer consumer_; + + ASSERT_HRESULT_SUCCEEDED( + consumer_.OpenRealtimeSession(session_name_.c_str())); +} + +TEST_F(EtwTraceConsumerBaseTest, ConsumerImmediateFailureWhenNoSession) { + TestConsumer consumer_; + + ASSERT_HRESULT_SUCCEEDED( + consumer_.OpenRealtimeSession(session_name_.c_str())); + ASSERT_HRESULT_FAILED(consumer_.Consume()); +} + +namespace { + +class EtwTraceConsumerRealtimeTest: public EtwTraceConsumerBaseTest { + public: + virtual void SetUp() { + EtwTraceConsumerBaseTest::SetUp(); + + ASSERT_HRESULT_SUCCEEDED( + consumer_.OpenRealtimeSession(session_name_.c_str())); + } + + virtual void TearDown() { + consumer_.Close(); + + EtwTraceConsumerBaseTest::TearDown(); + } + + DWORD ConsumerThread() { + ::SetEvent(consumer_ready_.Get()); + + HRESULT hr = consumer_.Consume(); + return hr; + } + + static DWORD WINAPI ConsumerThreadMainProc(void* arg) { + return reinterpret_cast<EtwTraceConsumerRealtimeTest*>(arg)-> + ConsumerThread(); + } + + HRESULT StartConsumerThread() { + consumer_ready_.Set(::CreateEvent(NULL, TRUE, FALSE, NULL)); + EXPECT_TRUE(consumer_ready_ != NULL); + consumer_thread_.Set(::CreateThread(NULL, 0, ConsumerThreadMainProc, + this, 0, NULL)); + if (NULL == consumer_thread_.Get()) + return HRESULT_FROM_WIN32(::GetLastError()); + + HRESULT hr = S_OK; + HANDLE events[] = { consumer_ready_, consumer_thread_ }; + DWORD result = ::WaitForMultipleObjects(arraysize(events), events, + FALSE, INFINITE); + switch (result) { + case WAIT_OBJECT_0: + // The event was set, the consumer_ is ready. + return S_OK; + case WAIT_OBJECT_0 + 1: { + // The thread finished. This may race with the event, so check + // explicitly for the event here, before concluding there's trouble. + if (WAIT_OBJECT_0 == ::WaitForSingleObject(consumer_ready_, 0)) + return S_OK; + DWORD exit_code = 0; + if (::GetExitCodeThread(consumer_thread_, &exit_code)) + return exit_code; + else + return HRESULT_FROM_WIN32(::GetLastError()); + break; + } + default: + return E_UNEXPECTED; + break; + } + + return hr; + } + + // Waits for consumer_ thread to exit, and returns its exit code. + HRESULT JoinConsumerThread() { + if (WAIT_OBJECT_0 != ::WaitForSingleObject(consumer_thread_, INFINITE)) + return HRESULT_FROM_WIN32(::GetLastError()); + + DWORD exit_code = 0; + if (::GetExitCodeThread(consumer_thread_, &exit_code)) + return exit_code; + + return HRESULT_FROM_WIN32(::GetLastError()); + } + + TestConsumer consumer_; + base::win::ScopedHandle consumer_ready_; + base::win::ScopedHandle consumer_thread_; +}; + +} // namespace + +TEST_F(EtwTraceConsumerRealtimeTest, ConsumerReturnsWhenSessionClosed) { + EtwTraceController controller; + + HRESULT hr = controller.StartRealtimeSession(session_name_.c_str(), + 100 * 1024); + if (hr == E_ACCESSDENIED) { + VLOG(1) << "You must be an administrator to run this test on Vista"; + return; + } + + // Start the consumer_. + ASSERT_HRESULT_SUCCEEDED(StartConsumerThread()); + + // Wait around for the consumer_ thread a bit. + ASSERT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(consumer_thread_, 50)); + + ASSERT_HRESULT_SUCCEEDED(controller.Stop(NULL)); + + // The consumer_ returns success on session stop. + ASSERT_HRESULT_SUCCEEDED(JoinConsumerThread()); +} + +namespace { + +// {57E47923-A549-476f-86CA-503D57F59E62} +DEFINE_GUID(kTestEventType, + 0x57e47923, 0xa549, 0x476f, 0x86, 0xca, 0x50, 0x3d, 0x57, 0xf5, 0x9e, 0x62); + +} // namespace + +TEST_F(EtwTraceConsumerRealtimeTest, ConsumeEvent) { + EtwTraceController controller; + HRESULT hr = controller.StartRealtimeSession(session_name_.c_str(), + 100 * 1024); + if (hr == E_ACCESSDENIED) { + VLOG(1) << "You must be an administrator to run this test on Vista"; + return; + } + + ASSERT_HRESULT_SUCCEEDED(controller.EnableProvider(test_provider_, + TRACE_LEVEL_VERBOSE, 0xFFFFFFFF)); + + EtwTraceProvider provider(test_provider_); + ASSERT_EQ(ERROR_SUCCESS, provider.Register()); + + // Start the consumer_. + ASSERT_HRESULT_SUCCEEDED(StartConsumerThread()); + + ASSERT_EQ(0, TestConsumer::events_.size()); + + EtwMofEvent<1> event(kTestEventType, 1, TRACE_LEVEL_ERROR); + EXPECT_EQ(ERROR_SUCCESS, provider.Log(&event.header)); + + EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(TestConsumer::sank_event_, + INFINITE)); + ASSERT_HRESULT_SUCCEEDED(controller.Stop(NULL)); + ASSERT_HRESULT_SUCCEEDED(JoinConsumerThread()); + ASSERT_NE(0u, TestConsumer::events_.size()); +} + +namespace { + +// We run events through a file session to assert that +// the content comes through. +class EtwTraceConsumerDataTest: public EtwTraceConsumerBaseTest { + public: + EtwTraceConsumerDataTest() { + } + + virtual void SetUp() { + EtwTraceConsumerBaseTest::SetUp(); + + EtwTraceProperties prop; + EtwTraceController::Stop(session_name_.c_str(), &prop); + + // Create a temp dir for this test. + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + // Construct a temp file name in our dir. + temp_file_ = temp_dir_.path().Append(L"test.etl"); + } + + virtual void TearDown() { + EXPECT_TRUE(file_util::Delete(temp_file_, false)); + + EtwTraceConsumerBaseTest::TearDown(); + } + + HRESULT LogEventToTempSession(PEVENT_TRACE_HEADER header) { + EtwTraceController controller; + + // Set up a file session. + HRESULT hr = controller.StartFileSession(session_name_.c_str(), + temp_file_.value().c_str()); + if (FAILED(hr)) + return hr; + + // Enable our provider. + EXPECT_HRESULT_SUCCEEDED(controller.EnableProvider(test_provider_, + TRACE_LEVEL_VERBOSE, 0xFFFFFFFF)); + + EtwTraceProvider provider(test_provider_); + // Then register our provider, means we get a session handle immediately. + EXPECT_EQ(ERROR_SUCCESS, provider.Register()); + // Trace the event, it goes to the temp file. + EXPECT_EQ(ERROR_SUCCESS, provider.Log(header)); + EXPECT_HRESULT_SUCCEEDED(controller.DisableProvider(test_provider_)); + EXPECT_HRESULT_SUCCEEDED(provider.Unregister()); + EXPECT_HRESULT_SUCCEEDED(controller.Flush(NULL)); + EXPECT_HRESULT_SUCCEEDED(controller.Stop(NULL)); + + return S_OK; + } + + HRESULT ConsumeEventFromTempSession() { + // Now consume the event(s). + TestConsumer consumer_; + HRESULT hr = consumer_.OpenFileSession(temp_file_.value().c_str()); + if (SUCCEEDED(hr)) + hr = consumer_.Consume(); + consumer_.Close(); + // And nab the result. + events_.swap(TestConsumer::events_); + return hr; + } + + HRESULT RoundTripEvent(PEVENT_TRACE_HEADER header, PEVENT_TRACE* trace) { + file_util::Delete(temp_file_, false); + + HRESULT hr = LogEventToTempSession(header); + if (SUCCEEDED(hr)) + hr = ConsumeEventFromTempSession(); + + if (FAILED(hr)) + return hr; + + // We should now have the event in the queue. + if (events_.empty()) + return E_FAIL; + + *trace = &events_.back(); + return S_OK; + } + + EventQueue events_; + base::ScopedTempDir temp_dir_; + FilePath temp_file_; +}; + +} // namespace + + +TEST_F(EtwTraceConsumerDataTest, RoundTrip) { + EtwMofEvent<1> event(kTestEventType, 1, TRACE_LEVEL_ERROR); + + static const char kData[] = "This is but test data"; + event.fields[0].DataPtr = reinterpret_cast<ULONG64>(kData); + event.fields[0].Length = sizeof(kData); + + PEVENT_TRACE trace = NULL; + HRESULT hr = RoundTripEvent(&event.header, &trace); + if (hr == E_ACCESSDENIED) { + VLOG(1) << "You must be an administrator to run this test on Vista"; + return; + } + ASSERT_HRESULT_SUCCEEDED(hr) << "RoundTripEvent failed"; + ASSERT_TRUE(NULL != trace); + ASSERT_EQ(sizeof(kData), trace->MofLength); + ASSERT_STREQ(kData, reinterpret_cast<const char*>(trace->MofData)); +}
diff --git a/src/base/win/event_trace_controller.cc b/src/base/win/event_trace_controller.cc new file mode 100644 index 0000000..0391fbc --- /dev/null +++ b/src/base/win/event_trace_controller.cc
@@ -0,0 +1,173 @@ +// Copyright (c) 2009 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. +// +// Implementation of a Windows event trace controller class. +#include "base/win/event_trace_controller.h" +#include "base/logging.h" + +namespace base { +namespace win { + +EtwTraceProperties::EtwTraceProperties() { + memset(buffer_, 0, sizeof(buffer_)); + EVENT_TRACE_PROPERTIES* prop = get(); + + prop->Wnode.BufferSize = sizeof(buffer_); + prop->Wnode.Flags = WNODE_FLAG_TRACED_GUID; + prop->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); + prop->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + + sizeof(wchar_t) * kMaxStringLen; +} + +HRESULT EtwTraceProperties::SetLoggerName(const wchar_t* logger_name) { + size_t len = wcslen(logger_name) + 1; + if (kMaxStringLen < len) + return E_INVALIDARG; + + memcpy(buffer_ + get()->LoggerNameOffset, + logger_name, + sizeof(wchar_t) * len); + return S_OK; +} + +HRESULT EtwTraceProperties::SetLoggerFileName(const wchar_t* logger_file_name) { + size_t len = wcslen(logger_file_name) + 1; + if (kMaxStringLen < len) + return E_INVALIDARG; + + memcpy(buffer_ + get()->LogFileNameOffset, + logger_file_name, + sizeof(wchar_t) * len); + return S_OK; +} + +EtwTraceController::EtwTraceController() : session_(NULL) { +} + +EtwTraceController::~EtwTraceController() { + Stop(NULL); +} + +HRESULT EtwTraceController::Start(const wchar_t* session_name, + EtwTraceProperties* prop) { + DCHECK(NULL == session_ && session_name_.empty()); + EtwTraceProperties ignore; + if (prop == NULL) + prop = &ignore; + + HRESULT hr = Start(session_name, prop, &session_); + if (SUCCEEDED(hr)) + session_name_ = session_name; + + return hr; +} + +HRESULT EtwTraceController::StartFileSession(const wchar_t* session_name, + const wchar_t* logfile_path, bool realtime) { + DCHECK(NULL == session_ && session_name_.empty()); + + EtwTraceProperties prop; + prop.SetLoggerFileName(logfile_path); + EVENT_TRACE_PROPERTIES& p = *prop.get(); + p.Wnode.ClientContext = 1; // QPC timer accuracy. + p.LogFileMode = EVENT_TRACE_FILE_MODE_SEQUENTIAL; // Sequential log. + if (realtime) + p.LogFileMode |= EVENT_TRACE_REAL_TIME_MODE; + + p.MaximumFileSize = 100; // 100M file size. + p.FlushTimer = 30; // 30 seconds flush lag. + return Start(session_name, &prop); +} + +HRESULT EtwTraceController::StartRealtimeSession(const wchar_t* session_name, + size_t buffer_size) { + DCHECK(NULL == session_ && session_name_.empty()); + EtwTraceProperties prop; + EVENT_TRACE_PROPERTIES& p = *prop.get(); + p.LogFileMode = EVENT_TRACE_REAL_TIME_MODE | EVENT_TRACE_USE_PAGED_MEMORY; + p.FlushTimer = 1; // flush every second. + p.BufferSize = 16; // 16 K buffers. + p.LogFileNameOffset = 0; + return Start(session_name, &prop); +} + +HRESULT EtwTraceController::EnableProvider(REFGUID provider, UCHAR level, + ULONG flags) { + ULONG error = ::EnableTrace(TRUE, flags, level, &provider, session_); + return HRESULT_FROM_WIN32(error); +} + +HRESULT EtwTraceController::DisableProvider(REFGUID provider) { + ULONG error = ::EnableTrace(FALSE, 0, 0, &provider, session_); + return HRESULT_FROM_WIN32(error); +} + +HRESULT EtwTraceController::Stop(EtwTraceProperties* properties) { + EtwTraceProperties ignore; + if (properties == NULL) + properties = &ignore; + + ULONG error = ::ControlTrace(session_, NULL, properties->get(), + EVENT_TRACE_CONTROL_STOP); + if (ERROR_SUCCESS != error) + return HRESULT_FROM_WIN32(error); + + session_ = NULL; + session_name_.clear(); + return S_OK; +} + +HRESULT EtwTraceController::Flush(EtwTraceProperties* properties) { + EtwTraceProperties ignore; + if (properties == NULL) + properties = &ignore; + + ULONG error = ::ControlTrace(session_, NULL, properties->get(), + EVENT_TRACE_CONTROL_FLUSH); + if (ERROR_SUCCESS != error) + return HRESULT_FROM_WIN32(error); + + return S_OK; +} + +HRESULT EtwTraceController::Start(const wchar_t* session_name, + EtwTraceProperties* properties, TRACEHANDLE* session_handle) { + DCHECK(properties != NULL); + ULONG err = ::StartTrace(session_handle, session_name, properties->get()); + return HRESULT_FROM_WIN32(err); +} + +HRESULT EtwTraceController::Query(const wchar_t* session_name, + EtwTraceProperties* properties) { + ULONG err = ::ControlTrace(NULL, session_name, properties->get(), + EVENT_TRACE_CONTROL_QUERY); + return HRESULT_FROM_WIN32(err); +}; + +HRESULT EtwTraceController::Update(const wchar_t* session_name, + EtwTraceProperties* properties) { + DCHECK(properties != NULL); + ULONG err = ::ControlTrace(NULL, session_name, properties->get(), + EVENT_TRACE_CONTROL_UPDATE); + return HRESULT_FROM_WIN32(err); +} + +HRESULT EtwTraceController::Stop(const wchar_t* session_name, + EtwTraceProperties* properties) { + DCHECK(properties != NULL); + ULONG err = ::ControlTrace(NULL, session_name, properties->get(), + EVENT_TRACE_CONTROL_STOP); + return HRESULT_FROM_WIN32(err); +} + +HRESULT EtwTraceController::Flush(const wchar_t* session_name, + EtwTraceProperties* properties) { + DCHECK(properties != NULL); + ULONG err = ::ControlTrace(NULL, session_name, properties->get(), + EVENT_TRACE_CONTROL_FLUSH); + return HRESULT_FROM_WIN32(err); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/event_trace_controller.h b/src/base/win/event_trace_controller.h new file mode 100644 index 0000000..69e755b --- /dev/null +++ b/src/base/win/event_trace_controller.h
@@ -0,0 +1,151 @@ +// 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. +// +// Declaration of a Windows event trace controller class. +// The controller takes care of creating and manipulating event trace +// sessions. +// +// Event tracing for Windows is a system-provided service that provides +// logging control and high-performance transport for generic, binary trace +// events. Event trace providers register with the system by their name, +// which is a GUID, and can from that point forward receive callbacks that +// start or end tracing and that change their trace level and enable mask. +// +// A trace controller can create an event tracing session, which either +// sends events to a binary file, or to a realtime consumer, or both. +// +// A trace consumer consumes events from zero or one realtime session, +// as well as potentially from multiple binary trace files. +#ifndef BASE_WIN_EVENT_TRACE_CONTROLLER_H_ +#define BASE_WIN_EVENT_TRACE_CONTROLLER_H_ + +#include <windows.h> +#include <wmistr.h> +#include <evntrace.h> +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace win { + +// Utility class to make it easier to work with EVENT_TRACE_PROPERTIES. +// The EVENT_TRACE_PROPERTIES structure contains information about an +// event tracing session. +class BASE_EXPORT EtwTraceProperties { + public: + EtwTraceProperties(); + + EVENT_TRACE_PROPERTIES* get() { + return &properties_; + } + + const EVENT_TRACE_PROPERTIES* get() const { + return reinterpret_cast<const EVENT_TRACE_PROPERTIES*>(&properties_); + } + + const wchar_t* GetLoggerName() const { + return reinterpret_cast<const wchar_t *>(buffer_ + get()->LoggerNameOffset); + } + + // Copies logger_name to the properties structure. + HRESULT SetLoggerName(const wchar_t* logger_name); + const wchar_t* GetLoggerFileName() const { + return reinterpret_cast<const wchar_t*>(buffer_ + get()->LogFileNameOffset); + } + + // Copies logger_file_name to the properties structure. + HRESULT SetLoggerFileName(const wchar_t* logger_file_name); + + // Max string len for name and session name is 1024 per documentation. + static const size_t kMaxStringLen = 1024; + // Properties buffer allocates space for header and for + // max length for name and session name. + static const size_t kBufSize = sizeof(EVENT_TRACE_PROPERTIES) + + 2 * sizeof(wchar_t) * (kMaxStringLen); + + private: + // The EVENT_TRACE_PROPERTIES structure needs to be overlaid on a + // larger buffer to allow storing the logger name and logger file + // name contiguously with the structure. + union { + public: + // Our properties header. + EVENT_TRACE_PROPERTIES properties_; + // The actual size of the buffer is forced by this member. + char buffer_[kBufSize]; + }; + + DISALLOW_COPY_AND_ASSIGN(EtwTraceProperties); +}; + +// This class implements an ETW controller, which knows how to start and +// stop event tracing sessions, as well as controlling ETW provider +// log levels and enable bit masks under the session. +class BASE_EXPORT EtwTraceController { + public: + EtwTraceController(); + ~EtwTraceController(); + + // Start a session with given name and properties. + HRESULT Start(const wchar_t* session_name, EtwTraceProperties* prop); + + // Starts a session tracing to a file with some default properties. + HRESULT StartFileSession(const wchar_t* session_name, + const wchar_t* logfile_path, + bool realtime = false); + + // Starts a realtime session with some default properties. + HRESULT StartRealtimeSession(const wchar_t* session_name, + size_t buffer_size); + + // Enables "provider" at "level" for this session. + // This will cause all providers registered with the GUID + // "provider" to start tracing at the new level, systemwide. + HRESULT EnableProvider(const GUID& provider, UCHAR level, + ULONG flags = 0xFFFFFFFF); + // Disables "provider". + HRESULT DisableProvider(const GUID& provider); + + // Stops our session and retrieve the new properties of the session, + // properties may be NULL. + HRESULT Stop(EtwTraceProperties* properties); + + // Flushes our session and retrieve the current properties, + // properties may be NULL. + HRESULT Flush(EtwTraceProperties* properties); + + // Static utility functions for controlling + // sessions we don't necessarily own. + static HRESULT Start(const wchar_t* session_name, + EtwTraceProperties* properties, + TRACEHANDLE* session_handle); + + static HRESULT Query(const wchar_t* session_name, + EtwTraceProperties* properties); + + static HRESULT Update(const wchar_t* session_name, + EtwTraceProperties* properties); + + static HRESULT Stop(const wchar_t* session_name, + EtwTraceProperties* properties); + static HRESULT Flush(const wchar_t* session_name, + EtwTraceProperties* properties); + + // Accessors. + TRACEHANDLE session() const { return session_; } + const wchar_t* session_name() const { return session_name_.c_str(); } + + private: + std::wstring session_name_; + TRACEHANDLE session_; + + DISALLOW_COPY_AND_ASSIGN(EtwTraceController); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_EVENT_TRACE_CONTROLLER_H_
diff --git a/src/base/win/event_trace_controller_unittest.cc b/src/base/win/event_trace_controller_unittest.cc new file mode 100644 index 0000000..fae606e --- /dev/null +++ b/src/base/win/event_trace_controller_unittest.cc
@@ -0,0 +1,236 @@ +// 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. +// +// Unit tests for event trace controller. + +#include <objbase.h> +#include <initguid.h> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/process.h" +#include "base/stringprintf.h" +#include "base/sys_info.h" +#include "base/win/event_trace_controller.h" +#include "base/win/event_trace_provider.h" +#include "base/win/scoped_handle.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using base::win::EtwTraceController; +using base::win::EtwTraceProvider; +using base::win::EtwTraceProperties; + +DEFINE_GUID(kGuidNull, + 0x0000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0); + +const ULONG kTestProviderFlags = 0xCAFEBABE; + +class TestingProvider: public EtwTraceProvider { + public: + explicit TestingProvider(const GUID& provider_name) + : EtwTraceProvider(provider_name) { + callback_event_.Set(::CreateEvent(NULL, TRUE, FALSE, NULL)); + } + + void WaitForCallback() { + ::WaitForSingleObject(callback_event_.Get(), INFINITE); + ::ResetEvent(callback_event_.Get()); + } + + private: + virtual void OnEventsEnabled() { + ::SetEvent(callback_event_.Get()); + } + virtual void PostEventsDisabled() { + ::SetEvent(callback_event_.Get()); + } + + base::win::ScopedHandle callback_event_; + + DISALLOW_COPY_AND_ASSIGN(TestingProvider); +}; + +} // namespace + +TEST(EtwTracePropertiesTest, Initialization) { + EtwTraceProperties prop; + + EVENT_TRACE_PROPERTIES* p = prop.get(); + EXPECT_NE(0u, p->Wnode.BufferSize); + EXPECT_EQ(0u, p->Wnode.ProviderId); + EXPECT_EQ(0u, p->Wnode.HistoricalContext); + + EXPECT_TRUE(kGuidNull == p->Wnode.Guid); + EXPECT_EQ(0, p->Wnode.ClientContext); + EXPECT_EQ(WNODE_FLAG_TRACED_GUID, p->Wnode.Flags); + + EXPECT_EQ(0, p->BufferSize); + EXPECT_EQ(0, p->MinimumBuffers); + EXPECT_EQ(0, p->MaximumBuffers); + EXPECT_EQ(0, p->MaximumFileSize); + EXPECT_EQ(0, p->LogFileMode); + EXPECT_EQ(0, p->FlushTimer); + EXPECT_EQ(0, p->EnableFlags); + EXPECT_EQ(0, p->AgeLimit); + + EXPECT_EQ(0, p->NumberOfBuffers); + EXPECT_EQ(0, p->FreeBuffers); + EXPECT_EQ(0, p->EventsLost); + EXPECT_EQ(0, p->BuffersWritten); + EXPECT_EQ(0, p->LogBuffersLost); + EXPECT_EQ(0, p->RealTimeBuffersLost); + EXPECT_EQ(0, p->LoggerThreadId); + EXPECT_NE(0u, p->LogFileNameOffset); + EXPECT_NE(0u, p->LoggerNameOffset); +} + +TEST(EtwTracePropertiesTest, Strings) { + EtwTraceProperties prop; + + ASSERT_STREQ(L"", prop.GetLoggerFileName()); + ASSERT_STREQ(L"", prop.GetLoggerName()); + + std::wstring name(1023, L'A'); + ASSERT_HRESULT_SUCCEEDED(prop.SetLoggerFileName(name.c_str())); + ASSERT_HRESULT_SUCCEEDED(prop.SetLoggerName(name.c_str())); + ASSERT_STREQ(name.c_str(), prop.GetLoggerFileName()); + ASSERT_STREQ(name.c_str(), prop.GetLoggerName()); + + std::wstring name2(1024, L'A'); + ASSERT_HRESULT_FAILED(prop.SetLoggerFileName(name2.c_str())); + ASSERT_HRESULT_FAILED(prop.SetLoggerName(name2.c_str())); +} + +namespace { + +class EtwTraceControllerTest : public testing::Test { + public: + EtwTraceControllerTest() : session_name_( + base::StringPrintf(L"TestSession-%d", base::Process::Current().pid())) { + } + + virtual void SetUp() { + EtwTraceProperties ignore; + EtwTraceController::Stop(session_name_.c_str(), &ignore); + + // Allocate a new provider name GUID for each test. + ASSERT_HRESULT_SUCCEEDED(::CoCreateGuid(&test_provider_)); + } + + virtual void TearDown() { + EtwTraceProperties prop; + EtwTraceController::Stop(session_name_.c_str(), &prop); + } + + protected: + GUID test_provider_; + std::wstring session_name_; +}; + +} // namespace + +TEST_F(EtwTraceControllerTest, Initialize) { + EtwTraceController controller; + + EXPECT_EQ(NULL, controller.session()); + EXPECT_STREQ(L"", controller.session_name()); +} + + +TEST_F(EtwTraceControllerTest, StartRealTimeSession) { + EtwTraceController controller; + + HRESULT hr = controller.StartRealtimeSession(session_name_.c_str(), + 100 * 1024); + if (hr == E_ACCESSDENIED) { + VLOG(1) << "You must be an administrator to run this test on Vista"; + return; + } + + EXPECT_TRUE(NULL != controller.session()); + EXPECT_STREQ(session_name_.c_str(), controller.session_name()); + + EXPECT_HRESULT_SUCCEEDED(controller.Stop(NULL)); + EXPECT_EQ(NULL, controller.session()); + EXPECT_STREQ(L"", controller.session_name()); +} + +TEST_F(EtwTraceControllerTest, StartFileSession) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath temp; + ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir.path(), &temp)); + + EtwTraceController controller; + HRESULT hr = controller.StartFileSession(session_name_.c_str(), + temp.value().c_str()); + if (hr == E_ACCESSDENIED) { + VLOG(1) << "You must be an administrator to run this test on Vista"; + file_util::Delete(temp, false); + return; + } + + EXPECT_TRUE(NULL != controller.session()); + EXPECT_STREQ(session_name_.c_str(), controller.session_name()); + + EXPECT_HRESULT_SUCCEEDED(controller.Stop(NULL)); + EXPECT_EQ(NULL, controller.session()); + EXPECT_STREQ(L"", controller.session_name()); + file_util::Delete(temp, false); +} + +TEST_F(EtwTraceControllerTest, EnableDisable) { + TestingProvider provider(test_provider_); + + EXPECT_EQ(ERROR_SUCCESS, provider.Register()); + EXPECT_EQ(NULL, provider.session_handle()); + + EtwTraceController controller; + HRESULT hr = controller.StartRealtimeSession(session_name_.c_str(), + 100 * 1024); + if (hr == E_ACCESSDENIED) { + VLOG(1) << "You must be an administrator to run this test on Vista"; + return; + } + + EXPECT_HRESULT_SUCCEEDED(controller.EnableProvider(test_provider_, + TRACE_LEVEL_VERBOSE, kTestProviderFlags)); + + provider.WaitForCallback(); + + EXPECT_EQ(TRACE_LEVEL_VERBOSE, provider.enable_level()); + EXPECT_EQ(kTestProviderFlags, provider.enable_flags()); + + EXPECT_HRESULT_SUCCEEDED(controller.DisableProvider(test_provider_)); + + provider.WaitForCallback(); + + EXPECT_EQ(0, provider.enable_level()); + EXPECT_EQ(0, provider.enable_flags()); + + EXPECT_EQ(ERROR_SUCCESS, provider.Unregister()); + + // Enable the provider again, before registering. + EXPECT_HRESULT_SUCCEEDED(controller.EnableProvider(test_provider_, + TRACE_LEVEL_VERBOSE, kTestProviderFlags)); + + // Register the provider again, the settings above + // should take immediate effect. + EXPECT_EQ(ERROR_SUCCESS, provider.Register()); + + EXPECT_EQ(TRACE_LEVEL_VERBOSE, provider.enable_level()); + EXPECT_EQ(kTestProviderFlags, provider.enable_flags()); + + EXPECT_HRESULT_SUCCEEDED(controller.Stop(NULL)); + + provider.WaitForCallback(); + + // Session should have wound down. + EXPECT_EQ(0, provider.enable_level()); + EXPECT_EQ(0, provider.enable_flags()); +}
diff --git a/src/base/win/event_trace_provider.cc b/src/base/win/event_trace_provider.cc new file mode 100644 index 0000000..8fcf67d --- /dev/null +++ b/src/base/win/event_trace_provider.cc
@@ -0,0 +1,134 @@ +// 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/event_trace_provider.h" +#include <windows.h> +#include <cguid.h> + +namespace base { +namespace win { + +TRACE_GUID_REGISTRATION EtwTraceProvider::obligatory_guid_registration_ = { + &GUID_NULL, + NULL +}; + +EtwTraceProvider::EtwTraceProvider(const GUID& provider_name) + : provider_name_(provider_name), registration_handle_(NULL), + session_handle_(NULL), enable_flags_(0), enable_level_(0) { +} + +EtwTraceProvider::EtwTraceProvider() + : provider_name_(GUID_NULL), registration_handle_(NULL), + session_handle_(NULL), enable_flags_(0), enable_level_(0) { +} + +EtwTraceProvider::~EtwTraceProvider() { + Unregister(); +} + +ULONG EtwTraceProvider::EnableEvents(void* buffer) { + session_handle_ = ::GetTraceLoggerHandle(buffer); + if (NULL == session_handle_) { + return ::GetLastError(); + } + + enable_flags_ = ::GetTraceEnableFlags(session_handle_); + enable_level_ = ::GetTraceEnableLevel(session_handle_); + + // Give subclasses a chance to digest the state change. + OnEventsEnabled(); + + return ERROR_SUCCESS; +} + +ULONG EtwTraceProvider::DisableEvents() { + // Give subclasses a chance to digest the state change. + OnEventsDisabled(); + + enable_level_ = 0; + enable_flags_ = 0; + session_handle_ = NULL; + + PostEventsDisabled(); + + return ERROR_SUCCESS; +} + +ULONG EtwTraceProvider::Callback(WMIDPREQUESTCODE request, void* buffer) { + switch (request) { + case WMI_ENABLE_EVENTS: + return EnableEvents(buffer); + case WMI_DISABLE_EVENTS: + return DisableEvents(); + default: + return ERROR_INVALID_PARAMETER; + } + // Not reached. +} + +ULONG WINAPI EtwTraceProvider::ControlCallback(WMIDPREQUESTCODE request, + void* context, ULONG *reserved, void* buffer) { + EtwTraceProvider *provider = reinterpret_cast<EtwTraceProvider*>(context); + + return provider->Callback(request, buffer); +} + +ULONG EtwTraceProvider::Register() { + if (provider_name_ == GUID_NULL) + return ERROR_INVALID_NAME; + + return ::RegisterTraceGuids(ControlCallback, this, &provider_name_, + 1, &obligatory_guid_registration_, NULL, NULL, ®istration_handle_); +} + +ULONG EtwTraceProvider::Unregister() { + // If a session is active, notify subclasses that it's going away. + if (session_handle_ != NULL) + DisableEvents(); + + ULONG ret = ::UnregisterTraceGuids(registration_handle_); + + registration_handle_ = NULL; + + return ret; +} + +ULONG EtwTraceProvider::Log(const EtwEventClass& event_class, + EtwEventType type, EtwEventLevel level, const char *message) { + if (NULL == session_handle_ || enable_level_ < level) + return ERROR_SUCCESS; // No one listening. + + EtwMofEvent<1> event(event_class, type, level); + + event.fields[0].DataPtr = reinterpret_cast<ULONG64>(message); + event.fields[0].Length = message ? + static_cast<ULONG>(sizeof(message[0]) * (1 + strlen(message))) : 0; + + return ::TraceEvent(session_handle_, &event.header); +} + +ULONG EtwTraceProvider::Log(const EtwEventClass& event_class, + EtwEventType type, EtwEventLevel level, const wchar_t *message) { + if (NULL == session_handle_ || enable_level_ < level) + return ERROR_SUCCESS; // No one listening. + + EtwMofEvent<1> event(event_class, type, level); + + event.fields[0].DataPtr = reinterpret_cast<ULONG64>(message); + event.fields[0].Length = message ? + static_cast<ULONG>(sizeof(message[0]) * (1 + wcslen(message))) : 0; + + return ::TraceEvent(session_handle_, &event.header); +} + +ULONG EtwTraceProvider::Log(EVENT_TRACE_HEADER* event) { + if (enable_level_ < event->Class.Level) + return ERROR_SUCCESS; + + return ::TraceEvent(session_handle_, event); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/event_trace_provider.h b/src/base/win/event_trace_provider.h new file mode 100644 index 0000000..9f6e7c4 --- /dev/null +++ b/src/base/win/event_trace_provider.h
@@ -0,0 +1,175 @@ +// 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. +// +// Declaration of a Windows event trace provider class, to allow using +// Windows Event Tracing for logging transport and control. +#ifndef BASE_WIN_EVENT_TRACE_PROVIDER_H_ +#define BASE_WIN_EVENT_TRACE_PROVIDER_H_ + +#include <windows.h> +#include <wmistr.h> +#include <evntrace.h> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace win { + +typedef GUID EtwEventClass; +typedef UCHAR EtwEventType; +typedef UCHAR EtwEventLevel; +typedef USHORT EtwEventVersion; +typedef ULONG EtwEventFlags; + +// Base class is a POD for correctness. +template <size_t N> struct EtwMofEventBase { + EVENT_TRACE_HEADER header; + MOF_FIELD fields[N]; +}; + +// Utility class to auto-initialize event trace header structures. +template <size_t N> class EtwMofEvent: public EtwMofEventBase<N> { + public: + typedef EtwMofEventBase<N> Super; + + EtwMofEvent() { + memset(static_cast<Super*>(this), 0, sizeof(Super)); + } + + EtwMofEvent(const EtwEventClass& event_class, EtwEventType type, + EtwEventLevel level) { + memset(static_cast<Super*>(this), 0, sizeof(Super)); + header.Size = sizeof(Super); + header.Guid = event_class; + header.Class.Type = type; + header.Class.Level = level; + header.Flags = WNODE_FLAG_TRACED_GUID | WNODE_FLAG_USE_MOF_PTR; + } + + EtwMofEvent(const EtwEventClass& event_class, EtwEventType type, + EtwEventVersion version, EtwEventLevel level) { + memset(static_cast<Super*>(this), 0, sizeof(Super)); + header.Size = sizeof(Super); + header.Guid = event_class; + header.Class.Type = type; + header.Class.Version = version; + header.Class.Level = level; + header.Flags = WNODE_FLAG_TRACED_GUID | WNODE_FLAG_USE_MOF_PTR; + } + + void SetField(int field, size_t size, const void *data) { + // DCHECK(field < N); + if ((field < N) && (size <= kuint32max)) { + fields[field].DataPtr = reinterpret_cast<ULONG64>(data); + fields[field].Length = static_cast<ULONG>(size); + } + } + + EVENT_TRACE_HEADER* get() { return& header; } + + private: + DISALLOW_COPY_AND_ASSIGN(EtwMofEvent); +}; + +// Trace provider with Event Tracing for Windows. The trace provider +// registers with ETW by its name which is a GUID. ETW calls back to +// the object whenever the trace level or enable flags for this provider +// name changes. +// Users of this class can test whether logging is currently enabled at +// a particular trace level, and whether particular enable flags are set, +// before other resources are consumed to generate and issue the log +// messages themselves. +class BASE_EXPORT EtwTraceProvider { + public: + // Creates an event trace provider identified by provider_name, which + // will be the name registered with Event Tracing for Windows (ETW). + explicit EtwTraceProvider(const GUID& provider_name); + + // Creates an unnamed event trace provider, the provider must be given + // a name before registration. + EtwTraceProvider(); + virtual ~EtwTraceProvider(); + + // Registers the trace provider with Event Tracing for Windows. + // Note: from this point forward ETW may call the provider's control + // callback. If the provider's name is enabled in some trace session + // already, the callback may occur recursively from this call, so + // call this only when you're ready to handle callbacks. + ULONG Register(); + // Unregisters the trace provider with ETW. + ULONG Unregister(); + + // Accessors. + void set_provider_name(const GUID& provider_name) { + provider_name_ = provider_name; + } + const GUID& provider_name() const { return provider_name_; } + TRACEHANDLE registration_handle() const { return registration_handle_; } + TRACEHANDLE session_handle() const { return session_handle_; } + EtwEventFlags enable_flags() const { return enable_flags_; } + EtwEventLevel enable_level() const { return enable_level_; } + + // Returns true iff logging should be performed for "level" and "flags". + // Note: flags is treated as a bitmask, and should normally have a single + // bit set, to test whether to log for a particular sub "facility". + bool ShouldLog(EtwEventLevel level, EtwEventFlags flags) { + return NULL != session_handle_ && level >= enable_level_ && + (0 != (flags & enable_flags_)); + } + + // Simple wrappers to log Unicode and ANSI strings. + // Do nothing if !ShouldLog(level, 0xFFFFFFFF). + ULONG Log(const EtwEventClass& event_class, EtwEventType type, + EtwEventLevel level, const char *message); + ULONG Log(const EtwEventClass& event_class, EtwEventType type, + EtwEventLevel level, const wchar_t *message); + + // Log the provided event. + ULONG Log(EVENT_TRACE_HEADER* event); + + protected: + // Called after events have been enabled, override in subclasses + // to set up state or log at the start of a session. + // Note: This function may be called ETW's thread and may be racy, + // bring your own locking if needed. + virtual void OnEventsEnabled() {} + + // Called just before events are disabled, override in subclasses + // to tear down state or log at the end of a session. + // Note: This function may be called ETW's thread and may be racy, + // bring your own locking if needed. + virtual void OnEventsDisabled() {} + + // Called just after events have been disabled, override in subclasses + // to tear down state at the end of a session. At this point it's + // to late to log anything to the session. + // Note: This function may be called ETW's thread and may be racy, + // bring your own locking if needed. + virtual void PostEventsDisabled() {} + + private: + ULONG EnableEvents(PVOID buffer); + ULONG DisableEvents(); + ULONG Callback(WMIDPREQUESTCODE request, PVOID buffer); + static ULONG WINAPI ControlCallback(WMIDPREQUESTCODE request, PVOID context, + ULONG *reserved, PVOID buffer); + + GUID provider_name_; + TRACEHANDLE registration_handle_; + TRACEHANDLE session_handle_; + EtwEventFlags enable_flags_; + EtwEventLevel enable_level_; + + // We don't use this, but on XP we're obliged to pass one in to + // RegisterTraceGuids. Non-const, because that's how the API needs it. + static TRACE_GUID_REGISTRATION obligatory_guid_registration_; + + DISALLOW_COPY_AND_ASSIGN(EtwTraceProvider); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_EVENT_TRACE_PROVIDER_H_
diff --git a/src/base/win/event_trace_provider_unittest.cc b/src/base/win/event_trace_provider_unittest.cc new file mode 100644 index 0000000..55b5ae6 --- /dev/null +++ b/src/base/win/event_trace_provider_unittest.cc
@@ -0,0 +1,110 @@ +// Copyright (c) 2010 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. +// +// Unit tests for event trace provider. +#include "base/win/event_trace_provider.h" +#include <new> +#include "testing/gtest/include/gtest/gtest.h" +#include <initguid.h> // NOLINT - has to be last + +namespace { + +using base::win::EtwTraceProvider; +using base::win::EtwMofEvent; + +// {7F0FD37F-FA3C-4cd6-9242-DF60967A2CB2} +DEFINE_GUID(kTestProvider, + 0x7f0fd37f, 0xfa3c, 0x4cd6, 0x92, 0x42, 0xdf, 0x60, 0x96, 0x7a, 0x2c, 0xb2); + +// {7F0FD37F-FA3C-4cd6-9242-DF60967A2CB2} +DEFINE_GUID(kTestEventClass, + 0x7f0fd37f, 0xfa3c, 0x4cd6, 0x92, 0x42, 0xdf, 0x60, 0x96, 0x7a, 0x2c, 0xb2); + +} // namespace + +TEST(EtwTraceProviderTest, ToleratesPreCreateInvocations) { + // Because the trace provider is used in logging, it's important that + // it be possible to use static provider instances without regard to + // whether they've been constructed or destructed. + // The interface of the class is designed to tolerate this usage. + char buf[sizeof(EtwTraceProvider)] = {0}; + EtwTraceProvider& provider = reinterpret_cast<EtwTraceProvider&>(buf); + + EXPECT_EQ(NULL, provider.registration_handle()); + EXPECT_EQ(NULL, provider.session_handle()); + EXPECT_EQ(0, provider.enable_flags()); + EXPECT_EQ(0, provider.enable_level()); + + EXPECT_FALSE(provider.ShouldLog(TRACE_LEVEL_FATAL, 0xfffffff)); + + // We expect these not to crash. + provider.Log(kTestEventClass, 0, TRACE_LEVEL_FATAL, "foo"); + provider.Log(kTestEventClass, 0, TRACE_LEVEL_FATAL, L"foo"); + + EtwMofEvent<1> dummy(kTestEventClass, 0, TRACE_LEVEL_FATAL); + DWORD data = 0; + dummy.SetField(0, sizeof(data), &data); + provider.Log(dummy.get()); + + // Placement-new the provider into our buffer. + new (buf) EtwTraceProvider(kTestProvider); + + // Registration is now safe. + EXPECT_EQ(ERROR_SUCCESS, provider.Register()); + + // Destruct the instance, this should unregister it. + provider.EtwTraceProvider::~EtwTraceProvider(); + + // And post-destruction, all of the above should still be safe. + EXPECT_EQ(NULL, provider.registration_handle()); + EXPECT_EQ(NULL, provider.session_handle()); + EXPECT_EQ(0, provider.enable_flags()); + EXPECT_EQ(0, provider.enable_level()); + + EXPECT_FALSE(provider.ShouldLog(TRACE_LEVEL_FATAL, 0xfffffff)); + + // We expect these not to crash. + provider.Log(kTestEventClass, 0, TRACE_LEVEL_FATAL, "foo"); + provider.Log(kTestEventClass, 0, TRACE_LEVEL_FATAL, L"foo"); + provider.Log(dummy.get()); +} + +TEST(EtwTraceProviderTest, Initialize) { + EtwTraceProvider provider(kTestProvider); + + EXPECT_EQ(NULL, provider.registration_handle()); + EXPECT_EQ(NULL, provider.session_handle()); + EXPECT_EQ(0, provider.enable_flags()); + EXPECT_EQ(0, provider.enable_level()); +} + +TEST(EtwTraceProviderTest, Register) { + EtwTraceProvider provider(kTestProvider); + + ASSERT_EQ(ERROR_SUCCESS, provider.Register()); + EXPECT_NE(NULL, provider.registration_handle()); + ASSERT_EQ(ERROR_SUCCESS, provider.Unregister()); + EXPECT_EQ(NULL, provider.registration_handle()); +} + +TEST(EtwTraceProviderTest, RegisterWithNoNameFails) { + EtwTraceProvider provider; + + EXPECT_TRUE(provider.Register() != ERROR_SUCCESS); +} + +TEST(EtwTraceProviderTest, Enable) { + EtwTraceProvider provider(kTestProvider); + + ASSERT_EQ(ERROR_SUCCESS, provider.Register()); + EXPECT_NE(NULL, provider.registration_handle()); + + // No session so far. + EXPECT_EQ(NULL, provider.session_handle()); + EXPECT_EQ(0, provider.enable_flags()); + EXPECT_EQ(0, provider.enable_level()); + + ASSERT_EQ(ERROR_SUCCESS, provider.Unregister()); + EXPECT_EQ(NULL, provider.registration_handle()); +}
diff --git a/src/base/win/i18n.cc b/src/base/win/i18n.cc new file mode 100644 index 0000000..9e523a1 --- /dev/null +++ b/src/base/win/i18n.cc
@@ -0,0 +1,169 @@ +// Copyright (c) 2010 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/i18n.h" + +#include <windows.h> + +#include "base/logging.h" + +namespace { + +// Keep this enum in sync with kLanguageFunctionNames. +enum LanguageFunction { + SYSTEM_LANGUAGES, + USER_LANGUAGES, + PROCESS_LANGUAGES, + THREAD_LANGUAGES, + NUM_FUNCTIONS +}; + +const char kSystemLanguagesFunctionName[] = "GetSystemPreferredUILanguages"; +const char kUserLanguagesFunctionName[] = "GetUserPreferredUILanguages"; +const char kProcessLanguagesFunctionName[] = "GetProcessPreferredUILanguages"; +const char kThreadLanguagesFunctionName[] = "GetThreadPreferredUILanguages"; + +// Keep this array in sync with enum LanguageFunction. +const char *const kLanguageFunctionNames[] = { + &kSystemLanguagesFunctionName[0], + &kUserLanguagesFunctionName[0], + &kProcessLanguagesFunctionName[0], + &kThreadLanguagesFunctionName[0] +}; + +COMPILE_ASSERT(NUM_FUNCTIONS == arraysize(kLanguageFunctionNames), + language_function_enum_and_names_out_of_sync); + +// Calls one of the MUI Get*PreferredUILanguages functions, placing the result +// in |languages|. |function| identifies the function to call and |flags| is +// the function-specific flags (callers must not specify MUI_LANGUAGE_ID or +// MUI_LANGUAGE_NAME). Returns true if at least one language is placed in +// |languages|. +bool GetMUIPreferredUILanguageList(LanguageFunction function, ULONG flags, + std::vector<wchar_t>* languages) { + DCHECK(0 <= function && NUM_FUNCTIONS > function); + DCHECK_EQ(0U, (flags & (MUI_LANGUAGE_ID | MUI_LANGUAGE_NAME))); + DCHECK(languages); + + HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); + if (NULL != kernel32) { + typedef BOOL (WINAPI* GetPreferredUILanguages_Fn)( + DWORD, PULONG, PZZWSTR, PULONG); + GetPreferredUILanguages_Fn get_preferred_ui_languages = + reinterpret_cast<GetPreferredUILanguages_Fn>( + GetProcAddress(kernel32, kLanguageFunctionNames[function])); + if (NULL != get_preferred_ui_languages) { + const ULONG call_flags = flags | MUI_LANGUAGE_NAME; + ULONG language_count = 0; + ULONG buffer_length = 0; + if (get_preferred_ui_languages(call_flags, &language_count, NULL, + &buffer_length) && + 0 != buffer_length) { + languages->resize(buffer_length); + if (get_preferred_ui_languages(call_flags, &language_count, + &(*languages)[0], &buffer_length) && + 0 != language_count) { + DCHECK(languages->size() == buffer_length); + return true; + } else { + DPCHECK(0 == language_count) + << "Failed getting preferred UI languages."; + } + } else { + DPCHECK(0 == buffer_length) + << "Failed getting size of preferred UI languages."; + } + } else { + DVLOG(2) << "MUI not available."; + } + } else { + NOTREACHED() << "kernel32.dll not found."; + } + + return false; +} + +bool GetUserDefaultUILanguage(std::wstring* language, std::wstring* region) { + DCHECK(language); + + LANGID lang_id = ::GetUserDefaultUILanguage(); + if (LOCALE_CUSTOM_UI_DEFAULT != lang_id) { + const LCID locale_id = MAKELCID(lang_id, SORT_DEFAULT); + // max size for LOCALE_SISO639LANGNAME and LOCALE_SISO3166CTRYNAME is 9 + wchar_t result_buffer[9]; + int result_length = + GetLocaleInfo(locale_id, LOCALE_SISO639LANGNAME, &result_buffer[0], + arraysize(result_buffer)); + DPCHECK(0 != result_length) << "Failed getting language id"; + if (1 < result_length) { + language->assign(&result_buffer[0], result_length - 1); + region->clear(); + if (SUBLANG_NEUTRAL != SUBLANGID(lang_id)) { + result_length = + GetLocaleInfo(locale_id, LOCALE_SISO3166CTRYNAME, &result_buffer[0], + arraysize(result_buffer)); + DPCHECK(0 != result_length) << "Failed getting region id"; + if (1 < result_length) + region->assign(&result_buffer[0], result_length - 1); + } + return true; + } + } else { + // This is entirely unexpected on pre-Vista, which is the only time we + // should try GetUserDefaultUILanguage anyway. + NOTREACHED() << "Cannot determine language for a supplemental locale."; + } + return false; +} + +bool GetPreferredUILanguageList(LanguageFunction function, ULONG flags, + std::vector<std::wstring>* languages) { + std::vector<wchar_t> buffer; + std::wstring language; + std::wstring region; + + if (GetMUIPreferredUILanguageList(function, flags, &buffer)) { + std::vector<wchar_t>::const_iterator scan = buffer.begin(); + language.assign(&*scan); + while (!language.empty()) { + languages->push_back(language); + scan += language.size() + 1; + language.assign(&*scan); + } + } else if (GetUserDefaultUILanguage(&language, ®ion)) { + // Mimic the MUI behavior of putting the neutral version of the lang after + // the regional one (e.g., "fr-CA, fr"). + if (!region.empty()) + languages->push_back(std::wstring(language) + .append(1, L'-') + .append(region)); + languages->push_back(language); + } else { + return false; + } + + return true; +} + +} // namespace + +namespace base { +namespace win { +namespace i18n { + +bool GetUserPreferredUILanguageList(std::vector<std::wstring>* languages) { + DCHECK(languages); + return GetPreferredUILanguageList(USER_LANGUAGES, 0, languages); +} + +bool GetThreadPreferredUILanguageList(std::vector<std::wstring>* languages) { + DCHECK(languages); + return GetPreferredUILanguageList( + THREAD_LANGUAGES, MUI_MERGE_SYSTEM_FALLBACK | MUI_MERGE_USER_FALLBACK, + languages); +} + +} // namespace i18n +} // namespace win +} // namespace base
diff --git a/src/base/win/i18n.h b/src/base/win/i18n.h new file mode 100644 index 0000000..c0379c1 --- /dev/null +++ b/src/base/win/i18n.h
@@ -0,0 +1,34 @@ +// 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. + +#ifndef BASE_WIN_I18N_H_ +#define BASE_WIN_I18N_H_ + +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace win { +namespace i18n { + +// Adds to |languages| the list of user preferred UI languages from MUI, if +// available, falling-back on the user default UI language otherwise. Returns +// true if at least one language is added. +BASE_EXPORT bool GetUserPreferredUILanguageList( + std::vector<std::wstring>* languages); + +// Adds to |languages| the list of thread, process, user, and system preferred +// UI languages from MUI, if available, falling-back on the user default UI +// language otherwise. Returns true if at least one language is added. +BASE_EXPORT bool GetThreadPreferredUILanguageList( + std::vector<std::wstring>* languages); + +} // namespace i18n +} // namespace win +} // namespace base + +#endif // BASE_WIN_I18N_H_
diff --git a/src/base/win/i18n_unittest.cc b/src/base/win/i18n_unittest.cc new file mode 100644 index 0000000..781fc39 --- /dev/null +++ b/src/base/win/i18n_unittest.cc
@@ -0,0 +1,42 @@ +// Copyright (c) 2010 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 contains unit tests for Windows internationalization funcs. + +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/win/i18n.h" +#include "base/win/windows_version.h" + +namespace base { +namespace win { +namespace i18n { + +// Tests that at least one user preferred UI language can be obtained. +TEST(I18NTest, GetUserPreferredUILanguageList) { + std::vector<std::wstring> languages; + EXPECT_TRUE(GetUserPreferredUILanguageList(&languages)); + EXPECT_NE(static_cast<std::vector<std::wstring>::size_type>(0), + languages.size()); + for (std::vector<std::wstring>::const_iterator scan = languages.begin(), + end = languages.end(); scan != end; ++scan) { + EXPECT_FALSE((*scan).empty()); + } +} + +// Tests that at least one thread preferred UI language can be obtained. +TEST(I18NTest, GetThreadPreferredUILanguageList) { + std::vector<std::wstring> languages; + EXPECT_TRUE(GetThreadPreferredUILanguageList(&languages)); + EXPECT_NE(static_cast<std::vector<std::wstring>::size_type>(0), + languages.size()); + for (std::vector<std::wstring>::const_iterator scan = languages.begin(), + end = languages.end(); scan != end; ++scan) { + EXPECT_FALSE((*scan).empty()); + } +} + +} // namespace i18n +} // namespace win +} // namespace base
diff --git a/src/base/win/iat_patch_function.cc b/src/base/win/iat_patch_function.cc new file mode 100644 index 0000000..a4a8902 --- /dev/null +++ b/src/base/win/iat_patch_function.cc
@@ -0,0 +1,278 @@ +// 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/win/iat_patch_function.h" + +#include "base/logging.h" +#include "base/win/pe_image.h" + +namespace base { +namespace win { + +namespace { + +struct InterceptFunctionInformation { + bool finished_operation; + const char* imported_from_module; + const char* function_name; + void* new_function; + void** old_function; + IMAGE_THUNK_DATA** iat_thunk; + DWORD return_code; +}; + +void* GetIATFunction(IMAGE_THUNK_DATA* iat_thunk) { + if (NULL == iat_thunk) { + NOTREACHED(); + return NULL; + } + + // Works around the 64 bit portability warning: + // The Function member inside IMAGE_THUNK_DATA is really a pointer + // to the IAT function. IMAGE_THUNK_DATA correctly maps to IMAGE_THUNK_DATA32 + // or IMAGE_THUNK_DATA64 for correct pointer size. + union FunctionThunk { + IMAGE_THUNK_DATA thunk; + void* pointer; + } iat_function; + + iat_function.thunk = *iat_thunk; + return iat_function.pointer; +} +// Change the page protection (of code pages) to writable and copy +// the data at the specified location +// +// Arguments: +// old_code Target location to copy +// new_code Source +// length Number of bytes to copy +// +// Returns: Windows error code (winerror.h). NO_ERROR if successful +DWORD ModifyCode(void* old_code, void* new_code, int length) { + if ((NULL == old_code) || (NULL == new_code) || (0 == length)) { + NOTREACHED(); + return ERROR_INVALID_PARAMETER; + } + + // Change the page protection so that we can write. + DWORD error = NO_ERROR; + DWORD old_page_protection = 0; + if (VirtualProtect(old_code, + length, + PAGE_READWRITE, + &old_page_protection)) { + + // Write the data. + CopyMemory(old_code, new_code, length); + + // Restore the old page protection. + error = ERROR_SUCCESS; + VirtualProtect(old_code, + length, + old_page_protection, + &old_page_protection); + } else { + error = GetLastError(); + NOTREACHED(); + } + + return error; +} + +bool InterceptEnumCallback(const base::win::PEImage& image, const char* module, + DWORD ordinal, const char* name, DWORD hint, + IMAGE_THUNK_DATA* iat, void* cookie) { + InterceptFunctionInformation* intercept_information = + reinterpret_cast<InterceptFunctionInformation*>(cookie); + + if (NULL == intercept_information) { + NOTREACHED(); + return false; + } + + DCHECK(module); + + if ((0 == lstrcmpiA(module, intercept_information->imported_from_module)) && + (NULL != name) && + (0 == lstrcmpiA(name, intercept_information->function_name))) { + // Save the old pointer. + if (NULL != intercept_information->old_function) { + *(intercept_information->old_function) = GetIATFunction(iat); + } + + if (NULL != intercept_information->iat_thunk) { + *(intercept_information->iat_thunk) = iat; + } + + // portability check + COMPILE_ASSERT(sizeof(iat->u1.Function) == + sizeof(intercept_information->new_function), unknown_IAT_thunk_format); + + // Patch the function. + intercept_information->return_code = + ModifyCode(&(iat->u1.Function), + &(intercept_information->new_function), + sizeof(intercept_information->new_function)); + + // Terminate further enumeration. + intercept_information->finished_operation = true; + return false; + } + + return true; +} + +// Helper to intercept a function in an import table of a specific +// module. +// +// Arguments: +// module_handle Module to be intercepted +// imported_from_module Module that exports the symbol +// function_name Name of the API to be intercepted +// new_function Interceptor function +// old_function Receives the original function pointer +// iat_thunk Receives pointer to IAT_THUNK_DATA +// for the API from the import table. +// +// Returns: Returns NO_ERROR on success or Windows error code +// as defined in winerror.h +DWORD InterceptImportedFunction(HMODULE module_handle, + const char* imported_from_module, + const char* function_name, void* new_function, + void** old_function, + IMAGE_THUNK_DATA** iat_thunk) { + if ((NULL == module_handle) || (NULL == imported_from_module) || + (NULL == function_name) || (NULL == new_function)) { + NOTREACHED(); + return ERROR_INVALID_PARAMETER; + } + + base::win::PEImage target_image(module_handle); + if (!target_image.VerifyMagic()) { + NOTREACHED(); + return ERROR_INVALID_PARAMETER; + } + + InterceptFunctionInformation intercept_information = { + false, + imported_from_module, + function_name, + new_function, + old_function, + iat_thunk, + ERROR_GEN_FAILURE}; + + // First go through the IAT. If we don't find the import we are looking + // for in IAT, search delay import table. + target_image.EnumAllImports(InterceptEnumCallback, &intercept_information); + if (!intercept_information.finished_operation) { + target_image.EnumAllDelayImports(InterceptEnumCallback, + &intercept_information); + } + + return intercept_information.return_code; +} + +// Restore intercepted IAT entry with the original function. +// +// Arguments: +// intercept_function Interceptor function +// original_function Receives the original function pointer +// +// Returns: Returns NO_ERROR on success or Windows error code +// as defined in winerror.h +DWORD RestoreImportedFunction(void* intercept_function, + void* original_function, + IMAGE_THUNK_DATA* iat_thunk) { + if ((NULL == intercept_function) || (NULL == original_function) || + (NULL == iat_thunk)) { + NOTREACHED(); + return ERROR_INVALID_PARAMETER; + } + + if (GetIATFunction(iat_thunk) != intercept_function) { + // Check if someone else has intercepted on top of us. + // We cannot unpatch in this case, just raise a red flag. + NOTREACHED(); + return ERROR_INVALID_FUNCTION; + } + + return ModifyCode(&(iat_thunk->u1.Function), + &original_function, + sizeof(original_function)); +} + +} // namespace + +IATPatchFunction::IATPatchFunction() + : module_handle_(NULL), + original_function_(NULL), + iat_thunk_(NULL), + intercept_function_(NULL) { +} + +IATPatchFunction::~IATPatchFunction() { + if (NULL != intercept_function_) { + DWORD error = Unpatch(); + DCHECK_EQ(static_cast<DWORD>(NO_ERROR), error); + } +} + +DWORD IATPatchFunction::Patch(const wchar_t* module, + const char* imported_from_module, + const char* function_name, + void* new_function) { + DCHECK_EQ(static_cast<void*>(NULL), original_function_); + DCHECK_EQ(static_cast<IMAGE_THUNK_DATA*>(NULL), iat_thunk_); + DCHECK_EQ(static_cast<void*>(NULL), intercept_function_); + + HMODULE module_handle = LoadLibraryW(module); + + if (module_handle == NULL) { + NOTREACHED(); + return GetLastError(); + } + + DWORD error = InterceptImportedFunction(module_handle, + imported_from_module, + function_name, + new_function, + &original_function_, + &iat_thunk_); + + if (NO_ERROR == error) { + DCHECK_NE(original_function_, intercept_function_); + module_handle_ = module_handle; + intercept_function_ = new_function; + } else { + FreeLibrary(module_handle); + } + + return error; +} + +DWORD IATPatchFunction::Unpatch() { + DWORD error = RestoreImportedFunction(intercept_function_, + original_function_, + iat_thunk_); + DCHECK_EQ(static_cast<DWORD>(NO_ERROR), error); + + // Hands off the intercept if we fail to unpatch. + // If IATPatchFunction::Unpatch fails during RestoreImportedFunction + // it means that we cannot safely unpatch the import address table + // patch. In this case its better to be hands off the intercept as + // trying to unpatch again in the destructor of IATPatchFunction is + // not going to be any safer + if (module_handle_) + FreeLibrary(module_handle_); + module_handle_ = NULL; + intercept_function_ = NULL; + original_function_ = NULL; + iat_thunk_ = NULL; + + return error; +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/iat_patch_function.h b/src/base/win/iat_patch_function.h new file mode 100644 index 0000000..3ae1f3c --- /dev/null +++ b/src/base/win/iat_patch_function.h
@@ -0,0 +1,72 @@ +// 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. + +#ifndef BASE_WIN_IAT_PATCH_FUNCTION_H_ +#define BASE_WIN_IAT_PATCH_FUNCTION_H_ + +#include <windows.h> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace win { + +// A class that encapsulates Import Address Table patching helpers and restores +// the original function in the destructor. +// +// It will intercept functions for a specific DLL imported from another DLL. +// This is the case when, for example, we want to intercept +// CertDuplicateCertificateContext function (exported from crypt32.dll) called +// by wininet.dll. +class BASE_EXPORT IATPatchFunction { + public: + IATPatchFunction(); + ~IATPatchFunction(); + + // Intercept a function in an import table of a specific + // module. Save the original function and the import + // table address. These values will be used later + // during Unpatch + // + // Arguments: + // module Module to be intercepted + // imported_from_module Module that exports the 'function_name' + // function_name Name of the API to be intercepted + // + // Returns: Windows error code (winerror.h). NO_ERROR if successful + // + // Note: Patching a function will make the IAT patch take some "ownership" on + // |module|. It will LoadLibrary(module) to keep the DLL alive until a call + // to Unpatch(), which will call FreeLibrary() and allow the module to be + // unloaded. The idea is to help prevent the DLL from going away while a + // patch is still active. + DWORD Patch(const wchar_t* module, + const char* imported_from_module, + const char* function_name, + void* new_function); + + // Unpatch the IAT entry using internally saved original + // function. + // + // Returns: Windows error code (winerror.h). NO_ERROR if successful + DWORD Unpatch(); + + bool is_patched() const { + return (NULL != intercept_function_); + } + + private: + HMODULE module_handle_; + void* intercept_function_; + void* original_function_; + IMAGE_THUNK_DATA* iat_thunk_; + + DISALLOW_COPY_AND_ASSIGN(IATPatchFunction); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_IAT_PATCH_FUNCTION_H_
diff --git a/src/base/win/iunknown_impl.cc b/src/base/win/iunknown_impl.cc new file mode 100644 index 0000000..9baa0f3 --- /dev/null +++ b/src/base/win/iunknown_impl.cc
@@ -0,0 +1,42 @@ +// 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/win/iunknown_impl.h" + +namespace base { +namespace win { + +IUnknownImpl::IUnknownImpl() + : ref_count_(0) { +} + +IUnknownImpl::~IUnknownImpl() { +} + +ULONG STDMETHODCALLTYPE IUnknownImpl::AddRef() { + base::AtomicRefCountInc(&ref_count_); + return 1; +} + +ULONG STDMETHODCALLTYPE IUnknownImpl::Release() { + if (!base::AtomicRefCountDec(&ref_count_)) { + delete this; + return 0; + } + return 1; +} + +STDMETHODIMP IUnknownImpl::QueryInterface(REFIID riid, void** ppv) { + if (riid == IID_IUnknown) { + *ppv = static_cast<IUnknown*>(this); + AddRef(); + return S_OK; + } + + *ppv = NULL; + return E_NOINTERFACE; +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/iunknown_impl.h b/src/base/win/iunknown_impl.h new file mode 100644 index 0000000..ff7e870 --- /dev/null +++ b/src/base/win/iunknown_impl.h
@@ -0,0 +1,38 @@ +// 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_WIN_IUNKNOWN_IMPL_H_ +#define BASE_WIN_IUNKNOWN_IMPL_H_ + +#include <unknwn.h> + +#include "base/atomic_ref_count.h" +#include "base/base_export.h" +#include "base/compiler_specific.h" + +namespace base { +namespace win { + +// IUnknown implementation for other classes to derive from. +class BASE_EXPORT IUnknownImpl : public IUnknown { + public: + IUnknownImpl(); + + virtual ULONG STDMETHODCALLTYPE AddRef() OVERRIDE; + virtual ULONG STDMETHODCALLTYPE Release() OVERRIDE; + + // Subclasses should extend this to return any interfaces they provide. + virtual STDMETHODIMP QueryInterface(REFIID riid, void** ppv) OVERRIDE; + + protected: + virtual ~IUnknownImpl(); + + private: + AtomicRefCount ref_count_; +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_IUNKNOWN_IMPL_H_
diff --git a/src/base/win/iunknown_impl_unittest.cc b/src/base/win/iunknown_impl_unittest.cc new file mode 100644 index 0000000..db86214 --- /dev/null +++ b/src/base/win/iunknown_impl_unittest.cc
@@ -0,0 +1,51 @@ +// 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/win/iunknown_impl.h" + +#include "base/win/scoped_com_initializer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +class TestIUnknownImplSubclass : public IUnknownImpl { + public: + TestIUnknownImplSubclass() { + ++instance_count; + } + virtual ~TestIUnknownImplSubclass() { + --instance_count; + } + static int instance_count; +}; + +// static +int TestIUnknownImplSubclass::instance_count = 0; + +TEST(IUnknownImplTest, IUnknownImpl) { + ScopedCOMInitializer com_initializer; + + EXPECT_EQ(0, TestIUnknownImplSubclass::instance_count); + IUnknown* u = new TestIUnknownImplSubclass(); + + EXPECT_EQ(1, TestIUnknownImplSubclass::instance_count); + + EXPECT_EQ(1, u->AddRef()); + EXPECT_EQ(1, u->AddRef()); + + IUnknown* other = NULL; + EXPECT_EQ(E_NOINTERFACE, u->QueryInterface( + IID_IDispatch, reinterpret_cast<void**>(&other))); + EXPECT_EQ(S_OK, u->QueryInterface( + IID_IUnknown, reinterpret_cast<void**>(&other))); + other->Release(); + + EXPECT_EQ(1, u->Release()); + EXPECT_EQ(0, u->Release()); + EXPECT_EQ(0, TestIUnknownImplSubclass::instance_count); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/metro.cc b/src/base/win/metro.cc new file mode 100644 index 0000000..22bc5e8 --- /dev/null +++ b/src/base/win/metro.cc
@@ -0,0 +1,189 @@ +// 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/metro.h" + +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/win/scoped_comptr.h" +#include "base/win/windows_version.h" + +namespace base { +namespace win { + +namespace { +bool g_should_tsf_aware_required = false; +} + +HMODULE GetMetroModule() { + const HMODULE kUninitialized = reinterpret_cast<HMODULE>(1); + static HMODULE metro_module = kUninitialized; + + if (metro_module == kUninitialized) { + // Initialize the cache, note that the initialization is idempotent + // under the assumption that metro_driver is never unloaded, so the + // race to this assignment is safe. + metro_module = GetModuleHandleA("metro_driver.dll"); + if (metro_module != NULL) { + // This must be a metro process if the metro_driver is loaded. + DCHECK(IsMetroProcess()); + } + } + + DCHECK(metro_module != kUninitialized); + return metro_module; +} + +bool IsMetroProcess() { + enum ImmersiveState { + kImmersiveUnknown, + kImmersiveTrue, + kImmersiveFalse + }; + // The immersive state of a process can never change. + // Look it up once and cache it here. + static ImmersiveState state = kImmersiveUnknown; + + if (state == kImmersiveUnknown) { + if (IsProcessImmersive(::GetCurrentProcess())) { + state = kImmersiveTrue; + } else { + state = kImmersiveFalse; + } + } + DCHECK_NE(kImmersiveUnknown, state); + return state == kImmersiveTrue; +} + +bool IsProcessImmersive(HANDLE process) { + typedef BOOL (WINAPI* IsImmersiveProcessFunc)(HANDLE process); + HMODULE user32 = ::GetModuleHandleA("user32.dll"); + DCHECK(user32 != NULL); + + IsImmersiveProcessFunc is_immersive_process = + reinterpret_cast<IsImmersiveProcessFunc>( + ::GetProcAddress(user32, "IsImmersiveProcess")); + + if (is_immersive_process) + return is_immersive_process(process) ? true: false; + return false; +} + +bool IsTSFAwareRequired() { + // Although this function is equal to IsMetroProcess at this moment, + // Chrome for Win7 and Vista may support TSF in the future. + return g_should_tsf_aware_required || IsMetroProcess(); +} + +void SetForceToUseTSF() { + g_should_tsf_aware_required = true; + + // Since Windows 8 Metro mode disables CUAS (Cicero Unaware Application + // Support) via ImmDisableLegacyIME API, Chrome must be fully TSF-aware on + // Metro mode. For debugging purposes, explicitly call ImmDisableLegacyIME so + // that one can test TSF functionality even on Windows 8 desktop mode. Note + // that CUAS cannot be disabled on Windows Vista/7 where ImmDisableLegacyIME + // is not available. + typedef BOOL (* ImmDisableLegacyIMEFunc)(); + HMODULE imm32 = ::GetModuleHandleA("imm32.dll"); + if (imm32 == NULL) + return; + + ImmDisableLegacyIMEFunc imm_disable_legacy_ime = + reinterpret_cast<ImmDisableLegacyIMEFunc>( + ::GetProcAddress(imm32, "ImmDisableLegacyIME")); + + if (imm_disable_legacy_ime == NULL) { + // Unsupported API, just do nothing. + return; + } + + if (!imm_disable_legacy_ime()) { + DVLOG(1) << "Failed to disable legacy IME."; + } +} + +wchar_t* LocalAllocAndCopyString(const string16& src) { + size_t dest_size = (src.length() + 1) * sizeof(wchar_t); + wchar_t* dest = reinterpret_cast<wchar_t*>(LocalAlloc(LPTR, dest_size)); + base::wcslcpy(dest, src.c_str(), dest_size); + return dest; +} + +bool IsTouchEnabled() { + int value = GetSystemMetrics(SM_DIGITIZER); + return (value & (NID_READY | NID_INTEGRATED_TOUCH)) == + (NID_READY | NID_INTEGRATED_TOUCH); +} + +bool IsParentalControlActivityLoggingOn() { + // Query this info on Windows Vista and above. + if (base::win::GetVersion() < base::win::VERSION_VISTA) + return false; + + static bool parental_control_logging_required = false; + static bool parental_control_status_determined = false; + + if (parental_control_status_determined) + return parental_control_logging_required; + + parental_control_status_determined = true; + + ScopedComPtr<IWindowsParentalControlsCore> parent_controls; + HRESULT hr = parent_controls.CreateInstance( + __uuidof(WindowsParentalControls)); + if (FAILED(hr)) + return false; + + ScopedComPtr<IWPCSettings> settings; + hr = parent_controls->GetUserSettings(NULL, settings.Receive()); + if (FAILED(hr)) + return false; + + unsigned long restrictions = 0; + settings->GetRestrictions(&restrictions); + + parental_control_logging_required = + (restrictions & WPCFLAG_LOGGING_REQUIRED) == WPCFLAG_LOGGING_REQUIRED; + return parental_control_logging_required; +} + +// Metro driver exports for getting the launch type, initial url, initial +// search term, etc. +extern "C" { +typedef const wchar_t* (*GetInitialUrl)(); +typedef const wchar_t* (*GetInitialSearchString)(); +typedef base::win::MetroLaunchType (*GetLaunchType)( + base::win::MetroPreviousExecutionState* previous_state); +} + +MetroLaunchType GetMetroLaunchParams(string16* params) { + HMODULE metro = base::win::GetMetroModule(); + if (!metro) + return base::win::METRO_LAUNCH_ERROR; + + GetLaunchType get_launch_type = reinterpret_cast<GetLaunchType>( + ::GetProcAddress(metro, "GetLaunchType")); + DCHECK(get_launch_type); + + base::win::MetroLaunchType launch_type = get_launch_type(NULL); + + if ((launch_type == base::win::METRO_PROTOCOL) || + (launch_type == base::win::METRO_LAUNCH)) { + GetInitialUrl initial_metro_url = reinterpret_cast<GetInitialUrl>( + ::GetProcAddress(metro, "GetInitialUrl")); + DCHECK(initial_metro_url); + *params = initial_metro_url(); + } else if (launch_type == base::win::METRO_SEARCH) { + GetInitialSearchString initial_search_string = + reinterpret_cast<GetInitialSearchString>( + ::GetProcAddress(metro, "GetInitialSearchString")); + DCHECK(initial_search_string); + *params = initial_search_string(); + } + return launch_type; +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/metro.h b/src/base/win/metro.h new file mode 100644 index 0000000..9354de8 --- /dev/null +++ b/src/base/win/metro.h
@@ -0,0 +1,142 @@ +// 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_WIN_METRO_H_ +#define BASE_WIN_METRO_H_ + +#include <windows.h> +#include <wpcapi.h> + +#include "base/base_export.h" +#include "base/callback.h" +#include "base/file_path.h" +#include "base/string16.h" + +namespace base { +namespace win { + +// Identifies the type of the metro launch. +enum MetroLaunchType { + METRO_LAUNCH, + METRO_SEARCH, + METRO_SHARE, + METRO_FILE, + METRO_PROTOCOL, + METRO_LAUNCH_ERROR, + METRO_LASTLAUNCHTYPE, +}; + +// In metro mode, this enum identifies the last execution state, i.e. whether +// we crashed, terminated, etc. +enum MetroPreviousExecutionState { + NOTRUNNING, + RUNNING, + SUSPENDED, + TERMINATED, + CLOSEDBYUSER, + LASTEXECUTIONSTATE, +}; + +// Enum values for UMA histogram reporting of site-specific tile pinning. +// TODO(tapted): Move this to win8/util when ready (http://crbug.com/160288). +enum MetroSecondaryTilePinUmaResult { + METRO_PIN_STATE_NONE, + METRO_PIN_INITIATED, + METRO_PIN_LOGO_READY, + METRO_PIN_REQUEST_SHOW_ERROR, + METRO_PIN_RESULT_CANCEL, + METRO_PIN_RESULT_OK, + METRO_PIN_RESULT_OTHER, + METRO_PIN_RESULT_ERROR, + METRO_UNPIN_INITIATED, + METRO_UNPIN_REQUEST_SHOW_ERROR, + METRO_UNPIN_RESULT_CANCEL, + METRO_UNPIN_RESULT_OK, + METRO_UNPIN_RESULT_OTHER, + METRO_UNPIN_RESULT_ERROR, + METRO_PIN_STATE_LIMIT +}; + +// Contains information about the currently displayed tab in metro mode. +struct CurrentTabInfo { + wchar_t* title; + wchar_t* url; +}; + +// Returns the handle to the metro dll loaded in the process. A NULL return +// indicates that the metro dll was not loaded in the process. +BASE_EXPORT HMODULE GetMetroModule(); + +// Returns true if this process is running as an immersive program +// in Windows Metro mode. +BASE_EXPORT bool IsMetroProcess(); + +// Returns true if the process identified by the handle passed in is an +// immersive (Metro) process. +BASE_EXPORT bool IsProcessImmersive(HANDLE process); + +// Returns true if this process is running under Text Services Framework (TSF) +// and browser must be TSF-aware. +BASE_EXPORT bool IsTSFAwareRequired(); + +// Sets browser to use Text Services Framework (TSF) regardless of process +// status. On Windows 8, this function also disables CUAS (Cicero Unaware +// Application Support) to emulate Windows Metro mode in terms of IME +// functionality. This should be beneficial in QA process because on can test +// IME functionality in Windows 8 desktop mode. +BASE_EXPORT void SetForceToUseTSF(); + +// Allocates and returns the destination string via the LocalAlloc API after +// copying the src to it. +BASE_EXPORT wchar_t* LocalAllocAndCopyString(const string16& src); + +// Returns true if the screen supports touch. +BASE_EXPORT bool IsTouchEnabled(); + +// Returns true if Windows Parental control activity logging is enabled. This +// feature is available on Windows Vista and beyond. +// This function should ideally be called on the UI thread. +BASE_EXPORT bool IsParentalControlActivityLoggingOn(); + +// Returns the type of launch and the activation params. For example if the +// the launch is for METRO_PROTOCOL then the params is a url. +BASE_EXPORT MetroLaunchType GetMetroLaunchParams(string16* params); + +// Handler function for the buttons on a metro dialog box +typedef void (*MetroDialogButtonPressedHandler)(); + +// Handler function invoked when a metro style notification is clicked. +typedef void (*MetroNotificationClickedHandler)(const wchar_t* context); + +// Function to display metro style notifications. +typedef void (*MetroNotification)(const char* origin_url, + const char* icon_url, + const wchar_t* title, + const wchar_t* body, + const wchar_t* display_source, + const char* notification_id, + MetroNotificationClickedHandler handler, + const wchar_t* handler_context); + +// Callback for UMA invoked by Metro Pin and UnPin functions after user gesture. +typedef base::Callback<void(MetroSecondaryTilePinUmaResult)> + MetroPinUmaResultCallback; + +// Function to pin a site-specific tile (bookmark) to the start screen. +typedef void (*MetroPinToStartScreen)( + const string16& tile_id, + const string16& title, + const string16& url, + const FilePath& logo_path, + const MetroPinUmaResultCallback& callback); + +// Function to un-pin a site-specific tile (bookmark) from the start screen. +typedef void (*MetroUnPinFromStartScreen)( + const string16& title_id, + const MetroPinUmaResultCallback& callback); + +} // namespace win +} // namespace base + +#endif // BASE_WIN_METRO_H_
diff --git a/src/base/win/object_watcher.cc b/src/base/win/object_watcher.cc new file mode 100644 index 0000000..ebe596f --- /dev/null +++ b/src/base/win/object_watcher.cc
@@ -0,0 +1,111 @@ +// 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/object_watcher.h" + +#include "base/bind.h" +#include "base/logging.h" + +namespace base { +namespace win { + +//----------------------------------------------------------------------------- + +ObjectWatcher::ObjectWatcher() + : weak_factory_(this), + object_(NULL), + wait_object_(NULL), + origin_loop_(NULL) { +} + +ObjectWatcher::~ObjectWatcher() { + StopWatching(); +} + +bool ObjectWatcher::StartWatching(HANDLE object, Delegate* delegate) { + CHECK(delegate); + if (wait_object_) { + NOTREACHED() << "Already watching an object"; + return false; + } + + // Since our job is to just notice when an object is signaled and report the + // result back to this thread, we can just run on a Windows wait thread. + DWORD wait_flags = WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE; + + // DoneWaiting can be synchronously called from RegisterWaitForSingleObject, + // so set up all state now. + callback_ = base::Bind(&ObjectWatcher::Signal, weak_factory_.GetWeakPtr(), + delegate); + object_ = object; + origin_loop_ = MessageLoop::current(); + + if (!RegisterWaitForSingleObject(&wait_object_, object, DoneWaiting, + this, INFINITE, wait_flags)) { + NOTREACHED() << "RegisterWaitForSingleObject failed: " << GetLastError(); + object_ = NULL; + wait_object_ = NULL; + return false; + } + + // We need to know if the current message loop is going away so we can + // prevent the wait thread from trying to access a dead message loop. + MessageLoop::current()->AddDestructionObserver(this); + return true; +} + +bool ObjectWatcher::StopWatching() { + if (!wait_object_) + return false; + + // Make sure ObjectWatcher is used in a single-threaded fashion. + DCHECK(origin_loop_ == MessageLoop::current()); + + // Blocking call to cancel the wait. Any callbacks already in progress will + // finish before we return from this call. + if (!UnregisterWaitEx(wait_object_, INVALID_HANDLE_VALUE)) { + NOTREACHED() << "UnregisterWaitEx failed: " << GetLastError(); + return false; + } + + weak_factory_.InvalidateWeakPtrs(); + object_ = NULL; + wait_object_ = NULL; + + MessageLoop::current()->RemoveDestructionObserver(this); + return true; +} + +HANDLE ObjectWatcher::GetWatchedObject() { + return object_; +} + +// static +void CALLBACK ObjectWatcher::DoneWaiting(void* param, BOOLEAN timed_out) { + DCHECK(!timed_out); + + // The destructor blocks on any callbacks that are in flight, so we know that + // that is always a pointer to a valid ObjectWater. + ObjectWatcher* that = static_cast<ObjectWatcher*>(param); + that->origin_loop_->PostTask(FROM_HERE, that->callback_); + that->callback_.Reset(); +} + +void ObjectWatcher::Signal(Delegate* delegate) { + // Signaling the delegate may result in our destruction or a nested call to + // StartWatching(). As a result, we save any state we need and clear previous + // watcher state before signaling the delegate. + HANDLE object = object_; + StopWatching(); + delegate->OnObjectSignaled(object); +} + +void ObjectWatcher::WillDestroyCurrentMessageLoop() { + // Need to shutdown the watch so that we don't try to access the MessageLoop + // after this point. + StopWatching(); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/object_watcher.h b/src/base/win/object_watcher.h new file mode 100644 index 0000000..742f2b0 --- /dev/null +++ b/src/base/win/object_watcher.h
@@ -0,0 +1,103 @@ +// 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. + +#ifndef BASE_WIN_OBJECT_WATCHER_H_ +#define BASE_WIN_OBJECT_WATCHER_H_ + +#include <windows.h> + +#include "base/base_export.h" +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop.h" + +namespace base { +namespace win { + +// A class that provides a means to asynchronously wait for a Windows object to +// become signaled. It is an abstraction around RegisterWaitForSingleObject +// that provides a notification callback, OnObjectSignaled, that runs back on +// the origin thread (i.e., the thread that called StartWatching). +// +// This class acts like a smart pointer such that when it goes out-of-scope, +// UnregisterWaitEx is automatically called, and any in-flight notification is +// suppressed. +// +// Typical usage: +// +// class MyClass : public base::ObjectWatcher::Delegate { +// public: +// void DoStuffWhenSignaled(HANDLE object) { +// watcher_.StartWatching(object, this); +// } +// virtual void OnObjectSignaled(HANDLE object) { +// // OK, time to do stuff! +// } +// private: +// base::ObjectWatcher watcher_; +// }; +// +// In the above example, MyClass wants to "do stuff" when object becomes +// signaled. ObjectWatcher makes this task easy. When MyClass goes out of +// scope, the watcher_ will be destroyed, and there is no need to worry about +// OnObjectSignaled being called on a deleted MyClass pointer. Easy! +// If the object is already signaled before being watched, OnObjectSignaled is +// still called after (but not necessarily immediately after) watch is started. +// +class BASE_EXPORT ObjectWatcher : public MessageLoop::DestructionObserver { + public: + class BASE_EXPORT Delegate { + public: + virtual ~Delegate() {} + // Called from the MessageLoop when a signaled object is detected. To + // continue watching the object, StartWatching must be called again. + virtual void OnObjectSignaled(HANDLE object) = 0; + }; + + ObjectWatcher(); + ~ObjectWatcher(); + + // When the object is signaled, the given delegate is notified on the thread + // where StartWatching is called. The ObjectWatcher is not responsible for + // deleting the delegate. + // + // Returns true if the watch was started. Otherwise, false is returned. + // + bool StartWatching(HANDLE object, Delegate* delegate); + + // Stops watching. Does nothing if the watch has already completed. If the + // watch is still active, then it is canceled, and the associated delegate is + // not notified. + // + // Returns true if the watch was canceled. Otherwise, false is returned. + // + bool StopWatching(); + + // Returns the handle of the object being watched, or NULL if the object + // watcher is stopped. + HANDLE GetWatchedObject(); + + private: + // Called on a background thread when done waiting. + static void CALLBACK DoneWaiting(void* param, BOOLEAN timed_out); + + void Signal(Delegate* delegate); + + // MessageLoop::DestructionObserver implementation: + virtual void WillDestroyCurrentMessageLoop(); + + // Internal state. + WeakPtrFactory<ObjectWatcher> weak_factory_; + Closure callback_; + HANDLE object_; // The object being watched + HANDLE wait_object_; // Returned by RegisterWaitForSingleObject + MessageLoop* origin_loop_; // Used to get back to the origin thread + + DISALLOW_COPY_AND_ASSIGN(ObjectWatcher); +}; + +} // namespace win +} // namespace base + +#endif // BASE_OBJECT_WATCHER_H_
diff --git a/src/base/win/object_watcher_unittest.cc b/src/base/win/object_watcher_unittest.cc new file mode 100644 index 0000000..e8484b9 --- /dev/null +++ b/src/base/win/object_watcher_unittest.cc
@@ -0,0 +1,172 @@ +// 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 <process.h> + +#include "base/message_loop.h" +#include "base/win/object_watcher.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +namespace { + +class QuitDelegate : public ObjectWatcher::Delegate { + public: + virtual void OnObjectSignaled(HANDLE object) { + MessageLoop::current()->Quit(); + } +}; + +class DecrementCountDelegate : public ObjectWatcher::Delegate { + public: + explicit DecrementCountDelegate(int* counter) : counter_(counter) { + } + virtual void OnObjectSignaled(HANDLE object) { + --(*counter_); + } + private: + int* counter_; +}; + +void RunTest_BasicSignal(MessageLoop::Type message_loop_type) { + MessageLoop message_loop(message_loop_type); + + ObjectWatcher watcher; + EXPECT_EQ(NULL, watcher.GetWatchedObject()); + + // A manual-reset event that is not yet signaled. + HANDLE event = CreateEvent(NULL, TRUE, FALSE, NULL); + + QuitDelegate delegate; + bool ok = watcher.StartWatching(event, &delegate); + EXPECT_TRUE(ok); + EXPECT_EQ(event, watcher.GetWatchedObject()); + + SetEvent(event); + + MessageLoop::current()->Run(); + + EXPECT_EQ(NULL, watcher.GetWatchedObject()); + CloseHandle(event); +} + +void RunTest_BasicCancel(MessageLoop::Type message_loop_type) { + MessageLoop message_loop(message_loop_type); + + ObjectWatcher watcher; + + // A manual-reset event that is not yet signaled. + HANDLE event = CreateEvent(NULL, TRUE, FALSE, NULL); + + QuitDelegate delegate; + bool ok = watcher.StartWatching(event, &delegate); + EXPECT_TRUE(ok); + + watcher.StopWatching(); + + CloseHandle(event); +} + +void RunTest_CancelAfterSet(MessageLoop::Type message_loop_type) { + MessageLoop message_loop(message_loop_type); + + ObjectWatcher watcher; + + int counter = 1; + DecrementCountDelegate delegate(&counter); + + // A manual-reset event that is not yet signaled. + HANDLE event = CreateEvent(NULL, TRUE, FALSE, NULL); + + bool ok = watcher.StartWatching(event, &delegate); + EXPECT_TRUE(ok); + + SetEvent(event); + + // Let the background thread do its business + Sleep(30); + + watcher.StopWatching(); + + MessageLoop::current()->RunUntilIdle(); + + // Our delegate should not have fired. + EXPECT_EQ(1, counter); + + CloseHandle(event); +} + +void RunTest_SignalBeforeWatch(MessageLoop::Type message_loop_type) { + MessageLoop message_loop(message_loop_type); + + ObjectWatcher watcher; + + // A manual-reset event that is signaled before we begin watching. + HANDLE event = CreateEvent(NULL, TRUE, TRUE, NULL); + + QuitDelegate delegate; + bool ok = watcher.StartWatching(event, &delegate); + EXPECT_TRUE(ok); + + MessageLoop::current()->Run(); + + EXPECT_EQ(NULL, watcher.GetWatchedObject()); + CloseHandle(event); +} + +void RunTest_OutlivesMessageLoop(MessageLoop::Type message_loop_type) { + // Simulate a MessageLoop that dies before an ObjectWatcher. This ordinarily + // doesn't happen when people use the Thread class, but it can happen when + // people use the Singleton pattern or atexit. + HANDLE event = CreateEvent(NULL, TRUE, FALSE, NULL); // not signaled + { + ObjectWatcher watcher; + { + MessageLoop message_loop(message_loop_type); + + QuitDelegate delegate; + watcher.StartWatching(event, &delegate); + } + } + CloseHandle(event); +} + +} // namespace + +//----------------------------------------------------------------------------- + +TEST(ObjectWatcherTest, BasicSignal) { + RunTest_BasicSignal(MessageLoop::TYPE_DEFAULT); + RunTest_BasicSignal(MessageLoop::TYPE_IO); + RunTest_BasicSignal(MessageLoop::TYPE_UI); +} + +TEST(ObjectWatcherTest, BasicCancel) { + RunTest_BasicCancel(MessageLoop::TYPE_DEFAULT); + RunTest_BasicCancel(MessageLoop::TYPE_IO); + RunTest_BasicCancel(MessageLoop::TYPE_UI); +} + +TEST(ObjectWatcherTest, CancelAfterSet) { + RunTest_CancelAfterSet(MessageLoop::TYPE_DEFAULT); + RunTest_CancelAfterSet(MessageLoop::TYPE_IO); + RunTest_CancelAfterSet(MessageLoop::TYPE_UI); +} + +TEST(ObjectWatcherTest, SignalBeforeWatch) { + RunTest_SignalBeforeWatch(MessageLoop::TYPE_DEFAULT); + RunTest_SignalBeforeWatch(MessageLoop::TYPE_IO); + RunTest_SignalBeforeWatch(MessageLoop::TYPE_UI); +} + +TEST(ObjectWatcherTest, OutlivesMessageLoop) { + RunTest_OutlivesMessageLoop(MessageLoop::TYPE_DEFAULT); + RunTest_OutlivesMessageLoop(MessageLoop::TYPE_IO); + RunTest_OutlivesMessageLoop(MessageLoop::TYPE_UI); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/pe_image.cc b/src/base/win/pe_image.cc new file mode 100644 index 0000000..fcf03c1 --- /dev/null +++ b/src/base/win/pe_image.cc
@@ -0,0 +1,570 @@ +// Copyright (c) 2010 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 implements PEImage, a generic class to manipulate PE files. +// This file was adapted from GreenBorder's Code. + +#include "base/win/pe_image.h" + +namespace base { +namespace win { + +#if defined(_WIN64) && !defined(NACL_WIN64) +// TODO(rvargas): Bug 27218. Make sure this is ok. +#error This code is not tested on x64. Please make sure all the base unit tests\ + pass before doing any real work. The current unit tests don't test the\ + differences between 32- and 64-bits implementations. Bugs may slip through.\ + You need to improve the coverage before continuing. +#endif + +// Structure to perform imports enumerations. +struct EnumAllImportsStorage { + PEImage::EnumImportsFunction callback; + PVOID cookie; +}; + +namespace { + + // Compare two strings byte by byte on an unsigned basis. + // if s1 == s2, return 0 + // if s1 < s2, return negative + // if s1 > s2, return positive + // Exception if inputs are invalid. + int StrCmpByByte(LPCSTR s1, LPCSTR s2) { + while (*s1 != '\0' && *s1 == *s2) { + ++s1; + ++s2; + } + + return (*reinterpret_cast<const unsigned char*>(s1) - + *reinterpret_cast<const unsigned char*>(s2)); + } + +} // namespace + +// Callback used to enumerate imports. See EnumImportChunksFunction. +bool ProcessImportChunk(const PEImage &image, LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, PVOID cookie) { + EnumAllImportsStorage &storage = *reinterpret_cast<EnumAllImportsStorage*>( + cookie); + + return image.EnumOneImportChunk(storage.callback, module, name_table, iat, + storage.cookie); +} + +// Callback used to enumerate delay imports. See EnumDelayImportChunksFunction. +bool ProcessDelayImportChunk(const PEImage &image, + PImgDelayDescr delay_descriptor, + LPCSTR module, PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, PIMAGE_THUNK_DATA bound_iat, + PIMAGE_THUNK_DATA unload_iat, PVOID cookie) { + EnumAllImportsStorage &storage = *reinterpret_cast<EnumAllImportsStorage*>( + cookie); + + return image.EnumOneDelayImportChunk(storage.callback, delay_descriptor, + module, name_table, iat, bound_iat, + unload_iat, storage.cookie); +} + +void PEImage::set_module(HMODULE module) { + module_ = module; +} + +PIMAGE_DOS_HEADER PEImage::GetDosHeader() const { + return reinterpret_cast<PIMAGE_DOS_HEADER>(module_); +} + +PIMAGE_NT_HEADERS PEImage::GetNTHeaders() const { + PIMAGE_DOS_HEADER dos_header = GetDosHeader(); + + return reinterpret_cast<PIMAGE_NT_HEADERS>( + reinterpret_cast<char*>(dos_header) + dos_header->e_lfanew); +} + +PIMAGE_SECTION_HEADER PEImage::GetSectionHeader(UINT section) const { + PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); + PIMAGE_SECTION_HEADER first_section = IMAGE_FIRST_SECTION(nt_headers); + + if (section < nt_headers->FileHeader.NumberOfSections) + return first_section + section; + else + return NULL; +} + +WORD PEImage::GetNumSections() const { + return GetNTHeaders()->FileHeader.NumberOfSections; +} + +DWORD PEImage::GetImageDirectoryEntrySize(UINT directory) const { + PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); + + return nt_headers->OptionalHeader.DataDirectory[directory].Size; +} + +PVOID PEImage::GetImageDirectoryEntryAddr(UINT directory) const { + PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); + + return RVAToAddr( + nt_headers->OptionalHeader.DataDirectory[directory].VirtualAddress); +} + +PIMAGE_SECTION_HEADER PEImage::GetImageSectionFromAddr(PVOID address) const { + PBYTE target = reinterpret_cast<PBYTE>(address); + PIMAGE_SECTION_HEADER section; + + for (UINT i = 0; NULL != (section = GetSectionHeader(i)); i++) { + // Don't use the virtual RVAToAddr. + PBYTE start = reinterpret_cast<PBYTE>( + PEImage::RVAToAddr(section->VirtualAddress)); + + DWORD size = section->Misc.VirtualSize; + + if ((start <= target) && (start + size > target)) + return section; + } + + return NULL; +} + +PIMAGE_SECTION_HEADER PEImage::GetImageSectionHeaderByName( + LPCSTR section_name) const { + if (NULL == section_name) + return NULL; + + PIMAGE_SECTION_HEADER ret = NULL; + int num_sections = GetNumSections(); + + for (int i = 0; i < num_sections; i++) { + PIMAGE_SECTION_HEADER section = GetSectionHeader(i); + if (0 == _strnicmp(reinterpret_cast<LPCSTR>(section->Name), section_name, + sizeof(section->Name))) { + ret = section; + break; + } + } + + return ret; +} + +PDWORD PEImage::GetExportEntry(LPCSTR name) const { + PIMAGE_EXPORT_DIRECTORY exports = GetExportDirectory(); + + if (NULL == exports) + return NULL; + + WORD ordinal = 0; + if (!GetProcOrdinal(name, &ordinal)) + return NULL; + + PDWORD functions = reinterpret_cast<PDWORD>( + RVAToAddr(exports->AddressOfFunctions)); + + return functions + ordinal - exports->Base; +} + +FARPROC PEImage::GetProcAddress(LPCSTR function_name) const { + PDWORD export_entry = GetExportEntry(function_name); + if (NULL == export_entry) + return NULL; + + PBYTE function = reinterpret_cast<PBYTE>(RVAToAddr(*export_entry)); + + PBYTE exports = reinterpret_cast<PBYTE>( + GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT)); + DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_EXPORT); + + // Check for forwarded exports as a special case. + if (exports <= function && exports + size > function) +#pragma warning(push) +#pragma warning(disable: 4312) + // This cast generates a warning because it is 32 bit specific. + return reinterpret_cast<FARPROC>(0xFFFFFFFF); +#pragma warning(pop) + + return reinterpret_cast<FARPROC>(function); +} + +bool PEImage::GetProcOrdinal(LPCSTR function_name, WORD *ordinal) const { + if (NULL == ordinal) + return false; + + PIMAGE_EXPORT_DIRECTORY exports = GetExportDirectory(); + + if (NULL == exports) + return false; + + if (IsOrdinal(function_name)) { + *ordinal = ToOrdinal(function_name); + } else { + PDWORD names = reinterpret_cast<PDWORD>(RVAToAddr(exports->AddressOfNames)); + PDWORD lower = names; + PDWORD upper = names + exports->NumberOfNames; + int cmp = -1; + + // Binary Search for the name. + while (lower != upper) { + PDWORD middle = lower + (upper - lower) / 2; + LPCSTR name = reinterpret_cast<LPCSTR>(RVAToAddr(*middle)); + + // This may be called by sandbox before MSVCRT dll loads, so can't use + // CRT function here. + cmp = StrCmpByByte(function_name, name); + + if (cmp == 0) { + lower = middle; + break; + } + + if (cmp > 0) + lower = middle + 1; + else + upper = middle; + } + + if (cmp != 0) + return false; + + + PWORD ordinals = reinterpret_cast<PWORD>( + RVAToAddr(exports->AddressOfNameOrdinals)); + + *ordinal = ordinals[lower - names] + static_cast<WORD>(exports->Base); + } + + return true; +} + +bool PEImage::EnumSections(EnumSectionsFunction callback, PVOID cookie) const { + PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); + UINT num_sections = nt_headers->FileHeader.NumberOfSections; + PIMAGE_SECTION_HEADER section = GetSectionHeader(0); + + for (UINT i = 0; i < num_sections; i++, section++) { + PVOID section_start = RVAToAddr(section->VirtualAddress); + DWORD size = section->Misc.VirtualSize; + + if (!callback(*this, section, section_start, size, cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumExports(EnumExportsFunction callback, PVOID cookie) const { + PVOID directory = GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT); + DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_EXPORT); + + // Check if there are any exports at all. + if (NULL == directory || 0 == size) + return true; + + PIMAGE_EXPORT_DIRECTORY exports = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>( + directory); + UINT ordinal_base = exports->Base; + UINT num_funcs = exports->NumberOfFunctions; + UINT num_names = exports->NumberOfNames; + PDWORD functions = reinterpret_cast<PDWORD>(RVAToAddr( + exports->AddressOfFunctions)); + PDWORD names = reinterpret_cast<PDWORD>(RVAToAddr(exports->AddressOfNames)); + PWORD ordinals = reinterpret_cast<PWORD>(RVAToAddr( + exports->AddressOfNameOrdinals)); + + for (UINT count = 0; count < num_funcs; count++) { + PVOID func = RVAToAddr(functions[count]); + if (NULL == func) + continue; + + // Check for a name. + LPCSTR name = NULL; + UINT hint; + for (hint = 0; hint < num_names; hint++) { + if (ordinals[hint] == count) { + name = reinterpret_cast<LPCSTR>(RVAToAddr(names[hint])); + break; + } + } + + if (name == NULL) + hint = 0; + + // Check for forwarded exports. + LPCSTR forward = NULL; + if (reinterpret_cast<char*>(func) >= reinterpret_cast<char*>(directory) && + reinterpret_cast<char*>(func) <= reinterpret_cast<char*>(directory) + + size) { + forward = reinterpret_cast<LPCSTR>(func); + func = 0; + } + + if (!callback(*this, ordinal_base + count, hint, name, func, forward, + cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumRelocs(EnumRelocsFunction callback, PVOID cookie) const { + PVOID directory = GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_BASERELOC); + DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_BASERELOC); + PIMAGE_BASE_RELOCATION base = reinterpret_cast<PIMAGE_BASE_RELOCATION>( + directory); + + if (directory == NULL || size < sizeof(IMAGE_BASE_RELOCATION)) + return true; + + while (base->SizeOfBlock) { + PWORD reloc = reinterpret_cast<PWORD>(base + 1); + UINT num_relocs = (base->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / + sizeof(WORD); + + for (UINT i = 0; i < num_relocs; i++, reloc++) { + WORD type = *reloc >> 12; + PVOID address = RVAToAddr(base->VirtualAddress + (*reloc & 0x0FFF)); + + if (!callback(*this, type, address, cookie)) + return false; + } + + base = reinterpret_cast<PIMAGE_BASE_RELOCATION>( + reinterpret_cast<char*>(base) + base->SizeOfBlock); + } + + return true; +} + +bool PEImage::EnumImportChunks(EnumImportChunksFunction callback, + PVOID cookie) const { + DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_IMPORT); + PIMAGE_IMPORT_DESCRIPTOR import = GetFirstImportChunk(); + + if (import == NULL || size < sizeof(IMAGE_IMPORT_DESCRIPTOR)) + return true; + + for (; import->FirstThunk; import++) { + LPCSTR module_name = reinterpret_cast<LPCSTR>(RVAToAddr(import->Name)); + PIMAGE_THUNK_DATA name_table = reinterpret_cast<PIMAGE_THUNK_DATA>( + RVAToAddr(import->OriginalFirstThunk)); + PIMAGE_THUNK_DATA iat = reinterpret_cast<PIMAGE_THUNK_DATA>( + RVAToAddr(import->FirstThunk)); + + if (!callback(*this, module_name, name_table, iat, cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumOneImportChunk(EnumImportsFunction callback, + LPCSTR module_name, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, PVOID cookie) const { + if (NULL == name_table) + return false; + + for (; name_table && name_table->u1.Ordinal; name_table++, iat++) { + LPCSTR name = NULL; + WORD ordinal = 0; + WORD hint = 0; + + if (IMAGE_SNAP_BY_ORDINAL(name_table->u1.Ordinal)) { + ordinal = static_cast<WORD>(IMAGE_ORDINAL32(name_table->u1.Ordinal)); + } else { + PIMAGE_IMPORT_BY_NAME import = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>( + RVAToAddr(name_table->u1.ForwarderString)); + + hint = import->Hint; + name = reinterpret_cast<LPCSTR>(&import->Name); + } + + if (!callback(*this, module_name, ordinal, name, hint, iat, cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumAllImports(EnumImportsFunction callback, PVOID cookie) const { + EnumAllImportsStorage temp = { callback, cookie }; + return EnumImportChunks(ProcessImportChunk, &temp); +} + +bool PEImage::EnumDelayImportChunks(EnumDelayImportChunksFunction callback, + PVOID cookie) const { + PVOID directory = GetImageDirectoryEntryAddr( + IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); + DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); + PImgDelayDescr delay_descriptor = reinterpret_cast<PImgDelayDescr>(directory); + + if (directory == NULL || size == 0) + return true; + + for (; delay_descriptor->rvaHmod; delay_descriptor++) { + PIMAGE_THUNK_DATA name_table; + PIMAGE_THUNK_DATA iat; + PIMAGE_THUNK_DATA bound_iat; // address of the optional bound IAT + PIMAGE_THUNK_DATA unload_iat; // address of optional copy of original IAT + LPCSTR module_name; + + // check if VC7-style imports, using RVAs instead of + // VC6-style addresses. + bool rvas = (delay_descriptor->grAttrs & dlattrRva) != 0; + + if (rvas) { + module_name = reinterpret_cast<LPCSTR>( + RVAToAddr(delay_descriptor->rvaDLLName)); + name_table = reinterpret_cast<PIMAGE_THUNK_DATA>( + RVAToAddr(delay_descriptor->rvaINT)); + iat = reinterpret_cast<PIMAGE_THUNK_DATA>( + RVAToAddr(delay_descriptor->rvaIAT)); + bound_iat = reinterpret_cast<PIMAGE_THUNK_DATA>( + RVAToAddr(delay_descriptor->rvaBoundIAT)); + unload_iat = reinterpret_cast<PIMAGE_THUNK_DATA>( + RVAToAddr(delay_descriptor->rvaUnloadIAT)); + } else { +#pragma warning(push) +#pragma warning(disable: 4312) + // These casts generate warnings because they are 32 bit specific. + module_name = reinterpret_cast<LPCSTR>(delay_descriptor->rvaDLLName); + name_table = reinterpret_cast<PIMAGE_THUNK_DATA>( + delay_descriptor->rvaINT); + iat = reinterpret_cast<PIMAGE_THUNK_DATA>(delay_descriptor->rvaIAT); + bound_iat = reinterpret_cast<PIMAGE_THUNK_DATA>( + delay_descriptor->rvaBoundIAT); + unload_iat = reinterpret_cast<PIMAGE_THUNK_DATA>( + delay_descriptor->rvaUnloadIAT); +#pragma warning(pop) + } + + if (!callback(*this, delay_descriptor, module_name, name_table, iat, + bound_iat, unload_iat, cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumOneDelayImportChunk(EnumImportsFunction callback, + PImgDelayDescr delay_descriptor, + LPCSTR module_name, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PIMAGE_THUNK_DATA bound_iat, + PIMAGE_THUNK_DATA unload_iat, + PVOID cookie) const { + UNREFERENCED_PARAMETER(bound_iat); + UNREFERENCED_PARAMETER(unload_iat); + + for (; name_table->u1.Ordinal; name_table++, iat++) { + LPCSTR name = NULL; + WORD ordinal = 0; + WORD hint = 0; + + if (IMAGE_SNAP_BY_ORDINAL(name_table->u1.Ordinal)) { + ordinal = static_cast<WORD>(IMAGE_ORDINAL32(name_table->u1.Ordinal)); + } else { + PIMAGE_IMPORT_BY_NAME import; + bool rvas = (delay_descriptor->grAttrs & dlattrRva) != 0; + + if (rvas) { + import = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>( + RVAToAddr(name_table->u1.ForwarderString)); + } else { +#pragma warning(push) +#pragma warning(disable: 4312) + // This cast generates a warning because it is 32 bit specific. + import = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>( + name_table->u1.ForwarderString); +#pragma warning(pop) + } + + hint = import->Hint; + name = reinterpret_cast<LPCSTR>(&import->Name); + } + + if (!callback(*this, module_name, ordinal, name, hint, iat, cookie)) + return false; + } + + return true; +} + +bool PEImage::EnumAllDelayImports(EnumImportsFunction callback, + PVOID cookie) const { + EnumAllImportsStorage temp = { callback, cookie }; + return EnumDelayImportChunks(ProcessDelayImportChunk, &temp); +} + +bool PEImage::VerifyMagic() const { + PIMAGE_DOS_HEADER dos_header = GetDosHeader(); + + if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) + return false; + + PIMAGE_NT_HEADERS nt_headers = GetNTHeaders(); + + if (nt_headers->Signature != IMAGE_NT_SIGNATURE) + return false; + + if (nt_headers->FileHeader.SizeOfOptionalHeader != + sizeof(IMAGE_OPTIONAL_HEADER)) + return false; + + if (nt_headers->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) + return false; + + return true; +} + +bool PEImage::ImageRVAToOnDiskOffset(DWORD rva, DWORD *on_disk_offset) const { + LPVOID address = RVAToAddr(rva); + return ImageAddrToOnDiskOffset(address, on_disk_offset); +} + +bool PEImage::ImageAddrToOnDiskOffset(LPVOID address, + DWORD *on_disk_offset) const { + if (NULL == address) + return false; + + // Get the section that this address belongs to. + PIMAGE_SECTION_HEADER section_header = GetImageSectionFromAddr(address); + if (NULL == section_header) + return false; + +#pragma warning(push) +#pragma warning(disable: 4311) + // These casts generate warnings because they are 32 bit specific. + // Don't follow the virtual RVAToAddr, use the one on the base. + DWORD offset_within_section = reinterpret_cast<DWORD>(address) - + reinterpret_cast<DWORD>(PEImage::RVAToAddr( + section_header->VirtualAddress)); +#pragma warning(pop) + + *on_disk_offset = section_header->PointerToRawData + offset_within_section; + return true; +} + +PVOID PEImage::RVAToAddr(DWORD rva) const { + if (rva == 0) + return NULL; + + return reinterpret_cast<char*>(module_) + rva; +} + +PVOID PEImageAsData::RVAToAddr(DWORD rva) const { + if (rva == 0) + return NULL; + + PVOID in_memory = PEImage::RVAToAddr(rva); + DWORD disk_offset; + + if (!ImageAddrToOnDiskOffset(in_memory, &disk_offset)) + return NULL; + + return PEImage::RVAToAddr(disk_offset); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/pe_image.h b/src/base/win/pe_image.h new file mode 100644 index 0000000..878ef52 --- /dev/null +++ b/src/base/win/pe_image.h
@@ -0,0 +1,268 @@ +// Copyright (c) 2010 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 was adapted from GreenBorder's Code. +// To understand what this class is about (for other than well known functions +// as GetProcAddress), a good starting point is "An In-Depth Look into the +// Win32 Portable Executable File Format" by Matt Pietrek: +// http://msdn.microsoft.com/msdnmag/issues/02/02/PE/default.aspx + +#ifndef BASE_WIN_PE_IMAGE_H_ +#define BASE_WIN_PE_IMAGE_H_ + +#include <windows.h> + +#if defined(_WIN32_WINNT_WIN8) +// The Windows 8 SDK defines FACILITY_VISUALCPP in winerror.h. +#undef FACILITY_VISUALCPP +#endif +#include <DelayIMP.h> + +namespace base { +namespace win { + +// This class is a wrapper for the Portable Executable File Format (PE). +// It's main purpose is to provide an easy way to work with imports and exports +// from a file, mapped in memory as image. +class PEImage { + public: + // Callback to enumerate sections. + // cookie is the value passed to the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumSectionsFunction)(const PEImage &image, + PIMAGE_SECTION_HEADER header, + PVOID section_start, DWORD section_size, + PVOID cookie); + + // Callback to enumerate exports. + // function is the actual address of the symbol. If forward is not null, it + // contains the dll and symbol to forward this export to. cookie is the value + // passed to the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumExportsFunction)(const PEImage &image, DWORD ordinal, + DWORD hint, LPCSTR name, PVOID function, + LPCSTR forward, PVOID cookie); + + // Callback to enumerate import blocks. + // name_table and iat point to the imports name table and address table for + // this block. cookie is the value passed to the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumImportChunksFunction)(const PEImage &image, LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, PVOID cookie); + + // Callback to enumerate imports. + // module is the dll that exports this symbol. cookie is the value passed to + // the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumImportsFunction)(const PEImage &image, LPCSTR module, + DWORD ordinal, LPCSTR name, DWORD hint, + PIMAGE_THUNK_DATA iat, PVOID cookie); + + // Callback to enumerate dalayed import blocks. + // module is the dll that exports this block of symbols. cookie is the value + // passed to the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumDelayImportChunksFunction)(const PEImage &image, + PImgDelayDescr delay_descriptor, + LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PIMAGE_THUNK_DATA bound_iat, + PIMAGE_THUNK_DATA unload_iat, + PVOID cookie); + + // Callback to enumerate relocations. + // cookie is the value passed to the enumerate method. + // Returns true to continue the enumeration. + typedef bool (*EnumRelocsFunction)(const PEImage &image, WORD type, + PVOID address, PVOID cookie); + + explicit PEImage(HMODULE module) : module_(module) {} + explicit PEImage(const void* module) { + module_ = reinterpret_cast<HMODULE>(const_cast<void*>(module)); + } + + // Gets the HMODULE for this object. + HMODULE module() const; + + // Sets this object's HMODULE. + void set_module(HMODULE module); + + // Checks if this symbol is actually an ordinal. + static bool IsOrdinal(LPCSTR name); + + // Converts a named symbol to the corresponding ordinal. + static WORD ToOrdinal(LPCSTR name); + + // Returns the DOS_HEADER for this PE. + PIMAGE_DOS_HEADER GetDosHeader() const; + + // Returns the NT_HEADER for this PE. + PIMAGE_NT_HEADERS GetNTHeaders() const; + + // Returns number of sections of this PE. + WORD GetNumSections() const; + + // Returns the header for a given section. + // returns NULL if there is no such section. + PIMAGE_SECTION_HEADER GetSectionHeader(UINT section) const; + + // Returns the size of a given directory entry. + DWORD GetImageDirectoryEntrySize(UINT directory) const; + + // Returns the address of a given directory entry. + PVOID GetImageDirectoryEntryAddr(UINT directory) const; + + // Returns the section header for a given address. + // Use: s = image.GetImageSectionFromAddr(a); + // Post: 's' is the section header of the section that contains 'a' + // or NULL if there is no such section. + PIMAGE_SECTION_HEADER GetImageSectionFromAddr(PVOID address) const; + + // Returns the section header for a given section. + PIMAGE_SECTION_HEADER GetImageSectionHeaderByName(LPCSTR section_name) const; + + // Returns the first block of imports. + PIMAGE_IMPORT_DESCRIPTOR GetFirstImportChunk() const; + + // Returns the exports directory. + PIMAGE_EXPORT_DIRECTORY GetExportDirectory() const; + + // Returns a given export entry. + // Use: e = image.GetExportEntry(f); + // Pre: 'f' is either a zero terminated string or ordinal + // Post: 'e' is a pointer to the export directory entry + // that contains 'f's export RVA, or NULL if 'f' + // is not exported from this image + PDWORD GetExportEntry(LPCSTR name) const; + + // Returns the address for a given exported symbol. + // Use: p = image.GetProcAddress(f); + // Pre: 'f' is either a zero terminated string or ordinal. + // Post: if 'f' is a non-forwarded export from image, 'p' is + // the exported function. If 'f' is a forwarded export + // then p is the special value 0xFFFFFFFF. In this case + // RVAToAddr(*GetExportEntry) can be used to resolve + // the string that describes the forward. + FARPROC GetProcAddress(LPCSTR function_name) const; + + // Retrieves the ordinal for a given exported symbol. + // Returns true if the symbol was found. + bool GetProcOrdinal(LPCSTR function_name, WORD *ordinal) const; + + // Enumerates PE sections. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumSections(EnumSectionsFunction callback, PVOID cookie) const; + + // Enumerates PE exports. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumExports(EnumExportsFunction callback, PVOID cookie) const; + + // Enumerates PE imports. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumAllImports(EnumImportsFunction callback, PVOID cookie) const; + + // Enumerates PE import blocks. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumImportChunks(EnumImportChunksFunction callback, PVOID cookie) const; + + // Enumerates the imports from a single PE import block. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumOneImportChunk(EnumImportsFunction callback, LPCSTR module_name, + PIMAGE_THUNK_DATA name_table, PIMAGE_THUNK_DATA iat, + PVOID cookie) const; + + + // Enumerates PE delay imports. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumAllDelayImports(EnumImportsFunction callback, PVOID cookie) const; + + // Enumerates PE delay import blocks. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumDelayImportChunks(EnumDelayImportChunksFunction callback, + PVOID cookie) const; + + // Enumerates imports from a single PE delay import block. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumOneDelayImportChunk(EnumImportsFunction callback, + PImgDelayDescr delay_descriptor, + LPCSTR module_name, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PIMAGE_THUNK_DATA bound_iat, + PIMAGE_THUNK_DATA unload_iat, + PVOID cookie) const; + + // Enumerates PE relocation entries. + // cookie is a generic cookie to pass to the callback. + // Returns true on success. + bool EnumRelocs(EnumRelocsFunction callback, PVOID cookie) const; + + // Verifies the magic values on the PE file. + // Returns true if all values are correct. + bool VerifyMagic() const; + + // Converts an rva value to the appropriate address. + virtual PVOID RVAToAddr(DWORD rva) const; + + // Converts an rva value to an offset on disk. + // Returns true on success. + bool ImageRVAToOnDiskOffset(DWORD rva, DWORD *on_disk_offset) const; + + // Converts an address to an offset on disk. + // Returns true on success. + bool ImageAddrToOnDiskOffset(LPVOID address, DWORD *on_disk_offset) const; + + private: + HMODULE module_; +}; + +// This class is an extension to the PEImage class that allows working with PE +// files mapped as data instead of as image file. +class PEImageAsData : public PEImage { + public: + explicit PEImageAsData(HMODULE hModule) : PEImage(hModule) {} + + virtual PVOID RVAToAddr(DWORD rva) const; +}; + +inline bool PEImage::IsOrdinal(LPCSTR name) { +#pragma warning(push) +#pragma warning(disable: 4311) + // This cast generates a warning because it is 32 bit specific. + return reinterpret_cast<DWORD>(name) <= 0xFFFF; +#pragma warning(pop) +} + +inline WORD PEImage::ToOrdinal(LPCSTR name) { + return reinterpret_cast<WORD>(name); +} + +inline HMODULE PEImage::module() const { + return module_; +} + +inline PIMAGE_IMPORT_DESCRIPTOR PEImage::GetFirstImportChunk() const { + return reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>( + GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_IMPORT)); +} + +inline PIMAGE_EXPORT_DIRECTORY PEImage::GetExportDirectory() const { + return reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>( + GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT)); +} + +} // namespace win +} // namespace base + +#endif // BASE_WIN_PE_IMAGE_H_
diff --git a/src/base/win/pe_image_unittest.cc b/src/base/win/pe_image_unittest.cc new file mode 100644 index 0000000..e308eae --- /dev/null +++ b/src/base/win/pe_image_unittest.cc
@@ -0,0 +1,256 @@ +// 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. + +// This file contains unit tests for PEImage. + +#include "testing/gtest/include/gtest/gtest.h" +#include "base/win/pe_image.h" +#include "base/win/windows_version.h" + +namespace base { +namespace win { + +// Just counts the number of invocations. +bool ExportsCallback(const PEImage &image, + DWORD ordinal, + DWORD hint, + LPCSTR name, + PVOID function, + LPCSTR forward, + PVOID cookie) { + int* count = reinterpret_cast<int*>(cookie); + (*count)++; + return true; +} + +// Just counts the number of invocations. +bool ImportsCallback(const PEImage &image, + LPCSTR module, + DWORD ordinal, + LPCSTR name, + DWORD hint, + PIMAGE_THUNK_DATA iat, + PVOID cookie) { + int* count = reinterpret_cast<int*>(cookie); + (*count)++; + return true; +} + +// Just counts the number of invocations. +bool SectionsCallback(const PEImage &image, + PIMAGE_SECTION_HEADER header, + PVOID section_start, + DWORD section_size, + PVOID cookie) { + int* count = reinterpret_cast<int*>(cookie); + (*count)++; + return true; +} + +// Just counts the number of invocations. +bool RelocsCallback(const PEImage &image, + WORD type, + PVOID address, + PVOID cookie) { + int* count = reinterpret_cast<int*>(cookie); + (*count)++; + return true; +} + +// Just counts the number of invocations. +bool ImportChunksCallback(const PEImage &image, + LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PVOID cookie) { + int* count = reinterpret_cast<int*>(cookie); + (*count)++; + return true; +} + +// Just counts the number of invocations. +bool DelayImportChunksCallback(const PEImage &image, + PImgDelayDescr delay_descriptor, + LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PIMAGE_THUNK_DATA bound_iat, + PIMAGE_THUNK_DATA unload_iat, + PVOID cookie) { + int* count = reinterpret_cast<int*>(cookie); + (*count)++; + return true; +} + +// Identifiers for the set of supported expectations. +enum ExpectationSet { + WIN_2K_SET, + WIN_XP_SET, + WIN_VISTA_SET, + WIN_7_SET, + WIN_8_SET, + UNSUPPORTED_SET, +}; + +// We'll be using some known values for the tests. +enum Value { + sections = 0, + imports_dlls, + delay_dlls, + exports, + imports, + delay_imports, + relocs +}; + +ExpectationSet GetExpectationSet(DWORD os) { + if (os == 50) + return WIN_2K_SET; + if (os == 51) + return WIN_XP_SET; + if (os == 60) + return WIN_VISTA_SET; + if (os == 61) + return WIN_7_SET; + if (os >= 62) + return WIN_8_SET; + return UNSUPPORTED_SET; +} + +// Retrieves the expected value from advapi32.dll based on the OS. +int GetExpectedValue(Value value, DWORD os) { + const int xp_delay_dlls = 2; + const int xp_exports = 675; + const int xp_imports = 422; + const int xp_delay_imports = 8; + const int xp_relocs = 9180; + const int vista_delay_dlls = 4; + const int vista_exports = 799; + const int vista_imports = 476; + const int vista_delay_imports = 24; + const int vista_relocs = 10188; + const int w2k_delay_dlls = 0; + const int w2k_exports = 566; + const int w2k_imports = 357; + const int w2k_delay_imports = 0; + const int w2k_relocs = 7388; + const int win7_delay_dlls = 7; + const int win7_exports = 806; + const int win7_imports = 568; + const int win7_delay_imports = 71; + const int win7_relocs = 7812; + const int win8_delay_dlls = 9; + const int win8_exports = 806; + const int win8_imports = 568; + const int win8_delay_imports = 113; + const int win8_relocs = 9478; + int win8_sections = 4; + int win8_import_dlls = 17; + + base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); + if (os_info->architecture() == base::win::OSInfo::X86_ARCHITECTURE) { + win8_sections = 5; + win8_import_dlls = 19; + } + + // Contains the expected value, for each enumerated property (Value), and the + // OS version: [Value][os_version] + const int expected[][5] = { + {4, 4, 4, 4, win8_sections}, + {3, 3, 3, 13, win8_import_dlls}, + {w2k_delay_dlls, xp_delay_dlls, vista_delay_dlls, win7_delay_dlls, + win8_delay_dlls}, + {w2k_exports, xp_exports, vista_exports, win7_exports, win8_exports}, + {w2k_imports, xp_imports, vista_imports, win7_imports, win8_imports}, + {w2k_delay_imports, xp_delay_imports, + vista_delay_imports, win7_delay_imports, win8_delay_imports}, + {w2k_relocs, xp_relocs, vista_relocs, win7_relocs, win8_relocs} + }; + COMPILE_ASSERT(arraysize(expected[0]) == UNSUPPORTED_SET, + expected_value_set_mismatch); + + if (value > relocs) + return 0; + ExpectationSet expected_set = GetExpectationSet(os); + if (expected_set >= arraysize(expected)) { + // This should never happen. Log a failure if it does. + EXPECT_NE(UNSUPPORTED_SET, expected_set); + expected_set = WIN_2K_SET; + } + + return expected[value][expected_set]; +} + +// Tests that we are able to enumerate stuff from a PE file, and that +// the actual number of items found is within the expected range. +TEST(PEImageTest, EnumeratesPE) { + HMODULE module = LoadLibrary(L"advapi32.dll"); + ASSERT_TRUE(NULL != module); + + PEImage pe(module); + int count = 0; + EXPECT_TRUE(pe.VerifyMagic()); + + DWORD os = pe.GetNTHeaders()->OptionalHeader.MajorOperatingSystemVersion; + os = os * 10 + pe.GetNTHeaders()->OptionalHeader.MinorOperatingSystemVersion; + + // Skip this test for unsupported OS versions. + if (GetExpectationSet(os) == UNSUPPORTED_SET) + return; + + pe.EnumSections(SectionsCallback, &count); + EXPECT_EQ(GetExpectedValue(sections, os), count); + + count = 0; + pe.EnumImportChunks(ImportChunksCallback, &count); + EXPECT_EQ(GetExpectedValue(imports_dlls, os), count); + + count = 0; + pe.EnumDelayImportChunks(DelayImportChunksCallback, &count); + EXPECT_EQ(GetExpectedValue(delay_dlls, os), count); + + count = 0; + pe.EnumExports(ExportsCallback, &count); + EXPECT_GT(count, GetExpectedValue(exports, os) - 20); + EXPECT_LT(count, GetExpectedValue(exports, os) + 100); + + count = 0; + pe.EnumAllImports(ImportsCallback, &count); + EXPECT_GT(count, GetExpectedValue(imports, os) - 20); + EXPECT_LT(count, GetExpectedValue(imports, os) + 100); + + count = 0; + pe.EnumAllDelayImports(ImportsCallback, &count); + EXPECT_GT(count, GetExpectedValue(delay_imports, os) - 2); + EXPECT_LT(count, GetExpectedValue(delay_imports, os) + 8); + + count = 0; + pe.EnumRelocs(RelocsCallback, &count); + EXPECT_GT(count, GetExpectedValue(relocs, os) - 150); + EXPECT_LT(count, GetExpectedValue(relocs, os) + 1500); + + FreeLibrary(module); +} + +// Tests that we can locate an specific exported symbol, by name and by ordinal. +TEST(PEImageTest, RetrievesExports) { + HMODULE module = LoadLibrary(L"advapi32.dll"); + ASSERT_TRUE(NULL != module); + + PEImage pe(module); + WORD ordinal; + + EXPECT_TRUE(pe.GetProcOrdinal("RegEnumKeyExW", &ordinal)); + + FARPROC address1 = pe.GetProcAddress("RegEnumKeyExW"); + FARPROC address2 = pe.GetProcAddress(reinterpret_cast<char*>(ordinal)); + EXPECT_TRUE(address1 != NULL); + EXPECT_TRUE(address2 != NULL); + EXPECT_TRUE(address1 == address2); + + FreeLibrary(module); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/registry.cc b/src/base/win/registry.cc new file mode 100644 index 0000000..83eb590 --- /dev/null +++ b/src/base/win/registry.cc
@@ -0,0 +1,464 @@ +// 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/registry.h" + +#include <shlwapi.h> +#include <algorithm> + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/threading/thread_restrictions.h" + +#pragma comment(lib, "shlwapi.lib") // for SHDeleteKey + +namespace base { +namespace win { + +namespace { + +// RegEnumValue() reports the number of characters from the name that were +// written to the buffer, not how many there are. This constant is the maximum +// name size, such that a buffer with this size should read any name. +const DWORD MAX_REGISTRY_NAME_SIZE = 16384; + +// Registry values are read as BYTE* but can have wchar_t* data whose last +// wchar_t is truncated. This function converts the reported |byte_size| to +// a size in wchar_t that can store a truncated wchar_t if necessary. +inline DWORD to_wchar_size(DWORD byte_size) { + return (byte_size + sizeof(wchar_t) - 1) / sizeof(wchar_t); +} + +} // namespace + +// RegKey ---------------------------------------------------------------------- + +RegKey::RegKey() + : key_(NULL), + watch_event_(0) { +} + +RegKey::RegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access) + : key_(NULL), + watch_event_(0) { + if (rootkey) { + if (access & (KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_CREATE_LINK)) + Create(rootkey, subkey, access); + else + Open(rootkey, subkey, access); + } else { + DCHECK(!subkey); + } +} + +RegKey::~RegKey() { + Close(); +} + +LONG RegKey::Create(HKEY rootkey, const wchar_t* subkey, REGSAM access) { + DWORD disposition_value; + return CreateWithDisposition(rootkey, subkey, &disposition_value, access); +} + +LONG RegKey::CreateWithDisposition(HKEY rootkey, const wchar_t* subkey, + DWORD* disposition, REGSAM access) { + DCHECK(rootkey && subkey && access && disposition); + Close(); + + LONG result = RegCreateKeyEx(rootkey, subkey, 0, NULL, + REG_OPTION_NON_VOLATILE, access, NULL, &key_, + disposition); + return result; +} + +LONG RegKey::CreateKey(const wchar_t* name, REGSAM access) { + DCHECK(name && access); + HKEY subkey = NULL; + LONG result = RegCreateKeyEx(key_, name, 0, NULL, REG_OPTION_NON_VOLATILE, + access, NULL, &subkey, NULL); + Close(); + + key_ = subkey; + return result; +} + +LONG RegKey::Open(HKEY rootkey, const wchar_t* subkey, REGSAM access) { + DCHECK(rootkey && subkey && access); + Close(); + + LONG result = RegOpenKeyEx(rootkey, subkey, 0, access, &key_); + return result; +} + +LONG RegKey::OpenKey(const wchar_t* relative_key_name, REGSAM access) { + DCHECK(relative_key_name && access); + HKEY subkey = NULL; + LONG result = RegOpenKeyEx(key_, relative_key_name, 0, access, &subkey); + + // We have to close the current opened key before replacing it with the new + // one. + Close(); + + key_ = subkey; + return result; +} + +void RegKey::Close() { + StopWatching(); + if (key_) { + ::RegCloseKey(key_); + key_ = NULL; + } +} + +bool RegKey::HasValue(const wchar_t* name) const { + return RegQueryValueEx(key_, name, 0, NULL, NULL, NULL) == ERROR_SUCCESS; +} + +DWORD RegKey::GetValueCount() const { + DWORD count = 0; + LONG result = RegQueryInfoKey(key_, NULL, 0, NULL, NULL, NULL, NULL, &count, + NULL, NULL, NULL, NULL); + return (result == ERROR_SUCCESS) ? count : 0; +} + +LONG RegKey::GetValueNameAt(int index, std::wstring* name) const { + wchar_t buf[256]; + DWORD bufsize = arraysize(buf); + LONG r = ::RegEnumValue(key_, index, buf, &bufsize, NULL, NULL, NULL, NULL); + if (r == ERROR_SUCCESS) + *name = buf; + + return r; +} + +LONG RegKey::DeleteKey(const wchar_t* name) { + DCHECK(key_); + DCHECK(name); + LONG result = SHDeleteKey(key_, name); + return result; +} + +LONG RegKey::DeleteValue(const wchar_t* value_name) { + DCHECK(key_); + LONG result = RegDeleteValue(key_, value_name); + return result; +} + +LONG RegKey::ReadValueDW(const wchar_t* name, DWORD* out_value) const { + DCHECK(out_value); + DWORD type = REG_DWORD; + DWORD size = sizeof(DWORD); + DWORD local_value = 0; + LONG result = ReadValue(name, &local_value, &size, &type); + if (result == ERROR_SUCCESS) { + if ((type == REG_DWORD || type == REG_BINARY) && size == sizeof(DWORD)) + *out_value = local_value; + else + result = ERROR_CANTREAD; + } + + return result; +} + +LONG RegKey::ReadInt64(const wchar_t* name, int64* out_value) const { + DCHECK(out_value); + DWORD type = REG_QWORD; + int64 local_value = 0; + DWORD size = sizeof(local_value); + LONG result = ReadValue(name, &local_value, &size, &type); + if (result == ERROR_SUCCESS) { + if ((type == REG_QWORD || type == REG_BINARY) && + size == sizeof(local_value)) + *out_value = local_value; + else + result = ERROR_CANTREAD; + } + + return result; +} + +LONG RegKey::ReadValue(const wchar_t* name, std::wstring* out_value) const { + DCHECK(out_value); + const size_t kMaxStringLength = 1024; // This is after expansion. + // Use the one of the other forms of ReadValue if 1024 is too small for you. + wchar_t raw_value[kMaxStringLength]; + DWORD type = REG_SZ, size = sizeof(raw_value); + LONG result = ReadValue(name, raw_value, &size, &type); + if (result == ERROR_SUCCESS) { + if (type == REG_SZ) { + *out_value = raw_value; + } else if (type == REG_EXPAND_SZ) { + wchar_t expanded[kMaxStringLength]; + size = ExpandEnvironmentStrings(raw_value, expanded, kMaxStringLength); + // Success: returns the number of wchar_t's copied + // Fail: buffer too small, returns the size required + // Fail: other, returns 0 + if (size == 0 || size > kMaxStringLength) { + result = ERROR_MORE_DATA; + } else { + *out_value = expanded; + } + } else { + // Not a string. Oops. + result = ERROR_CANTREAD; + } + } + + return result; +} + +LONG RegKey::ReadValue(const wchar_t* name, + void* data, + DWORD* dsize, + DWORD* dtype) const { + LONG result = RegQueryValueEx(key_, name, 0, dtype, + reinterpret_cast<LPBYTE>(data), dsize); + return result; +} + +LONG RegKey::ReadValues(const wchar_t* name, + std::vector<std::wstring>* values) { + values->clear(); + + DWORD type = REG_MULTI_SZ; + DWORD size = 0; + LONG result = ReadValue(name, NULL, &size, &type); + if (FAILED(result) || size == 0) + return result; + + if (type != REG_MULTI_SZ) + return ERROR_CANTREAD; + + std::vector<wchar_t> buffer(size / sizeof(wchar_t)); + result = ReadValue(name, &buffer[0], &size, NULL); + if (FAILED(result) || size == 0) + return result; + + // Parse the double-null-terminated list of strings. + // Note: This code is paranoid to not read outside of |buf|, in the case where + // it may not be properly terminated. + const wchar_t* entry = &buffer[0]; + const wchar_t* buffer_end = entry + (size / sizeof(wchar_t)); + while (entry < buffer_end && entry[0] != '\0') { + const wchar_t* entry_end = std::find(entry, buffer_end, L'\0'); + values->push_back(std::wstring(entry, entry_end)); + entry = entry_end + 1; + } + return 0; +} + +LONG RegKey::WriteValue(const wchar_t* name, DWORD in_value) { + return WriteValue( + name, &in_value, static_cast<DWORD>(sizeof(in_value)), REG_DWORD); +} + +LONG RegKey::WriteValue(const wchar_t * name, const wchar_t* in_value) { + return WriteValue(name, in_value, + static_cast<DWORD>(sizeof(*in_value) * (wcslen(in_value) + 1)), REG_SZ); +} + +LONG RegKey::WriteValue(const wchar_t* name, + const void* data, + DWORD dsize, + DWORD dtype) { + DCHECK(data || !dsize); + + LONG result = RegSetValueEx(key_, name, 0, dtype, + reinterpret_cast<LPBYTE>(const_cast<void*>(data)), dsize); + return result; +} + +LONG RegKey::StartWatching() { + DCHECK(key_); + if (!watch_event_) + watch_event_ = CreateEvent(NULL, TRUE, FALSE, NULL); + + DWORD filter = REG_NOTIFY_CHANGE_NAME | + REG_NOTIFY_CHANGE_ATTRIBUTES | + REG_NOTIFY_CHANGE_LAST_SET | + REG_NOTIFY_CHANGE_SECURITY; + + // Watch the registry key for a change of value. + LONG result = RegNotifyChangeKeyValue(key_, TRUE, filter, watch_event_, TRUE); + if (result != ERROR_SUCCESS) { + CloseHandle(watch_event_); + watch_event_ = 0; + } + + return result; +} + +bool RegKey::HasChanged() { + if (watch_event_) { + if (WaitForSingleObject(watch_event_, 0) == WAIT_OBJECT_0) { + StartWatching(); + return true; + } + } + return false; +} + +LONG RegKey::StopWatching() { + LONG result = ERROR_INVALID_HANDLE; + if (watch_event_) { + CloseHandle(watch_event_); + watch_event_ = 0; + result = ERROR_SUCCESS; + } + return result; +} + +// RegistryValueIterator ------------------------------------------------------ + +RegistryValueIterator::RegistryValueIterator(HKEY root_key, + const wchar_t* folder_key) + : name_(MAX_PATH, L'\0'), + value_(MAX_PATH, L'\0') { + LONG result = RegOpenKeyEx(root_key, folder_key, 0, KEY_READ, &key_); + if (result != ERROR_SUCCESS) { + key_ = NULL; + } else { + DWORD count = 0; + result = ::RegQueryInfoKey(key_, NULL, 0, NULL, NULL, NULL, NULL, &count, + NULL, NULL, NULL, NULL); + + if (result != ERROR_SUCCESS) { + ::RegCloseKey(key_); + key_ = NULL; + } else { + index_ = count - 1; + } + } + + Read(); +} + +RegistryValueIterator::~RegistryValueIterator() { + if (key_) + ::RegCloseKey(key_); +} + +DWORD RegistryValueIterator::ValueCount() const { + DWORD count = 0; + LONG result = ::RegQueryInfoKey(key_, NULL, 0, NULL, NULL, NULL, NULL, + &count, NULL, NULL, NULL, NULL); + if (result != ERROR_SUCCESS) + return 0; + + return count; +} + +bool RegistryValueIterator::Valid() const { + return key_ != NULL && index_ >= 0; +} + +void RegistryValueIterator::operator++() { + --index_; + Read(); +} + +bool RegistryValueIterator::Read() { + if (Valid()) { + DWORD capacity = static_cast<DWORD>(name_.capacity()); + DWORD name_size = capacity; + // |value_size_| is in bytes. Reserve the last character for a NUL. + value_size_ = static_cast<DWORD>((value_.size() - 1) * sizeof(wchar_t)); + LONG result = ::RegEnumValue( + key_, index_, WriteInto(&name_, name_size), &name_size, NULL, &type_, + reinterpret_cast<BYTE*>(vector_as_array(&value_)), &value_size_); + + if (result == ERROR_MORE_DATA) { + // Registry key names are limited to 255 characters and fit within + // MAX_PATH (which is 260) but registry value names can use up to 16,383 + // characters and the value itself is not limited + // (from http://msdn.microsoft.com/en-us/library/windows/desktop/ + // ms724872(v=vs.85).aspx). + // Resize the buffers and retry if their size caused the failure. + DWORD value_size_in_wchars = to_wchar_size(value_size_); + if (value_size_in_wchars + 1 > value_.size()) + value_.resize(value_size_in_wchars + 1, L'\0'); + value_size_ = static_cast<DWORD>((value_.size() - 1) * sizeof(wchar_t)); + name_size = name_size == capacity ? MAX_REGISTRY_NAME_SIZE : capacity; + result = ::RegEnumValue( + key_, index_, WriteInto(&name_, name_size), &name_size, NULL, &type_, + reinterpret_cast<BYTE*>(vector_as_array(&value_)), &value_size_); + } + + if (result == ERROR_SUCCESS) { + DCHECK_LT(to_wchar_size(value_size_), value_.size()); + value_[to_wchar_size(value_size_)] = L'\0'; + return true; + } + } + + name_[0] = L'\0'; + value_[0] = L'\0'; + value_size_ = 0; + return false; +} + +// RegistryKeyIterator -------------------------------------------------------- + +RegistryKeyIterator::RegistryKeyIterator(HKEY root_key, + const wchar_t* folder_key) { + LONG result = RegOpenKeyEx(root_key, folder_key, 0, KEY_READ, &key_); + if (result != ERROR_SUCCESS) { + key_ = NULL; + } else { + DWORD count = 0; + LONG result = ::RegQueryInfoKey(key_, NULL, 0, NULL, &count, NULL, NULL, + NULL, NULL, NULL, NULL, NULL); + + if (result != ERROR_SUCCESS) { + ::RegCloseKey(key_); + key_ = NULL; + } else { + index_ = count - 1; + } + } + + Read(); +} + +RegistryKeyIterator::~RegistryKeyIterator() { + if (key_) + ::RegCloseKey(key_); +} + +DWORD RegistryKeyIterator::SubkeyCount() const { + DWORD count = 0; + LONG result = ::RegQueryInfoKey(key_, NULL, 0, NULL, &count, NULL, NULL, + NULL, NULL, NULL, NULL, NULL); + if (result != ERROR_SUCCESS) + return 0; + + return count; +} + +bool RegistryKeyIterator::Valid() const { + return key_ != NULL && index_ >= 0; +} + +void RegistryKeyIterator::operator++() { + --index_; + Read(); +} + +bool RegistryKeyIterator::Read() { + if (Valid()) { + DWORD ncount = arraysize(name_); + FILETIME written; + LONG r = ::RegEnumKeyEx(key_, index_, name_, &ncount, NULL, NULL, + NULL, &written); + if (ERROR_SUCCESS == r) + return true; + } + + name_[0] = '\0'; + return false; +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/registry.h b/src/base/win/registry.h new file mode 100644 index 0000000..7a3d970 --- /dev/null +++ b/src/base/win/registry.h
@@ -0,0 +1,212 @@ +// 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_WIN_REGISTRY_H_ +#define BASE_WIN_REGISTRY_H_ + +#include <windows.h> +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/stl_util.h" + +namespace base { +namespace win { + +// Utility class to read, write and manipulate the Windows Registry. +// Registry vocabulary primer: a "key" is like a folder, in which there +// are "values", which are <name, data> pairs, with an associated data type. +// +// Note: +// ReadValue family of functions guarantee that the return arguments +// are not touched in case of failure. +class BASE_EXPORT RegKey { + public: + RegKey(); + RegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access); + ~RegKey(); + + LONG Create(HKEY rootkey, const wchar_t* subkey, REGSAM access); + + LONG CreateWithDisposition(HKEY rootkey, const wchar_t* subkey, + DWORD* disposition, REGSAM access); + + // Creates a subkey or open it if it already exists. + LONG CreateKey(const wchar_t* name, REGSAM access); + + // Opens an existing reg key. + LONG Open(HKEY rootkey, const wchar_t* subkey, REGSAM access); + + // Opens an existing reg key, given the relative key name. + LONG OpenKey(const wchar_t* relative_key_name, REGSAM access); + + // Closes this reg key. + void Close(); + + // Returns false if this key does not have the specified value, of if an error + // occurrs while attempting to access it. + bool HasValue(const wchar_t* value_name) const; + + // Returns the number of values for this key, of 0 if the number cannot be + // determined. + DWORD GetValueCount() const; + + // Determine the nth value's name. + LONG GetValueNameAt(int index, std::wstring* name) const; + + // True while the key is valid. + bool Valid() const { return key_ != NULL; } + + // Kill a key and everything that live below it; please be careful when using + // it. + LONG DeleteKey(const wchar_t* name); + + // Deletes a single value within the key. + LONG DeleteValue(const wchar_t* name); + + // Getters: + + // Returns an int32 value. If |name| is NULL or empty, returns the default + // value, if any. + LONG ReadValueDW(const wchar_t* name, DWORD* out_value) const; + + // Returns an int64 value. If |name| is NULL or empty, returns the default + // value, if any. + LONG ReadInt64(const wchar_t* name, int64* out_value) const; + + // Returns a string value. If |name| is NULL or empty, returns the default + // value, if any. + LONG ReadValue(const wchar_t* name, std::wstring* out_value) const; + + // Reads a REG_MULTI_SZ registry field into a vector of strings. Clears + // |values| initially and adds further strings to the list. Returns + // ERROR_CANTREAD if type is not REG_MULTI_SZ. + LONG ReadValues(const wchar_t* name, std::vector<std::wstring>* values); + + // Returns raw data. If |name| is NULL or empty, returns the default + // value, if any. + LONG ReadValue(const wchar_t* name, + void* data, + DWORD* dsize, + DWORD* dtype) const; + + // Setters: + + // Sets an int32 value. + LONG WriteValue(const wchar_t* name, DWORD in_value); + + // Sets a string value. + LONG WriteValue(const wchar_t* name, const wchar_t* in_value); + + // Sets raw data, including type. + LONG WriteValue(const wchar_t* name, + const void* data, + DWORD dsize, + DWORD dtype); + + // Starts watching the key to see if any of its values have changed. + // The key must have been opened with the KEY_NOTIFY access privilege. + LONG StartWatching(); + + // If StartWatching hasn't been called, always returns false. + // Otherwise, returns true if anything under the key has changed. + // This can't be const because the |watch_event_| may be refreshed. + bool HasChanged(); + + // Will automatically be called by destructor if not manually called + // beforehand. Returns true if it was watching, false otherwise. + LONG StopWatching(); + + inline bool IsWatching() const { return watch_event_ != 0; } + HANDLE watch_event() const { return watch_event_; } + HKEY Handle() const { return key_; } + + private: + HKEY key_; // The registry key being iterated. + HANDLE watch_event_; + + DISALLOW_COPY_AND_ASSIGN(RegKey); +}; + +// Iterates the entries found in a particular folder on the registry. +class BASE_EXPORT RegistryValueIterator { + public: + RegistryValueIterator(HKEY root_key, const wchar_t* folder_key); + + ~RegistryValueIterator(); + + DWORD ValueCount() const; + + // True while the iterator is valid. + bool Valid() const; + + // Advances to the next registry entry. + void operator++(); + + const wchar_t* Name() const { return name_.c_str(); } + const wchar_t* Value() const { return vector_as_array(&value_); } + // ValueSize() is in bytes. + DWORD ValueSize() const { return value_size_; } + DWORD Type() const { return type_; } + + int Index() const { return index_; } + + private: + // Read in the current values. + bool Read(); + + // The registry key being iterated. + HKEY key_; + + // Current index of the iteration. + int index_; + + // Current values. + std::wstring name_; + std::vector<wchar_t> value_; + DWORD value_size_; + DWORD type_; + + DISALLOW_COPY_AND_ASSIGN(RegistryValueIterator); +}; + +class BASE_EXPORT RegistryKeyIterator { + public: + RegistryKeyIterator(HKEY root_key, const wchar_t* folder_key); + + ~RegistryKeyIterator(); + + DWORD SubkeyCount() const; + + // True while the iterator is valid. + bool Valid() const; + + // Advances to the next entry in the folder. + void operator++(); + + const wchar_t* Name() const { return name_; } + + int Index() const { return index_; } + + private: + // Read in the current values. + bool Read(); + + // The registry key being iterated. + HKEY key_; + + // Current index of the iteration. + int index_; + + wchar_t name_[MAX_PATH]; + + DISALLOW_COPY_AND_ASSIGN(RegistryKeyIterator); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_REGISTRY_H_
diff --git a/src/base/win/registry_unittest.cc b/src/base/win/registry_unittest.cc new file mode 100644 index 0000000..155402a --- /dev/null +++ b/src/base/win/registry_unittest.cc
@@ -0,0 +1,164 @@ +// 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/win/registry.h" + +#include <cstring> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/stl_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +namespace { + +const wchar_t kRootKey[] = L"Base_Registry_Unittest"; + +class RegistryTest : public testing::Test { + public: + RegistryTest() {} + + protected: + virtual void SetUp() OVERRIDE { + // Create a temporary key. + RegKey key(HKEY_CURRENT_USER, L"", KEY_ALL_ACCESS); + key.DeleteKey(kRootKey); + ASSERT_NE(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, kRootKey, KEY_READ)); + ASSERT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, kRootKey, KEY_READ)); + } + + virtual void TearDown() OVERRIDE { + // Clean up the temporary key. + RegKey key(HKEY_CURRENT_USER, L"", KEY_SET_VALUE); + ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(kRootKey)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(RegistryTest); +}; + +TEST_F(RegistryTest, ValueTest) { + RegKey key; + + std::wstring foo_key(kRootKey); + foo_key += L"\\Foo"; + ASSERT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, foo_key.c_str(), + KEY_READ)); + + { + ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, foo_key.c_str(), + KEY_READ | KEY_SET_VALUE)); + ASSERT_TRUE(key.Valid()); + + const wchar_t kStringValueName[] = L"StringValue"; + const wchar_t kDWORDValueName[] = L"DWORDValue"; + const wchar_t kInt64ValueName[] = L"Int64Value"; + const wchar_t kStringData[] = L"string data"; + const DWORD kDWORDData = 0xdeadbabe; + const int64 kInt64Data = 0xdeadbabedeadbabeLL; + + // Test value creation + ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kStringValueName, kStringData)); + ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kDWORDValueName, kDWORDData)); + ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kInt64ValueName, &kInt64Data, + sizeof(kInt64Data), REG_QWORD)); + EXPECT_EQ(3U, key.GetValueCount()); + EXPECT_TRUE(key.HasValue(kStringValueName)); + EXPECT_TRUE(key.HasValue(kDWORDValueName)); + EXPECT_TRUE(key.HasValue(kInt64ValueName)); + + // Test Read + std::wstring string_value; + DWORD dword_value = 0; + int64 int64_value = 0; + ASSERT_EQ(ERROR_SUCCESS, key.ReadValue(kStringValueName, &string_value)); + ASSERT_EQ(ERROR_SUCCESS, key.ReadValueDW(kDWORDValueName, &dword_value)); + ASSERT_EQ(ERROR_SUCCESS, key.ReadInt64(kInt64ValueName, &int64_value)); + EXPECT_STREQ(kStringData, string_value.c_str()); + EXPECT_EQ(kDWORDData, dword_value); + EXPECT_EQ(kInt64Data, int64_value); + + // Make sure out args are not touched if ReadValue fails + const wchar_t* kNonExistent = L"NonExistent"; + ASSERT_NE(ERROR_SUCCESS, key.ReadValue(kNonExistent, &string_value)); + ASSERT_NE(ERROR_SUCCESS, key.ReadValueDW(kNonExistent, &dword_value)); + ASSERT_NE(ERROR_SUCCESS, key.ReadInt64(kNonExistent, &int64_value)); + EXPECT_STREQ(kStringData, string_value.c_str()); + EXPECT_EQ(kDWORDData, dword_value); + EXPECT_EQ(kInt64Data, int64_value); + + // Test delete + ASSERT_EQ(ERROR_SUCCESS, key.DeleteValue(kStringValueName)); + ASSERT_EQ(ERROR_SUCCESS, key.DeleteValue(kDWORDValueName)); + ASSERT_EQ(ERROR_SUCCESS, key.DeleteValue(kInt64ValueName)); + EXPECT_EQ(0U, key.GetValueCount()); + EXPECT_FALSE(key.HasValue(kStringValueName)); + EXPECT_FALSE(key.HasValue(kDWORDValueName)); + EXPECT_FALSE(key.HasValue(kInt64ValueName)); + } +} + +TEST_F(RegistryTest, BigValueIteratorTest) { + RegKey key; + std::wstring foo_key(kRootKey); + foo_key += L"\\Foo"; + ASSERT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, foo_key.c_str(), + KEY_READ)); + ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, foo_key.c_str(), + KEY_READ | KEY_SET_VALUE)); + ASSERT_TRUE(key.Valid()); + + // Create a test value that is larger than MAX_PATH. + std::wstring data(MAX_PATH * 2, L'a'); + + ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(data.c_str(), data.c_str())); + + RegistryValueIterator iterator(HKEY_CURRENT_USER, foo_key.c_str()); + ASSERT_TRUE(iterator.Valid()); + EXPECT_STREQ(data.c_str(), iterator.Name()); + EXPECT_STREQ(data.c_str(), iterator.Value()); + // ValueSize() is in bytes, including NUL. + EXPECT_EQ((MAX_PATH * 2 + 1) * sizeof(wchar_t), iterator.ValueSize()); + ++iterator; + EXPECT_FALSE(iterator.Valid()); +} + +TEST_F(RegistryTest, TruncatedCharTest) { + RegKey key; + std::wstring foo_key(kRootKey); + foo_key += L"\\Foo"; + ASSERT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, foo_key.c_str(), + KEY_READ)); + ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, foo_key.c_str(), + KEY_READ | KEY_SET_VALUE)); + ASSERT_TRUE(key.Valid()); + + const wchar_t kName[] = L"name"; + // kData size is not a multiple of sizeof(wchar_t). + const uint8 kData[] = { 1, 2, 3, 4, 5 }; + EXPECT_EQ(5, arraysize(kData)); + ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kName, kData, + arraysize(kData), REG_BINARY)); + + RegistryValueIterator iterator(HKEY_CURRENT_USER, foo_key.c_str()); + ASSERT_TRUE(iterator.Valid()); + EXPECT_STREQ(kName, iterator.Name()); + // ValueSize() is in bytes. + ASSERT_EQ(arraysize(kData), iterator.ValueSize()); + // Value() is NUL terminated. + int end = (iterator.ValueSize() + sizeof(wchar_t) - 1) / sizeof(wchar_t); + EXPECT_NE(L'\0', iterator.Value()[end-1]); + EXPECT_EQ(L'\0', iterator.Value()[end]); + EXPECT_EQ(0, std::memcmp(kData, iterator.Value(), arraysize(kData))); + ++iterator; + EXPECT_FALSE(iterator.Valid()); +} + +} // namespace + +} // namespace win +} // namespace base
diff --git a/src/base/win/resource_util.cc b/src/base/win/resource_util.cc new file mode 100644 index 0000000..de9f583 --- /dev/null +++ b/src/base/win/resource_util.cc
@@ -0,0 +1,39 @@ +// Copyright (c) 2006-2008 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/logging.h" +#include "base/win/resource_util.h" + +namespace base { +namespace win { +bool GetDataResourceFromModule(HMODULE module, int resource_id, + void** data, size_t* length) { + if (!module) + return false; + + if (!IS_INTRESOURCE(resource_id)) { + NOTREACHED(); + return false; + } + + HRSRC hres_info = FindResource(module, MAKEINTRESOURCE(resource_id), + L"BINDATA"); + if (NULL == hres_info) + return false; + + DWORD data_size = SizeofResource(module, hres_info); + HGLOBAL hres = LoadResource(module, hres_info); + if (!hres) + return false; + + void* resource = LockResource(hres); + if (!resource) + return false; + + *data = resource; + *length = static_cast<size_t>(data_size); + return true; +} +} // namespace win +} // namespace base
diff --git a/src/base/win/resource_util.h b/src/base/win/resource_util.h new file mode 100644 index 0000000..9955402 --- /dev/null +++ b/src/base/win/resource_util.h
@@ -0,0 +1,28 @@ +// 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 contains utility functions for accessing resources in external +// files (DLLs) or embedded in the executable itself. + +#ifndef BASE_WIN_RESOURCE_UTIL_H__ +#define BASE_WIN_RESOURCE_UTIL_H__ + +#include <windows.h> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace win { + +// Function for getting a data resource (BINDATA) from a dll. Some +// resources are optional, especially in unit tests, so this returns false +// but doesn't raise an error if the resource can't be loaded. +bool BASE_EXPORT GetDataResourceFromModule(HMODULE module, int resource_id, + void** data, size_t* length); + +} // namespace win +} // namespace base + +#endif // BASE_WIN_RESOURCE_UTIL_H__
diff --git a/src/base/win/sampling_profiler.cc b/src/base/win/sampling_profiler.cc new file mode 100644 index 0000000..150452c --- /dev/null +++ b/src/base/win/sampling_profiler.cc
@@ -0,0 +1,238 @@ +// 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/sampling_profiler.h" + +#include <winternl.h> // for NTSTATUS. + +#include "base/lazy_instance.h" + +// Copied from wdm.h in the WDK as we don't want to take +// a dependency on the WDK. +typedef enum _KPROFILE_SOURCE { + ProfileTime, + ProfileAlignmentFixup, + ProfileTotalIssues, + ProfilePipelineDry, + ProfileLoadInstructions, + ProfilePipelineFrozen, + ProfileBranchInstructions, + ProfileTotalNonissues, + ProfileDcacheMisses, + ProfileIcacheMisses, + ProfileCacheMisses, + ProfileBranchMispredictions, + ProfileStoreInstructions, + ProfileFpInstructions, + ProfileIntegerInstructions, + Profile2Issue, + Profile3Issue, + Profile4Issue, + ProfileSpecialInstructions, + ProfileTotalCycles, + ProfileIcacheIssues, + ProfileDcacheAccesses, + ProfileMemoryBarrierCycles, + ProfileLoadLinkedIssues, + ProfileMaximum +} KPROFILE_SOURCE; + + +namespace { + +// Signatures for the native functions we need to access the sampling profiler. +typedef NTSTATUS (NTAPI *ZwSetIntervalProfileFunc)(ULONG, KPROFILE_SOURCE); +typedef NTSTATUS (NTAPI *ZwQueryIntervalProfileFunc)(KPROFILE_SOURCE, PULONG); + +typedef NTSTATUS (NTAPI *ZwCreateProfileFunc)(PHANDLE profile, + HANDLE process, + PVOID code_start, + ULONG code_size, + ULONG eip_bucket_shift, + PULONG buckets, + ULONG buckets_byte_size, + KPROFILE_SOURCE source, + DWORD_PTR processor_mask); + +typedef NTSTATUS (NTAPI *ZwStartProfileFunc)(HANDLE); +typedef NTSTATUS (NTAPI *ZwStopProfileFunc)(HANDLE); + +// This class is used to lazy-initialize pointers to the native +// functions we need to access. +class ProfilerFuncs { + public: + ProfilerFuncs(); + + ZwSetIntervalProfileFunc ZwSetIntervalProfile; + ZwQueryIntervalProfileFunc ZwQueryIntervalProfile; + ZwCreateProfileFunc ZwCreateProfile; + ZwStartProfileFunc ZwStartProfile; + ZwStopProfileFunc ZwStopProfile; + + // True iff all of the function pointers above were successfully initialized. + bool initialized_; +}; + +ProfilerFuncs::ProfilerFuncs() + : ZwSetIntervalProfile(NULL), + ZwQueryIntervalProfile(NULL), + ZwCreateProfile(NULL), + ZwStartProfile(NULL), + ZwStopProfile(NULL), + initialized_(false) { + HMODULE ntdll = ::GetModuleHandle(L"ntdll.dll"); + if (ntdll != NULL) { + ZwSetIntervalProfile = reinterpret_cast<ZwSetIntervalProfileFunc>( + ::GetProcAddress(ntdll, "ZwSetIntervalProfile")); + ZwQueryIntervalProfile = reinterpret_cast<ZwQueryIntervalProfileFunc>( + ::GetProcAddress(ntdll, "ZwQueryIntervalProfile")); + ZwCreateProfile = reinterpret_cast<ZwCreateProfileFunc>( + ::GetProcAddress(ntdll, "ZwCreateProfile")); + ZwStartProfile = reinterpret_cast<ZwStartProfileFunc>( + ::GetProcAddress(ntdll, "ZwStartProfile")); + ZwStopProfile = reinterpret_cast<ZwStopProfileFunc>( + ::GetProcAddress(ntdll, "ZwStopProfile")); + + if (ZwSetIntervalProfile && + ZwQueryIntervalProfile && + ZwCreateProfile && + ZwStartProfile && + ZwStopProfile) { + initialized_ = true; + } + } +} + +base::LazyInstance<ProfilerFuncs>::Leaky funcs = LAZY_INSTANCE_INITIALIZER; + +} // namespace + + +namespace base { +namespace win { + +SamplingProfiler::SamplingProfiler() : is_started_(false) { +} + +SamplingProfiler::~SamplingProfiler() { + if (is_started_) { + CHECK(Stop()) << + "Unable to stop sampling profiler, this will cause memory corruption."; + } +} + +bool SamplingProfiler::Initialize(HANDLE process, + void* start, + size_t size, + size_t log2_bucket_size) { + // You only get to initialize each instance once. + DCHECK(!profile_handle_.IsValid()); + DCHECK(!is_started_); + DCHECK(start != NULL); + DCHECK_NE(0U, size); + DCHECK_LE(2, log2_bucket_size); + DCHECK_GE(32, log2_bucket_size); + + // Bail if the native functions weren't found. + if (!funcs.Get().initialized_) + return false; + + size_t bucket_size = 1 << log2_bucket_size; + size_t num_buckets = (size + bucket_size - 1) / bucket_size; + DCHECK(num_buckets != 0); + buckets_.resize(num_buckets); + + // Get our affinity mask for the call below. + DWORD_PTR process_affinity = 0; + DWORD_PTR system_affinity = 0; + if (!::GetProcessAffinityMask(process, &process_affinity, &system_affinity)) { + LOG(ERROR) << "Failed to get process affinity mask."; + return false; + } + + HANDLE profile = NULL; + NTSTATUS status = + funcs.Get().ZwCreateProfile(&profile, + process, + start, + static_cast<ULONG>(size), + static_cast<ULONG>(log2_bucket_size), + &buckets_[0], + static_cast<ULONG>( + sizeof(buckets_[0]) * num_buckets), + ProfileTime, + process_affinity); + + if (!NT_SUCCESS(status)) { + // Might as well deallocate the buckets. + buckets_.resize(0); + LOG(ERROR) << "Failed to create profile, error 0x" << std::hex << status; + return false; + } + + DCHECK(profile != NULL); + profile_handle_.Set(profile); + + return true; +} + +bool SamplingProfiler::Start() { + DCHECK(profile_handle_.IsValid()); + DCHECK(!is_started_); + DCHECK(funcs.Get().initialized_); + + NTSTATUS status = funcs.Get().ZwStartProfile(profile_handle_.Get()); + if (!NT_SUCCESS(status)) + return false; + + is_started_ = true; + + return true; +} + +bool SamplingProfiler::Stop() { + DCHECK(profile_handle_.IsValid()); + DCHECK(is_started_); + DCHECK(funcs.Get().initialized_); + + NTSTATUS status = funcs.Get().ZwStopProfile(profile_handle_.Get()); + if (!NT_SUCCESS(status)) + return false; + is_started_ = false; + + return true; +} + +bool SamplingProfiler::SetSamplingInterval(base::TimeDelta sampling_interval) { + if (!funcs.Get().initialized_) + return false; + + // According to Nebbet, the sampling interval is in units of 100ns. + ULONG interval = sampling_interval.InMicroseconds() * 10; + NTSTATUS status = funcs.Get().ZwSetIntervalProfile(interval, ProfileTime); + if (!NT_SUCCESS(status)) + return false; + + return true; +} + +bool SamplingProfiler::GetSamplingInterval(base::TimeDelta* sampling_interval) { + DCHECK(sampling_interval != NULL); + + if (!funcs.Get().initialized_) + return false; + + ULONG interval = 0; + NTSTATUS status = funcs.Get().ZwQueryIntervalProfile(ProfileTime, &interval); + if (!NT_SUCCESS(status)) + return false; + + // According to Nebbet, the sampling interval is in units of 100ns. + *sampling_interval = base::TimeDelta::FromMicroseconds(interval / 10); + + return true; +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/sampling_profiler.h b/src/base/win/sampling_profiler.h new file mode 100644 index 0000000..e7e76d8 --- /dev/null +++ b/src/base/win/sampling_profiler.h
@@ -0,0 +1,73 @@ +// 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. + +#ifndef BASE_WIN_SAMPLING_PROFILER_H_ +#define BASE_WIN_SAMPLING_PROFILER_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/time.h" +#include "base/win/scoped_handle.h" + +namespace base { +namespace win { + +// This class exposes the functionality of Window's built-in sampling profiler. +// Each profiler instance covers a range of memory, and while the profiler is +// running, its buckets will count the number of times the instruction counter +// lands in the associated range of memory on a sample. +// The sampling interval is settable, but the setting is system-wide. +class BASE_EXPORT SamplingProfiler { + public: + // Create an uninitialized sampling profiler. + SamplingProfiler(); + ~SamplingProfiler(); + + // Initializes the profiler to cover the memory range |start| through + // |start| + |size|, in the process |process_handle| with bucket size + // |2^log2_bucket_size|, |log2_bucket_size| must be in the range 2-31, + // for bucket sizes of 4 bytes to 2 gigabytes. + // The process handle must grant at least PROCESS_QUERY_INFORMATION. + // The memory range should be exectuable code, like e.g. the text segment + // of an exectuable (whether DLL or EXE). + // Returns true on success. + bool Initialize(HANDLE process_handle, + void* start, + size_t size, + size_t log2_bucket_size); + + // Start this profiler, which must be initialized and not started. + bool Start(); + // Stop this profiler, which must be started. + bool Stop(); + + // Get and set the sampling interval. + // Note that this is a system-wide setting. + static bool SetSamplingInterval(base::TimeDelta sampling_interval); + static bool GetSamplingInterval(base::TimeDelta* sampling_interval); + + // Accessors. + bool is_started() const { return is_started_; } + + // It is safe to read the counts in the sampling buckets at any time. + // Note however that there's no guarantee that you'll read consistent counts + // until the profiler has been stopped, as the counts may be updating on other + // CPU cores. + const std::vector<ULONG>& buckets() const { return buckets_; } + + private: + // Handle to the corresponding kernel object. + ScopedHandle profile_handle_; + // True iff this profiler is started. + bool is_started_; + std::vector<ULONG> buckets_; + + DISALLOW_COPY_AND_ASSIGN(SamplingProfiler); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SAMPLING_PROFILER_H_
diff --git a/src/base/win/sampling_profiler_unittest.cc b/src/base/win/sampling_profiler_unittest.cc new file mode 100644 index 0000000..d022026 --- /dev/null +++ b/src/base/win/sampling_profiler_unittest.cc
@@ -0,0 +1,120 @@ +// 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/logging.h" +#include "base/test/test_timeouts.h" +#include "base/win/sampling_profiler.h" +#include "base/win/pe_image.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "testing/gtest/include/gtest/gtest.h" + +// The address of our image base. +extern "C" IMAGE_DOS_HEADER __ImageBase; + +namespace base { +namespace win { + +namespace { + +class SamplingProfilerTest : public testing::Test { + public: + SamplingProfilerTest() : code_start(NULL), code_size(0) { + } + + virtual void SetUp() { + process.Set(::OpenProcess(PROCESS_QUERY_INFORMATION, + FALSE, + ::GetCurrentProcessId())); + ASSERT_TRUE(process.IsValid()); + + PEImage image(&__ImageBase); + + // Get the address of the .text section, which is the first section output + // by the VS tools. + ASSERT_TRUE(image.GetNumSections() > 0); + const IMAGE_SECTION_HEADER* text_section = image.GetSectionHeader(0); + ASSERT_EQ(0, strncmp(".text", + reinterpret_cast<const char*>(text_section->Name), + arraysize(text_section->Name))); + ASSERT_NE(0U, text_section->Characteristics & IMAGE_SCN_MEM_EXECUTE); + + code_start = reinterpret_cast<uint8*>(&__ImageBase) + + text_section->VirtualAddress; + code_size = text_section->Misc.VirtualSize; + } + + protected: + ScopedHandle process; + void* code_start; + size_t code_size; +}; + +} // namespace + +TEST_F(SamplingProfilerTest, Initialize) { + SamplingProfiler profiler; + + ASSERT_TRUE(profiler.Initialize(process.Get(), code_start, code_size, 8)); +} + +TEST_F(SamplingProfilerTest, Sample) { + if (base::win::GetVersion() == base::win::VERSION_WIN8) { + LOG(INFO) << "Not running test on Windows 8"; + return; + } + SamplingProfiler profiler; + + // Initialize with a huge bucket size, aiming for a single bucket. + ASSERT_TRUE( + profiler.Initialize(process.Get(), code_start, code_size, 31)); + + ASSERT_EQ(1, profiler.buckets().size()); + ASSERT_EQ(0, profiler.buckets()[0]); + + // We use a roomy timeout to make sure this test is not flaky. + // On the buildbots, there may not be a whole lot of CPU time + // allotted to our process in this wall-clock time duration, + // and samples will only accrue while this thread is busy on + // a CPU core. + base::TimeDelta spin_time = TestTimeouts::action_timeout(); + + base::TimeDelta save_sampling_interval; + ASSERT_TRUE(SamplingProfiler::GetSamplingInterval(&save_sampling_interval)); + + // Sample every 0.5 millisecs. + ASSERT_TRUE(SamplingProfiler::SetSamplingInterval( + base::TimeDelta::FromMicroseconds(500))); + + ASSERT_TRUE(SamplingProfiler::SetSamplingInterval( + base::TimeDelta::FromMicroseconds(500))); + + // Start the profiler. + ASSERT_TRUE(profiler.Start()); + + // Get a volatile pointer to our bucket to make sure that the compiler + // doesn't optimize out the test in the loop that follows. + volatile const ULONG* bucket_ptr = &profiler.buckets()[0]; + + // Spin for spin_time wall-clock seconds, or until we get some samples. + // Note that sleeping isn't going to do us any good, the samples only + // accrue while we're executing code. + base::Time start = base::Time::Now(); + base::TimeDelta elapsed; + do { + elapsed = base::Time::Now() - start; + } while((elapsed < spin_time) && *bucket_ptr == 0); + + // Stop the profiler. + ASSERT_TRUE(profiler.Stop()); + + // Restore the sampling interval we found. + ASSERT_TRUE(SamplingProfiler::SetSamplingInterval(save_sampling_interval)); + + // Check that we got some samples. + ASSERT_NE(0U, profiler.buckets()[0]); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/scoped_bstr.cc b/src/base/win/scoped_bstr.cc new file mode 100644 index 0000000..63ade0c --- /dev/null +++ b/src/base/win/scoped_bstr.cc
@@ -0,0 +1,71 @@ +// Copyright (c) 2010 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_bstr.h" + +#include "base/logging.h" + +namespace base { +namespace win { + +ScopedBstr::ScopedBstr(const char16* non_bstr) + : bstr_(SysAllocString(non_bstr)) { +} + +ScopedBstr::~ScopedBstr() { + COMPILE_ASSERT(sizeof(ScopedBstr) == sizeof(BSTR), ScopedBstrSize); + SysFreeString(bstr_); +} + +void ScopedBstr::Reset(BSTR bstr) { + if (bstr != bstr_) { + // if |bstr_| is NULL, SysFreeString does nothing. + SysFreeString(bstr_); + bstr_ = bstr; + } +} + +BSTR ScopedBstr::Release() { + BSTR bstr = bstr_; + bstr_ = NULL; + return bstr; +} + +void ScopedBstr::Swap(ScopedBstr& bstr2) { + BSTR tmp = bstr_; + bstr_ = bstr2.bstr_; + bstr2.bstr_ = tmp; +} + +BSTR* ScopedBstr::Receive() { + DCHECK(!bstr_) << "BSTR leak."; + return &bstr_; +} + +BSTR ScopedBstr::Allocate(const char16* str) { + Reset(SysAllocString(str)); + return bstr_; +} + +BSTR ScopedBstr::AllocateBytes(size_t bytes) { + Reset(SysAllocStringByteLen(NULL, static_cast<UINT>(bytes))); + return bstr_; +} + +void ScopedBstr::SetByteLen(size_t bytes) { + DCHECK(bstr_ != NULL) << "attempting to modify a NULL bstr"; + uint32* data = reinterpret_cast<uint32*>(bstr_); + data[-1] = static_cast<uint32>(bytes); +} + +size_t ScopedBstr::Length() const { + return SysStringLen(bstr_); +} + +size_t ScopedBstr::ByteLength() const { + return SysStringByteLen(bstr_); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/scoped_bstr.h b/src/base/win/scoped_bstr.h new file mode 100644 index 0000000..ed46d63 --- /dev/null +++ b/src/base/win/scoped_bstr.h
@@ -0,0 +1,97 @@ +// 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. + +#ifndef BASE_WIN_SCOPED_BSTR_H_ +#define BASE_WIN_SCOPED_BSTR_H_ + +#include <windows.h> +#include <oleauto.h> + +#include "base/base_export.h" +#include "base/logging.h" +#include "base/string16.h" + +namespace base { +namespace win { + +// Manages a BSTR string pointer. +// The class interface is based on scoped_ptr. +class BASE_EXPORT ScopedBstr { + public: + ScopedBstr() : bstr_(NULL) { + } + + // Constructor to create a new BSTR. + // + // NOTE: Do not pass a BSTR to this constructor expecting ownership to + // be transferred - even though it compiles! ;-) + explicit ScopedBstr(const char16* non_bstr); + ~ScopedBstr(); + + // Give ScopedBstr ownership over an already allocated BSTR or NULL. + // If you need to allocate a new BSTR instance, use |allocate| instead. + void Reset(BSTR bstr = NULL); + + // Releases ownership of the BSTR to the caller. + BSTR Release(); + + // Creates a new BSTR from a 16-bit C-style string. + // + // If you already have a BSTR and want to transfer ownership to the + // ScopedBstr instance, call |reset| instead. + // + // Returns a pointer to the new BSTR, or NULL if allocation failed. + BSTR Allocate(const char16* str); + + // Allocates a new BSTR with the specified number of bytes. + // Returns a pointer to the new BSTR, or NULL if allocation failed. + BSTR AllocateBytes(size_t bytes); + + // Sets the allocated length field of the already-allocated BSTR to be + // |bytes|. This is useful when the BSTR was preallocated with e.g. + // SysAllocStringLen or SysAllocStringByteLen (call |AllocateBytes|) and then + // not all the bytes are being used. + // + // Note that if you want to set the length to a specific number of + // characters, you need to multiply by sizeof(wchar_t). Oddly, there's no + // public API to set the length, so we do this ourselves by hand. + // + // NOTE: The actual allocated size of the BSTR MUST be >= bytes. That + // responsibility is with the caller. + void SetByteLen(size_t bytes); + + // Swap values of two ScopedBstr's. + void Swap(ScopedBstr& bstr2); + + // Retrieves the pointer address. + // Used to receive BSTRs as out arguments (and take ownership). + // The function DCHECKs on the current value being NULL. + // Usage: GetBstr(bstr.Receive()); + BSTR* Receive(); + + // Returns number of chars in the BSTR. + size_t Length() const; + + // Returns the number of bytes allocated for the BSTR. + size_t ByteLength() const; + + operator BSTR() const { + return bstr_; + } + + protected: + BSTR bstr_; + + private: + // Forbid comparison of ScopedBstr types. You should never have the same + // BSTR owned by two different scoped_ptrs. + bool operator==(const ScopedBstr& bstr2) const; + bool operator!=(const ScopedBstr& bstr2) const; + DISALLOW_COPY_AND_ASSIGN(ScopedBstr); +}; + +} // namespace win +} // namespace base + +#endif // BASE_SCOPED_BSTR_H_
diff --git a/src/base/win/scoped_bstr_unittest.cc b/src/base/win/scoped_bstr_unittest.cc new file mode 100644 index 0000000..5f6f7df --- /dev/null +++ b/src/base/win/scoped_bstr_unittest.cc
@@ -0,0 +1,77 @@ +// Copyright (c) 2010 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_bstr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +namespace { + +static const wchar_t kTestString1[] = L"123"; +static const wchar_t kTestString2[] = L"456789"; +size_t test1_len = arraysize(kTestString1) - 1; +size_t test2_len = arraysize(kTestString2) - 1; + +void DumbBstrTests() { + ScopedBstr b; + EXPECT_TRUE(b == NULL); + EXPECT_EQ(0, b.Length()); + EXPECT_EQ(0, b.ByteLength()); + b.Reset(NULL); + EXPECT_TRUE(b == NULL); + EXPECT_TRUE(b.Release() == NULL); + ScopedBstr b2; + b.Swap(b2); + EXPECT_TRUE(b2 == NULL); +} + +void GiveMeABstr(BSTR* ret) { + *ret = SysAllocString(kTestString1); +} + +void BasicBstrTests() { + ScopedBstr b1(kTestString1); + EXPECT_EQ(test1_len, b1.Length()); + EXPECT_EQ(test1_len * sizeof(kTestString1[0]), b1.ByteLength()); + + ScopedBstr b2; + b1.Swap(b2); + EXPECT_EQ(test1_len, b2.Length()); + EXPECT_EQ(0, b1.Length()); + EXPECT_EQ(0, lstrcmp(b2, kTestString1)); + BSTR tmp = b2.Release(); + EXPECT_TRUE(tmp != NULL); + EXPECT_EQ(0, lstrcmp(tmp, kTestString1)); + EXPECT_TRUE(b2 == NULL); + SysFreeString(tmp); + + GiveMeABstr(b2.Receive()); + EXPECT_TRUE(b2 != NULL); + b2.Reset(); + EXPECT_TRUE(b2.AllocateBytes(100) != NULL); + EXPECT_EQ(100, b2.ByteLength()); + EXPECT_EQ(100 / sizeof(kTestString1[0]), b2.Length()); + lstrcpy(static_cast<BSTR>(b2), kTestString1); + EXPECT_EQ(test1_len, lstrlen(b2)); + EXPECT_EQ(100 / sizeof(kTestString1[0]), b2.Length()); + b2.SetByteLen(lstrlen(b2) * sizeof(kTestString2[0])); + EXPECT_EQ(b2.Length(), lstrlen(b2)); + + EXPECT_TRUE(b1.Allocate(kTestString2) != NULL); + EXPECT_EQ(test2_len, b1.Length()); + b1.SetByteLen((test2_len - 1) * sizeof(kTestString2[0])); + EXPECT_EQ(test2_len - 1, b1.Length()); +} + +} // namespace + +TEST(ScopedBstrTest, ScopedBstr) { + DumbBstrTests(); + BasicBstrTests(); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/scoped_co_mem.h b/src/base/win/scoped_co_mem.h new file mode 100644 index 0000000..572999a --- /dev/null +++ b/src/base/win/scoped_co_mem.h
@@ -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. + +#ifndef BASE_WIN_SCOPED_CO_MEM_H_ +#define BASE_WIN_SCOPED_CO_MEM_H_ + +#include <objbase.h> + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace base { +namespace win { + +// Simple scoped memory releaser class for COM allocated memory. +// Example: +// base::win::ScopedCoMem<ITEMIDLIST> file_item; +// SHGetSomeInfo(&file_item, ...); +// ... +// return; <-- memory released +template<typename T> +class ScopedCoMem { + public: + ScopedCoMem() : mem_ptr_(NULL) {} + ~ScopedCoMem() { + Reset(NULL); + } + + T** operator&() { // NOLINT + DCHECK(mem_ptr_ == NULL); // To catch memory leaks. + return &mem_ptr_; + } + + operator T*() { + return mem_ptr_; + } + + T* operator->() { + DCHECK(mem_ptr_ != NULL); + return mem_ptr_; + } + + const T* operator->() const { + DCHECK(mem_ptr_ != NULL); + return mem_ptr_; + } + + void Reset(T* ptr) { + if (mem_ptr_) + CoTaskMemFree(mem_ptr_); + mem_ptr_ = ptr; + } + + private: + T* mem_ptr_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCoMem); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_CO_MEM_H_
diff --git a/src/base/win/scoped_com_initializer.h b/src/base/win/scoped_com_initializer.h new file mode 100644 index 0000000..392c351 --- /dev/null +++ b/src/base/win/scoped_com_initializer.h
@@ -0,0 +1,74 @@ +// 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_WIN_SCOPED_COM_INITIALIZER_H_ +#define BASE_WIN_SCOPED_COM_INITIALIZER_H_ + +#include <objbase.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "build/build_config.h" + +namespace base { +namespace win { + +// Initializes COM in the constructor (STA or MTA), and uninitializes COM in the +// destructor. +class ScopedCOMInitializer { + public: + // Enum value provided to initialize the thread as an MTA instead of STA. + enum SelectMTA { kMTA }; + + // Constructor for STA initialization. + ScopedCOMInitializer() { + Initialize(COINIT_APARTMENTTHREADED); + } + + // Constructor for MTA initialization. + explicit ScopedCOMInitializer(SelectMTA mta) { + Initialize(COINIT_MULTITHREADED); + } + + ~ScopedCOMInitializer() { +#ifndef NDEBUG + // Using the windows API directly to avoid dependency on platform_thread. + DCHECK_EQ(GetCurrentThreadId(), thread_id_); +#endif + if (succeeded()) + CoUninitialize(); + } + + bool succeeded() const { return SUCCEEDED(hr_); } + + private: + void Initialize(COINIT init) { +#ifndef NDEBUG + thread_id_ = GetCurrentThreadId(); +#endif + hr_ = CoInitializeEx(NULL, init); +#ifndef NDEBUG + if (hr_ == S_FALSE) + LOG(ERROR) << "Multiple CoInitialize() calls for thread " << thread_id_; + else + DCHECK_NE(RPC_E_CHANGED_MODE, hr_) << "Invalid COM thread model change"; +#endif + } + + HRESULT hr_; +#ifndef NDEBUG + // In debug builds we use this variable to catch a potential bug where a + // ScopedCOMInitializer instance is deleted on a different thread than it + // was initially created on. If that ever happens it can have bad + // consequences and the cause can be tricky to track down. + DWORD thread_id_; +#endif + + DISALLOW_COPY_AND_ASSIGN(ScopedCOMInitializer); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_COM_INITIALIZER_H_
diff --git a/src/base/win/scoped_comptr.h b/src/base/win/scoped_comptr.h new file mode 100644 index 0000000..9d5301f --- /dev/null +++ b/src/base/win/scoped_comptr.h
@@ -0,0 +1,168 @@ +// 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. + +#ifndef BASE_WIN_SCOPED_COMPTR_H_ +#define BASE_WIN_SCOPED_COMPTR_H_ + +#include <unknwn.h> + +#include "base/logging.h" +#include "base/memory/ref_counted.h" + +namespace base { +namespace win { + +// A fairly minimalistic smart class for COM interface pointers. +// Uses scoped_refptr for the basic smart pointer functionality +// and adds a few IUnknown specific services. +template <class Interface, const IID* interface_id = &__uuidof(Interface)> +class ScopedComPtr : public scoped_refptr<Interface> { + public: + // Utility template to prevent users of ScopedComPtr from calling AddRef + // and/or Release() without going through the ScopedComPtr class. + class BlockIUnknownMethods : public Interface { + private: + STDMETHOD(QueryInterface)(REFIID iid, void** object) = 0; + STDMETHOD_(ULONG, AddRef)() = 0; + STDMETHOD_(ULONG, Release)() = 0; + }; + + typedef scoped_refptr<Interface> ParentClass; + + ScopedComPtr() { + } + + explicit ScopedComPtr(Interface* p) : ParentClass(p) { + } + + ScopedComPtr(const ScopedComPtr<Interface, interface_id>& p) + : ParentClass(p) { + } + + ~ScopedComPtr() { + // We don't want the smart pointer class to be bigger than the pointer + // it wraps. + COMPILE_ASSERT(sizeof(ScopedComPtr<Interface, interface_id>) == + sizeof(Interface*), ScopedComPtrSize); + } + + // Explicit Release() of the held object. Useful for reuse of the + // ScopedComPtr instance. + // Note that this function equates to IUnknown::Release and should not + // be confused with e.g. scoped_ptr::release(). + void Release() { + if (ptr_ != NULL) { + ptr_->Release(); + ptr_ = NULL; + } + } + + // Sets the internal pointer to NULL and returns the held object without + // releasing the reference. + Interface* Detach() { + Interface* p = ptr_; + ptr_ = NULL; + return p; + } + + // Accepts an interface pointer that has already been addref-ed. + void Attach(Interface* p) { + DCHECK(!ptr_); + ptr_ = p; + } + + // Retrieves the pointer address. + // Used to receive object pointers as out arguments (and take ownership). + // The function DCHECKs on the current value being NULL. + // Usage: Foo(p.Receive()); + Interface** Receive() { + DCHECK(!ptr_) << "Object leak. Pointer must be NULL"; + return &ptr_; + } + + // A convenience for whenever a void pointer is needed as an out argument. + void** ReceiveVoid() { + return reinterpret_cast<void**>(Receive()); + } + + template <class Query> + HRESULT QueryInterface(Query** p) { + DCHECK(p != NULL); + DCHECK(ptr_ != NULL); + // IUnknown already has a template version of QueryInterface + // so the iid parameter is implicit here. The only thing this + // function adds are the DCHECKs. + return ptr_->QueryInterface(p); + } + + // QI for times when the IID is not associated with the type. + HRESULT QueryInterface(const IID& iid, void** obj) { + DCHECK(obj != NULL); + DCHECK(ptr_ != NULL); + return ptr_->QueryInterface(iid, obj); + } + + // Queries |other| for the interface this object wraps and returns the + // error code from the other->QueryInterface operation. + HRESULT QueryFrom(IUnknown* object) { + DCHECK(object != NULL); + return object->QueryInterface(Receive()); + } + + // Convenience wrapper around CoCreateInstance + HRESULT CreateInstance(const CLSID& clsid, IUnknown* outer = NULL, + DWORD context = CLSCTX_ALL) { + DCHECK(!ptr_); + HRESULT hr = ::CoCreateInstance(clsid, outer, context, *interface_id, + reinterpret_cast<void**>(&ptr_)); + return hr; + } + + // Checks if the identity of |other| and this object is the same. + bool IsSameObject(IUnknown* other) { + if (!other && !ptr_) + return true; + + if (!other || !ptr_) + return false; + + ScopedComPtr<IUnknown> my_identity; + QueryInterface(my_identity.Receive()); + + ScopedComPtr<IUnknown> other_identity; + other->QueryInterface(other_identity.Receive()); + + return static_cast<IUnknown*>(my_identity) == + static_cast<IUnknown*>(other_identity); + } + + // Provides direct access to the interface. + // Here we use a well known trick to make sure we block access to + // IUknown methods so that something bad like this doesn't happen: + // ScopedComPtr<IUnknown> p(Foo()); + // p->Release(); + // ... later the destructor runs, which will Release() again. + // and to get the benefit of the DCHECKs we add to QueryInterface. + // There's still a way to call these methods if you absolutely must + // by statically casting the ScopedComPtr instance to the wrapped interface + // and then making the call... but generally that shouldn't be necessary. + BlockIUnknownMethods* operator->() const { + DCHECK(ptr_ != NULL); + return reinterpret_cast<BlockIUnknownMethods*>(ptr_); + } + + // Pull in operator=() from the parent class. + using scoped_refptr<Interface>::operator=; + + // static methods + + static const IID& iid() { + return *interface_id; + } +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_COMPTR_H_
diff --git a/src/base/win/scoped_comptr_unittest.cc b/src/base/win/scoped_comptr_unittest.cc new file mode 100644 index 0000000..d8d12be --- /dev/null +++ b/src/base/win/scoped_comptr_unittest.cc
@@ -0,0 +1,111 @@ +// 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/win/scoped_comptr.h" + +#include <shlobj.h> + +#include "base/memory/scoped_ptr.h" +#include "base/win/scoped_com_initializer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +namespace { + +struct Dummy { + Dummy() : adds(0), releases(0) { } + void AddRef() { ++adds; } + void Release() { ++releases; } + + int adds; + int releases; +}; + +extern const IID dummy_iid; +const IID dummy_iid = { 0x12345678u, 0x1234u, 0x5678u, 01, 23, 45, 67, 89, + 01, 23, 45 }; + +} // namespace + +TEST(ScopedComPtrTest, ScopedComPtr) { + EXPECT_TRUE(memcmp(&ScopedComPtr<IUnknown>::iid(), &IID_IUnknown, + sizeof(IID)) == 0); + + base::win::ScopedCOMInitializer com_initializer; + EXPECT_TRUE(com_initializer.succeeded()); + + ScopedComPtr<IUnknown> unk; + EXPECT_TRUE(SUCCEEDED(unk.CreateInstance(CLSID_ShellLink))); + ScopedComPtr<IUnknown> unk2; + unk2.Attach(unk.Detach()); + EXPECT_TRUE(unk == NULL); + EXPECT_TRUE(unk2 != NULL); + + ScopedComPtr<IMalloc> mem_alloc; + EXPECT_TRUE(SUCCEEDED(CoGetMalloc(1, mem_alloc.Receive()))); + + ScopedComPtr<IUnknown> qi_test; + EXPECT_HRESULT_SUCCEEDED(mem_alloc.QueryInterface(IID_IUnknown, + reinterpret_cast<void**>(qi_test.Receive()))); + EXPECT_TRUE(qi_test.get() != NULL); + qi_test.Release(); + + // test ScopedComPtr& constructor + ScopedComPtr<IMalloc> copy1(mem_alloc); + EXPECT_TRUE(copy1.IsSameObject(mem_alloc)); + EXPECT_FALSE(copy1.IsSameObject(unk2)); // unk2 is valid but different + EXPECT_FALSE(copy1.IsSameObject(unk)); // unk is NULL + + IMalloc* naked_copy = copy1.Detach(); + copy1 = naked_copy; // Test the =(T*) operator. + naked_copy->Release(); + + copy1.Release(); + EXPECT_FALSE(copy1.IsSameObject(unk2)); // unk2 is valid, copy1 is not + + // test Interface* constructor + ScopedComPtr<IMalloc> copy2(static_cast<IMalloc*>(mem_alloc)); + EXPECT_TRUE(copy2.IsSameObject(mem_alloc)); + + EXPECT_TRUE(SUCCEEDED(unk.QueryFrom(mem_alloc))); + EXPECT_TRUE(unk != NULL); + unk.Release(); + EXPECT_TRUE(unk == NULL); + EXPECT_TRUE(unk.IsSameObject(copy1)); // both are NULL +} + +TEST(ScopedComPtrTest, ScopedComPtrVector) { + // Verify we don't get error C2558. + typedef ScopedComPtr<Dummy, &dummy_iid> Ptr; + std::vector<Ptr> bleh; + + scoped_ptr<Dummy> p(new Dummy); + { + Ptr p2(p.get()); + EXPECT_EQ(p->adds, 1); + EXPECT_EQ(p->releases, 0); + Ptr p3 = p2; + EXPECT_EQ(p->adds, 2); + EXPECT_EQ(p->releases, 0); + p3 = p2; + EXPECT_EQ(p->adds, 3); + EXPECT_EQ(p->releases, 1); + // To avoid hitting a reallocation. + bleh.reserve(1); + bleh.push_back(p2); + EXPECT_EQ(p->adds, 4); + EXPECT_EQ(p->releases, 1); + EXPECT_EQ(bleh[0], p.get()); + bleh.pop_back(); + EXPECT_EQ(p->adds, 4); + EXPECT_EQ(p->releases, 2); + } + EXPECT_EQ(p->adds, 4); + EXPECT_EQ(p->releases, 4); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/scoped_gdi_object.h b/src/base/win/scoped_gdi_object.h new file mode 100644 index 0000000..d44310a --- /dev/null +++ b/src/base/win/scoped_gdi_object.h
@@ -0,0 +1,77 @@ +// Copyright (c) 2010 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_WIN_SCOPED_GDI_OBJECT_H_ +#define BASE_WIN_SCOPED_GDI_OBJECT_H_ + +#include <windows.h> + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace base { +namespace win { + +// Like ScopedHandle but for GDI objects. +template<class T> +class ScopedGDIObject { + public: + ScopedGDIObject() : object_(NULL) {} + explicit ScopedGDIObject(T object) : object_(object) {} + + ~ScopedGDIObject() { + Close(); + } + + T Get() { + return object_; + } + + void Set(T object) { + if (object_ && object != object_) + Close(); + object_ = object; + } + + ScopedGDIObject& operator=(T object) { + Set(object); + return *this; + } + + T release() { + T object = object_; + object_ = NULL; + return object; + } + + operator T() { return object_; } + + private: + void Close() { + if (object_) + DeleteObject(object_); + } + + T object_; + DISALLOW_COPY_AND_ASSIGN(ScopedGDIObject); +}; + +// An explicit specialization for HICON because we have to call DestroyIcon() +// instead of DeleteObject() for HICON. +template<> +void ScopedGDIObject<HICON>::Close() { + if (object_) + DestroyIcon(object_); +} + +// Typedefs for some common use cases. +typedef ScopedGDIObject<HBITMAP> ScopedBitmap; +typedef ScopedGDIObject<HRGN> ScopedRegion; +typedef ScopedGDIObject<HFONT> ScopedHFONT; +typedef ScopedGDIObject<HICON> ScopedHICON; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_GDI_OBJECT_H_
diff --git a/src/base/win/scoped_handle.cc b/src/base/win/scoped_handle.cc new file mode 100644 index 0000000..03d026a --- /dev/null +++ b/src/base/win/scoped_handle.cc
@@ -0,0 +1,83 @@ +// 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 <map> +#include <set> + +#include "base/debug/alias.h" +#include "base/lazy_instance.h" +#include "base/synchronization/lock.h" +#include "base/win/windows_version.h" + +namespace { + +struct Info { + const void* owner; + const void* pc1; + const void* pc2; + DWORD thread_id; +}; +typedef std::map<HANDLE, Info> HandleMap; + +base::LazyInstance<HandleMap>::Leaky g_handle_map = LAZY_INSTANCE_INITIALIZER; +base::LazyInstance<std::set<const void*> >::Leaky g_owner_set = + LAZY_INSTANCE_INITIALIZER; +base::LazyInstance<base::Lock>::Leaky g_lock = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace base { +namespace win { + +// Static. +void VerifierTraits::StartTracking(HANDLE handle, const void* owner, + const void* pc1, const void* pc2) { + // Grab the thread id before the lock. + DWORD thread_id = GetCurrentThreadId(); + + AutoLock lock(g_lock.Get()); + + if (handle == INVALID_HANDLE_VALUE) { + // Cannot track this handle. + g_owner_set.Get().insert(owner); + return; + } + + Info handle_info = { owner, pc1, pc2, thread_id }; + std::pair<HANDLE, Info> item(handle, handle_info); + std::pair<HandleMap::iterator, bool> result = g_handle_map.Get().insert(item); + if (!result.second) { + Info other = result.first->second; + debug::Alias(&other); + CHECK(false); + } +} + +// Static. +void VerifierTraits::StopTracking(HANDLE handle, const void* owner, + const void* pc1, const void* pc2) { + AutoLock lock(g_lock.Get()); + HandleMap::iterator i = g_handle_map.Get().find(handle); + if (i == g_handle_map.Get().end()) { + std::set<const void*>::iterator j = g_owner_set.Get().find(owner); + if (j != g_owner_set.Get().end()) { + g_owner_set.Get().erase(j); + return; + } + CHECK(false); + } + + Info other = i->second; + if (other.owner != owner) { + debug::Alias(&other); + CHECK(false); + } + + g_handle_map.Get().erase(i); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/scoped_handle.h b/src/base/win/scoped_handle.h new file mode 100644 index 0000000..4fe3592 --- /dev/null +++ b/src/base/win/scoped_handle.h
@@ -0,0 +1,188 @@ +// 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_WIN_SCOPED_HANDLE_H_ +#define BASE_WIN_SCOPED_HANDLE_H_ + +#include <windows.h> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/location.h" +#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(); +} +#define BASE_WIN_GET_CALLER _ReturnAddress() +#elif defined(COMPILER_GCC) +#define BASE_WIN_GET_CALLER __builtin_extract_return_addr(\\ + __builtin_return_address(0)) +#endif + +// 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: +// - IsValid() method can tolerate multiple invalid handle values such as NULL +// and INVALID_HANDLE_VALUE (-1) for Win32 handles. +// - Receive() method allows to receive a handle value from a function that +// takes a raw handle pointer only. +template <class Traits, class Verifier> +class GenericScopedHandle { + MOVE_ONLY_TYPE_FOR_CPP_03(GenericScopedHandle, RValue) + + public: + typedef typename Traits::Handle Handle; + + GenericScopedHandle() : handle_(Traits::NullHandle()) {} + + explicit GenericScopedHandle(Handle handle) : handle_(Traits::NullHandle()) { + Set(handle); + } + + // Move constructor for C++03 move emulation of this type. + GenericScopedHandle(RValue other) : handle_(Traits::NullHandle()) { + Set(other.object->Take()); + } + + ~GenericScopedHandle() { + Close(); + } + + bool IsValid() const { + return Traits::IsHandleValid(handle_); + } + + // Move operator= for C++03 move emulation of this type. + GenericScopedHandle& operator=(RValue other) { + if (this != other.object) { + Set(other.object->Take()); + } + return *this; + } + + void Set(Handle handle) { + if (handle_ != handle) { + Close(); + + if (Traits::IsHandleValid(handle)) { + handle_ = handle; + Verifier::StartTracking(handle, this, BASE_WIN_GET_CALLER, + tracked_objects::GetProgramCounter()); + } + } + } + + Handle Get() const { + return handle_; + } + + operator Handle() const { + return handle_; + } + + Handle* Receive() { + DCHECK(!Traits::IsHandleValid(handle_)) << "Handle must be NULL"; + + // We cannot track this case :(. Just tell the verifier about it. + Verifier::StartTracking(INVALID_HANDLE_VALUE, this, BASE_WIN_GET_CALLER, + tracked_objects::GetProgramCounter()); + return &handle_; + } + + // Transfers ownership away from this object. + Handle Take() { + Handle temp = handle_; + handle_ = Traits::NullHandle(); + if (Traits::IsHandleValid(temp)) { + Verifier::StopTracking(temp, this, BASE_WIN_GET_CALLER, + tracked_objects::GetProgramCounter()); + } + return temp; + } + + // Explicitly closes the owned handle. + void Close() { + if (Traits::IsHandleValid(handle_)) { + Verifier::StopTracking(handle_, this, BASE_WIN_GET_CALLER, + tracked_objects::GetProgramCounter()); + + if (!Traits::CloseHandle(handle_)) + CHECK(false); + + handle_ = Traits::NullHandle(); + } + } + + private: + Handle handle_; +}; + +#undef BASE_WIN_GET_CALLER + +// The traits class for Win32 handles that can be closed via CloseHandle() API. +class HandleTraits { + public: + typedef HANDLE Handle; + + // Closes the handle. + static bool CloseHandle(HANDLE handle) { + return ::CloseHandle(handle) != FALSE; + } + + // Returns true if the handle value is valid. + static bool IsHandleValid(HANDLE handle) { + return handle != NULL && handle != INVALID_HANDLE_VALUE; + } + + // Returns NULL handle value. + static HANDLE NullHandle() { + return NULL; + } + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(HandleTraits); +}; + +// Do-nothing verifier. +class DummyVerifierTraits { + public: + typedef HANDLE Handle; + + static void StartTracking(HANDLE handle, const void* owner, + const void* pc1, const void* pc2) {} + static void StopTracking(HANDLE handle, const void* owner, + const void* pc1, const void* pc2) {} + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(DummyVerifierTraits); +}; + +// Performs actual run-time tracking. +class BASE_EXPORT VerifierTraits { + public: + typedef HANDLE Handle; + + static void StartTracking(HANDLE handle, const void* owner, + const void* pc1, const void* pc2); + static void StopTracking(HANDLE handle, const void* owner, + const void* pc1, const void* pc2); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(VerifierTraits); +}; + +typedef GenericScopedHandle<HandleTraits, DummyVerifierTraits> ScopedHandle; + +} // namespace win +} // namespace base + +#endif // BASE_SCOPED_HANDLE_WIN_H_
diff --git a/src/base/win/scoped_hdc.h b/src/base/win/scoped_hdc.h new file mode 100644 index 0000000..9aead96 --- /dev/null +++ b/src/base/win/scoped_hdc.h
@@ -0,0 +1,76 @@ +// 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_WIN_SCOPED_HDC_H_ +#define BASE_WIN_SCOPED_HDC_H_ + +#include <windows.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/win/scoped_handle.h" + +namespace base { +namespace win { + +// Like ScopedHandle but for HDC. Only use this on HDCs returned from +// GetDC. +class ScopedGetDC { + public: + explicit ScopedGetDC(HWND hwnd) + : hwnd_(hwnd), + hdc_(GetDC(hwnd)) { + if (hwnd_) { + DCHECK(IsWindow(hwnd_)); + DCHECK(hdc_); + } else { + // If GetDC(NULL) returns NULL, something really bad has happened, like + // GDI handle exhaustion. In this case Chrome is going to behave badly no + // matter what, so we may as well just force a crash now. + CHECK(hdc_); + } + } + + ~ScopedGetDC() { + if (hdc_) + ReleaseDC(hwnd_, hdc_); + } + + operator HDC() { return hdc_; } + + private: + HWND hwnd_; + HDC hdc_; + + DISALLOW_COPY_AND_ASSIGN(ScopedGetDC); +}; + +// Like ScopedHandle but for HDC. Only use this on HDCs returned from +// CreateCompatibleDC, CreateDC and CreateIC. +class CreateDCTraits { + public: + typedef HDC Handle; + + static bool CloseHandle(HDC handle) { + return ::DeleteDC(handle) != FALSE; + } + + static bool IsHandleValid(HDC handle) { + return handle != NULL; + } + + static HDC NullHandle() { + return NULL; + } + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(CreateDCTraits); +}; + +typedef GenericScopedHandle<CreateDCTraits, VerifierTraits> ScopedCreateDC; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_HDC_H_
diff --git a/src/base/win/scoped_hglobal.h b/src/base/win/scoped_hglobal.h new file mode 100644 index 0000000..891e6cd --- /dev/null +++ b/src/base/win/scoped_hglobal.h
@@ -0,0 +1,52 @@ +// Copyright (c) 2010 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_WIN_SCOPED_HGLOBAL_H_ +#define BASE_WIN_SCOPED_HGLOBAL_H_ + +#include <windows.h> + +#include "base/basictypes.h" + +namespace base { +namespace win { + +// Like ScopedHandle except for HGLOBAL. +template<class T> +class ScopedHGlobal { + public: + explicit ScopedHGlobal(HGLOBAL glob) : glob_(glob) { + data_ = static_cast<T*>(GlobalLock(glob_)); + } + ~ScopedHGlobal() { + GlobalUnlock(glob_); + } + + T* get() { return data_; } + + size_t Size() const { return GlobalSize(glob_); } + + T* operator->() const { + assert(data_ != 0); + return data_; + } + + T* release() { + T* data = data_; + data_ = NULL; + return data; + } + + private: + HGLOBAL glob_; + + T* data_; + + DISALLOW_COPY_AND_ASSIGN(ScopedHGlobal); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_HGLOBAL_H_
diff --git a/src/base/win/scoped_process_information.cc b/src/base/win/scoped_process_information.cc new file mode 100644 index 0000000..4adb8d4 --- /dev/null +++ b/src/base/win/scoped_process_information.cc
@@ -0,0 +1,126 @@ +// 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_process_information.h" + +#include "base/logging.h" +#include "base/win/scoped_handle.h" + +namespace base { +namespace win { + +namespace { + +// Closes the provided handle if it is not NULL. +void CheckAndCloseHandle(HANDLE handle) { + if (!handle) + return; + if (::CloseHandle(handle)) + return; + CHECK(false); +} + +// 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) { + if (!source) + return true; + + HANDLE temp = NULL; + if (!::DuplicateHandle(::GetCurrentProcess(), source, + ::GetCurrentProcess(), &temp, 0, FALSE, + DUPLICATE_SAME_ACCESS)) { + DPLOG(ERROR) << "Failed to duplicate a handle."; + return false; + } + *target = temp; + return true; +} + +} // namespace + +ScopedProcessInformation::ScopedProcessInformation() + : process_information_() { +} + +ScopedProcessInformation::~ScopedProcessInformation() { + Close(); +} + +PROCESS_INFORMATION* ScopedProcessInformation::Receive() { + DCHECK(!IsValid()) << "process_information_ must be NULL"; + return &process_information_; +} + +bool ScopedProcessInformation::IsValid() const { + return process_information_.hThread || process_information_.hProcess || + process_information_.dwProcessId || process_information_.dwThreadId; +} + + +void ScopedProcessInformation::Close() { + CheckAndCloseHandle(process_information_.hThread); + CheckAndCloseHandle(process_information_.hProcess); + Reset(); +} + +void ScopedProcessInformation::Swap(ScopedProcessInformation* other) { + DCHECK(other); + PROCESS_INFORMATION temp = other->process_information_; + other->process_information_ = process_information_; + process_information_ = temp; +} + +bool ScopedProcessInformation::DuplicateFrom( + const ScopedProcessInformation& other) { + DCHECK(!IsValid()) << "target ScopedProcessInformation must be NULL"; + DCHECK(other.IsValid()) << "source ScopedProcessInformation must be valid"; + + ScopedHandle duplicate_process; + ScopedHandle duplicate_thread; + + if (CheckAndDuplicateHandle(other.process_handle(), + duplicate_process.Receive()) && + CheckAndDuplicateHandle(other.thread_handle(), + duplicate_thread.Receive())) { + process_information_.dwProcessId = other.process_id(); + process_information_.dwThreadId = other.thread_id(); + process_information_.hProcess = duplicate_process.Take(); + process_information_.hThread = duplicate_thread.Take(); + return true; + } + + return false; +} + +PROCESS_INFORMATION ScopedProcessInformation::Take() { + PROCESS_INFORMATION process_information = process_information_; + Reset(); + return process_information; +} + +HANDLE ScopedProcessInformation::TakeProcessHandle() { + HANDLE process = process_information_.hProcess; + process_information_.hProcess = NULL; + process_information_.dwProcessId = 0; + return process; +} + +HANDLE ScopedProcessInformation::TakeThreadHandle() { + HANDLE thread = process_information_.hThread; + process_information_.hThread = NULL; + process_information_.dwThreadId = 0; + return thread; +} + +void ScopedProcessInformation::Reset() { + process_information_.hThread = NULL; + process_information_.hProcess = NULL; + process_information_.dwProcessId = 0; + process_information_.dwThreadId = 0; +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/scoped_process_information.h b/src/base/win/scoped_process_information.h new file mode 100644 index 0000000..cfa7dc9 --- /dev/null +++ b/src/base/win/scoped_process_information.h
@@ -0,0 +1,93 @@ +// 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_WIN_SCOPED_PROCESS_INFORMATION_H_ +#define BASE_WIN_SCOPED_PROCESS_INFORMATION_H_ + +#include <windows.h> + +#include "base/basictypes.h" +#include "base/base_export.h" + +namespace base { +namespace win { + +// Manages the closing of process and thread handles from PROCESS_INFORMATION +// structures. Allows clients to take ownership of either handle independently. +class BASE_EXPORT ScopedProcessInformation { + public: + // Creates an instance holding a null PROCESS_INFORMATION. + ScopedProcessInformation(); + + // Closes the held thread and process handles, if any. + ~ScopedProcessInformation(); + + // Returns a pointer 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. + PROCESS_INFORMATION* Receive(); + + // Returns true iff this instance is holding a thread and/or process handle. + bool IsValid() const; + + // Closes the held thread and process handles, if any, and resets the held + // PROCESS_INFORMATION to null. + void Close(); + + // Swaps contents with the other ScopedProcessInformation. + void Swap(ScopedProcessInformation* other); + + // Populates this instance with duplicate handles and the thread/process IDs + // from |other|. Returns false in case of failure, in which case this instance + // will be completely unpopulated. + bool DuplicateFrom(const ScopedProcessInformation& other); + + // Transfers ownership of the held PROCESS_INFORMATION, if any, away from this + // instance. Resets the held PROCESS_INFORMATION to null. + PROCESS_INFORMATION Take(); + + // Transfers ownership of the held process handle, if any, away from this + // instance. The hProcess and dwProcessId members of the held + // PROCESS_INFORMATION will be reset. + HANDLE TakeProcessHandle(); + + // Transfers ownership of the held thread handle, if any, away from this + // instance. The hThread and dwThreadId members of the held + // PROCESS_INFORMATION will be reset. + HANDLE TakeThreadHandle(); + + // Returns the held process handle, if any, while retaining ownership. + HANDLE process_handle() const { + return process_information_.hProcess; + } + + // Returns the held thread handle, if any, while retaining ownership. + HANDLE thread_handle() const { + return process_information_.hThread; + } + + // Returns the held process id, if any. + DWORD process_id() const { + return process_information_.dwProcessId; + } + + // Returns the held thread id, if any. + DWORD thread_id() const { + return process_information_.dwThreadId; + } + + private: + // Resets the held PROCESS_INFORMATION to null. + void Reset(); + + PROCESS_INFORMATION process_information_; + + DISALLOW_COPY_AND_ASSIGN(ScopedProcessInformation); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_PROCESS_INFORMATION_H_
diff --git a/src/base/win/scoped_process_information_unittest.cc b/src/base/win/scoped_process_information_unittest.cc new file mode 100644 index 0000000..906c156 --- /dev/null +++ b/src/base/win/scoped_process_information_unittest.cc
@@ -0,0 +1,181 @@ +// 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 <windows.h> + +#include <string> + +#include "base/command_line.h" +#include "base/process_util.h" +#include "base/test/multiprocess_test.h" +#include "base/win/scoped_process_information.h" +#include "testing/multiprocess_func_list.h" + +class ScopedProcessInformationTest : public base::MultiProcessTest { + protected: + void DoCreateProcess(const std::string& main_id, + PROCESS_INFORMATION* process_handle); +}; + +MULTIPROCESS_TEST_MAIN(ReturnSeven) { + return 7; +} + +MULTIPROCESS_TEST_MAIN(ReturnNine) { + return 9; +} + +void ScopedProcessInformationTest::DoCreateProcess( + const std::string& main_id, PROCESS_INFORMATION* process_handle) { + std::wstring cmd_line = + this->MakeCmdLine(main_id, false).GetCommandLineString(); + STARTUPINFO startup_info = {}; + startup_info.cb = sizeof(startup_info); + + EXPECT_TRUE(::CreateProcess(NULL, + const_cast<wchar_t*>(cmd_line.c_str()), + NULL, NULL, false, 0, NULL, NULL, + &startup_info, process_handle)); +} + +TEST_F(ScopedProcessInformationTest, TakeProcess) { + base::win::ScopedProcessInformation process_info; + DoCreateProcess("ReturnSeven", process_info.Receive()); + int exit_code = 0; + ASSERT_TRUE(base::WaitForExitCode(process_info.TakeProcessHandle(), + &exit_code)); + ASSERT_EQ(7, exit_code); + ASSERT_TRUE(process_info.IsValid()); + ASSERT_EQ(0u, process_info.process_id()); + ASSERT_TRUE(process_info.process_handle() == NULL); + ASSERT_NE(0u, process_info.thread_id()); + ASSERT_FALSE(process_info.thread_handle() == NULL); +} + +TEST_F(ScopedProcessInformationTest, TakeThread) { + base::win::ScopedProcessInformation process_info; + DoCreateProcess("ReturnSeven", process_info.Receive()); + ASSERT_TRUE(::CloseHandle(process_info.TakeThreadHandle())); + ASSERT_TRUE(process_info.IsValid()); + ASSERT_NE(0u, process_info.process_id()); + ASSERT_FALSE(process_info.process_handle() == NULL); + ASSERT_EQ(0u, process_info.thread_id()); + ASSERT_TRUE(process_info.thread_handle() == NULL); +} + +TEST_F(ScopedProcessInformationTest, TakeBoth) { + base::win::ScopedProcessInformation process_info; + DoCreateProcess("ReturnSeven", process_info.Receive()); + int exit_code = 0; + ASSERT_TRUE(base::WaitForExitCode(process_info.TakeProcessHandle(), + &exit_code)); + ASSERT_EQ(7, exit_code); + ASSERT_TRUE(::CloseHandle(process_info.TakeThreadHandle())); + ASSERT_FALSE(process_info.IsValid()); + ASSERT_EQ(0u, process_info.process_id()); + ASSERT_TRUE(process_info.process_handle() == NULL); + ASSERT_EQ(0u, process_info.thread_id()); + ASSERT_TRUE(process_info.thread_handle() == NULL); +} + +TEST_F(ScopedProcessInformationTest, TakeNothing) { + base::win::ScopedProcessInformation process_info; + DoCreateProcess("ReturnSeven", process_info.Receive()); + ASSERT_TRUE(process_info.IsValid()); + ASSERT_NE(0u, process_info.thread_id()); + ASSERT_FALSE(process_info.thread_handle() == NULL); + ASSERT_NE(0u, process_info.process_id()); + ASSERT_FALSE(process_info.process_handle() == NULL); +} + +TEST_F(ScopedProcessInformationTest, TakeWholeStruct) { + base::win::ScopedProcessInformation process_info; + DoCreateProcess("ReturnSeven", process_info.Receive()); + base::win::ScopedProcessInformation other; + *other.Receive() = process_info.Take(); + + ASSERT_FALSE(process_info.IsValid()); + ASSERT_EQ(0u, process_info.process_id()); + ASSERT_TRUE(process_info.process_handle() == NULL); + ASSERT_EQ(0u, process_info.thread_id()); + ASSERT_TRUE(process_info.thread_handle() == NULL); + + // Validate that what was taken is good. + ASSERT_NE(0u, other.thread_id()); + ASSERT_NE(0u, other.process_id()); + int exit_code = 0; + ASSERT_TRUE(base::WaitForExitCode(other.TakeProcessHandle(), + &exit_code)); + ASSERT_EQ(7, exit_code); + ASSERT_TRUE(::CloseHandle(other.TakeThreadHandle())); +} + +TEST_F(ScopedProcessInformationTest, Duplicate) { + base::win::ScopedProcessInformation process_info; + DoCreateProcess("ReturnSeven", process_info.Receive()); + base::win::ScopedProcessInformation duplicate; + duplicate.DuplicateFrom(process_info); + + ASSERT_TRUE(process_info.IsValid()); + ASSERT_NE(0u, process_info.process_id()); + ASSERT_EQ(duplicate.process_id(), process_info.process_id()); + ASSERT_NE(0u, process_info.thread_id()); + ASSERT_EQ(duplicate.thread_id(), process_info.thread_id()); + + // Validate that we have separate handles that are good. + int exit_code = 0; + ASSERT_TRUE(base::WaitForExitCode(process_info.TakeProcessHandle(), + &exit_code)); + ASSERT_EQ(7, exit_code); + + exit_code = 0; + ASSERT_TRUE(base::WaitForExitCode(duplicate.TakeProcessHandle(), + &exit_code)); + ASSERT_EQ(7, exit_code); + + ASSERT_TRUE(::CloseHandle(process_info.TakeThreadHandle())); + ASSERT_TRUE(::CloseHandle(duplicate.TakeThreadHandle())); +} + +TEST_F(ScopedProcessInformationTest, Swap) { + base::win::ScopedProcessInformation seven_to_nine_info; + DoCreateProcess("ReturnSeven", seven_to_nine_info.Receive()); + base::win::ScopedProcessInformation nine_to_seven_info; + DoCreateProcess("ReturnNine", nine_to_seven_info.Receive()); + + HANDLE seven_process = seven_to_nine_info.process_handle(); + DWORD seven_process_id = seven_to_nine_info.process_id(); + HANDLE seven_thread = seven_to_nine_info.thread_handle(); + DWORD seven_thread_id = seven_to_nine_info.thread_id(); + HANDLE nine_process = nine_to_seven_info.process_handle(); + DWORD nine_process_id = nine_to_seven_info.process_id(); + HANDLE nine_thread = nine_to_seven_info.thread_handle(); + DWORD nine_thread_id = nine_to_seven_info.thread_id(); + + seven_to_nine_info.Swap(&nine_to_seven_info); + + ASSERT_EQ(seven_process, nine_to_seven_info.process_handle()); + ASSERT_EQ(seven_process_id, nine_to_seven_info.process_id()); + ASSERT_EQ(seven_thread, nine_to_seven_info.thread_handle()); + ASSERT_EQ(seven_thread_id, nine_to_seven_info.thread_id()); + ASSERT_EQ(nine_process, seven_to_nine_info.process_handle()); + ASSERT_EQ(nine_process_id, seven_to_nine_info.process_id()); + ASSERT_EQ(nine_thread, seven_to_nine_info.thread_handle()); + ASSERT_EQ(nine_thread_id, seven_to_nine_info.thread_id()); + + int exit_code = 0; + ASSERT_TRUE(base::WaitForExitCode(seven_to_nine_info.TakeProcessHandle(), + &exit_code)); + ASSERT_EQ(9, exit_code); + + ASSERT_TRUE(base::WaitForExitCode(nine_to_seven_info.TakeProcessHandle(), + &exit_code)); + ASSERT_EQ(7, exit_code); + +} + +TEST_F(ScopedProcessInformationTest, InitiallyInvalid) { + base::win::ScopedProcessInformation process_info; + ASSERT_FALSE(process_info.IsValid()); +}
diff --git a/src/base/win/scoped_select_object.h b/src/base/win/scoped_select_object.h new file mode 100644 index 0000000..347de79 --- /dev/null +++ b/src/base/win/scoped_select_object.h
@@ -0,0 +1,43 @@ +// 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. + +#ifndef BASE_WIN_SCOPED_SELECT_OBJECT_H_ +#define BASE_WIN_SCOPED_SELECT_OBJECT_H_ + +#include <windows.h> + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace base { +namespace win { + +// Helper class for deselecting object from DC. +class ScopedSelectObject { + public: + ScopedSelectObject(HDC hdc, HGDIOBJ object) + : hdc_(hdc), + oldobj_(SelectObject(hdc, object)) { + DCHECK(hdc_); + DCHECK(object); + DCHECK(oldobj_ != NULL && oldobj_ != HGDI_ERROR); + } + + ~ScopedSelectObject() { + HGDIOBJ object = SelectObject(hdc_, oldobj_); + DCHECK((GetObjectType(oldobj_) != OBJ_REGION && object != NULL) || + (GetObjectType(oldobj_) == OBJ_REGION && object != HGDI_ERROR)); + } + + private: + HDC hdc_; + HGDIOBJ oldobj_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSelectObject); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_SELECT_OBJECT_H_
diff --git a/src/base/win/scoped_variant.cc b/src/base/win/scoped_variant.cc new file mode 100644 index 0000000..f57ab93 --- /dev/null +++ b/src/base/win/scoped_variant.cc
@@ -0,0 +1,276 @@ +// Copyright (c) 2010 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_variant.h" +#include "base/logging.h" + +namespace base { +namespace win { + +// Global, const instance of an empty variant. +const VARIANT ScopedVariant::kEmptyVariant = { VT_EMPTY }; + +ScopedVariant::~ScopedVariant() { + COMPILE_ASSERT(sizeof(ScopedVariant) == sizeof(VARIANT), ScopedVariantSize); + ::VariantClear(&var_); +} + +ScopedVariant::ScopedVariant(const wchar_t* str) { + var_.vt = VT_EMPTY; + Set(str); +} + +ScopedVariant::ScopedVariant(const wchar_t* str, UINT length) { + var_.vt = VT_BSTR; + var_.bstrVal = ::SysAllocStringLen(str, length); +} + +ScopedVariant::ScopedVariant(int value, VARTYPE vt) { + var_.vt = vt; + var_.lVal = value; +} + +ScopedVariant::ScopedVariant(double value, VARTYPE vt) { + DCHECK(vt == VT_R8 || vt == VT_DATE); + var_.vt = vt; + var_.dblVal = value; +} + +ScopedVariant::ScopedVariant(IDispatch* dispatch) { + var_.vt = VT_EMPTY; + Set(dispatch); +} + +ScopedVariant::ScopedVariant(IUnknown* unknown) { + var_.vt = VT_EMPTY; + Set(unknown); +} + +ScopedVariant::ScopedVariant(SAFEARRAY* safearray) { + var_.vt = VT_EMPTY; + Set(safearray); +} + +ScopedVariant::ScopedVariant(const VARIANT& var) { + var_.vt = VT_EMPTY; + Set(var); +} + +void ScopedVariant::Reset(const VARIANT& var) { + if (&var != &var_) { + ::VariantClear(&var_); + var_ = var; + } +} + +VARIANT ScopedVariant::Release() { + VARIANT var = var_; + var_.vt = VT_EMPTY; + return var; +} + +void ScopedVariant::Swap(ScopedVariant& var) { + VARIANT tmp = var_; + var_ = var.var_; + var.var_ = tmp; +} + +VARIANT* ScopedVariant::Receive() { + DCHECK(!IsLeakableVarType(var_.vt)) << "variant leak. type: " << var_.vt; + return &var_; +} + +VARIANT ScopedVariant::Copy() const { + VARIANT ret = { VT_EMPTY }; + ::VariantCopy(&ret, &var_); + return ret; +} + +int ScopedVariant::Compare(const VARIANT& var, bool ignore_case) const { + ULONG flags = ignore_case ? NORM_IGNORECASE : 0; + HRESULT hr = ::VarCmp(const_cast<VARIANT*>(&var_), const_cast<VARIANT*>(&var), + LOCALE_USER_DEFAULT, flags); + int ret = 0; + + switch (hr) { + case VARCMP_LT: + ret = -1; + break; + + case VARCMP_GT: + case VARCMP_NULL: + ret = 1; + break; + + default: + // Equal. + break; + } + + return ret; +} + +void ScopedVariant::Set(const wchar_t* str) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_BSTR; + var_.bstrVal = ::SysAllocString(str); +} + +void ScopedVariant::Set(int8 i8) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_I1; + var_.cVal = i8; +} + +void ScopedVariant::Set(uint8 ui8) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_UI1; + var_.bVal = ui8; +} + +void ScopedVariant::Set(int16 i16) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_I2; + var_.iVal = i16; +} + +void ScopedVariant::Set(uint16 ui16) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_UI2; + var_.uiVal = ui16; +} + +void ScopedVariant::Set(int32 i32) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_I4; + var_.lVal = i32; +} + +void ScopedVariant::Set(uint32 ui32) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_UI4; + var_.ulVal = ui32; +} + +void ScopedVariant::Set(int64 i64) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_I8; + var_.llVal = i64; +} + +void ScopedVariant::Set(uint64 ui64) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_UI8; + var_.ullVal = ui64; +} + +void ScopedVariant::Set(float r32) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_R4; + var_.fltVal = r32; +} + +void ScopedVariant::Set(double r64) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_R8; + var_.dblVal = r64; +} + +void ScopedVariant::SetDate(DATE date) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_DATE; + var_.date = date; +} + +void ScopedVariant::Set(IDispatch* disp) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_DISPATCH; + var_.pdispVal = disp; + if (disp) + disp->AddRef(); +} + +void ScopedVariant::Set(bool b) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_BOOL; + var_.boolVal = b ? VARIANT_TRUE : VARIANT_FALSE; +} + +void ScopedVariant::Set(IUnknown* unk) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + var_.vt = VT_UNKNOWN; + var_.punkVal = unk; + if (unk) + unk->AddRef(); +} + +void ScopedVariant::Set(SAFEARRAY* array) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + if (SUCCEEDED(::SafeArrayGetVartype(array, &var_.vt))) { + var_.vt |= VT_ARRAY; + var_.parray = array; + } else { + DCHECK(!array) << "Unable to determine safearray vartype"; + var_.vt = VT_EMPTY; + } +} + +void ScopedVariant::Set(const VARIANT& var) { + DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; + if (FAILED(::VariantCopy(&var_, &var))) { + DLOG(ERROR) << "VariantCopy failed"; + var_.vt = VT_EMPTY; + } +} + +ScopedVariant& ScopedVariant::operator=(const VARIANT& var) { + if (&var != &var_) { + VariantClear(&var_); + Set(var); + } + return *this; +} + +bool ScopedVariant::IsLeakableVarType(VARTYPE vt) { + bool leakable = false; + switch (vt & VT_TYPEMASK) { + case VT_BSTR: + case VT_DISPATCH: + // we treat VT_VARIANT as leakable to err on the safe side. + case VT_VARIANT: + case VT_UNKNOWN: + case VT_SAFEARRAY: + + // very rarely used stuff (if ever): + case VT_VOID: + case VT_PTR: + case VT_CARRAY: + case VT_USERDEFINED: + case VT_LPSTR: + case VT_LPWSTR: + case VT_RECORD: + case VT_INT_PTR: + case VT_UINT_PTR: + case VT_FILETIME: + case VT_BLOB: + case VT_STREAM: + case VT_STORAGE: + case VT_STREAMED_OBJECT: + case VT_STORED_OBJECT: + case VT_BLOB_OBJECT: + case VT_VERSIONED_STREAM: + case VT_BSTR_BLOB: + leakable = true; + break; + } + + if (!leakable && (vt & VT_ARRAY) != 0) { + leakable = true; + } + + return leakable; +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/scoped_variant.h b/src/base/win/scoped_variant.h new file mode 100644 index 0000000..b6e6579 --- /dev/null +++ b/src/base/win/scoped_variant.h
@@ -0,0 +1,166 @@ +// 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. + +#ifndef BASE_WIN_SCOPED_VARIANT_H_ +#define BASE_WIN_SCOPED_VARIANT_H_ + +#include <windows.h> +#include <oleauto.h> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace win { + +// Scoped VARIANT class for automatically freeing a COM VARIANT at the +// end of a scope. Additionally provides a few functions to make the +// encapsulated VARIANT easier to use. +// Instead of inheriting from VARIANT, we take the containment approach +// in order to have more control over the usage of the variant and guard +// against memory leaks. +class BASE_EXPORT ScopedVariant { + public: + // Declaration of a global variant variable that's always VT_EMPTY + static const VARIANT kEmptyVariant; + + // Default constructor. + ScopedVariant() { + // This is equivalent to what VariantInit does, but less code. + var_.vt = VT_EMPTY; + } + + // Constructor to create a new VT_BSTR VARIANT. + // NOTE: Do not pass a BSTR to this constructor expecting ownership to + // be transferred + explicit ScopedVariant(const wchar_t* str); + + // Creates a new VT_BSTR variant of a specified length. + ScopedVariant(const wchar_t* str, UINT length); + + // Creates a new integral type variant and assigns the value to + // VARIANT.lVal (32 bit sized field). + explicit ScopedVariant(int value, VARTYPE vt = VT_I4); + + // Creates a new double-precision type variant. |vt| must be either VT_R8 + // or VT_DATE. + explicit ScopedVariant(double value, VARTYPE vt = VT_R8); + + // VT_DISPATCH + explicit ScopedVariant(IDispatch* dispatch); + + // VT_UNKNOWN + explicit ScopedVariant(IUnknown* unknown); + + // SAFEARRAY + explicit ScopedVariant(SAFEARRAY* safearray); + + // Copies the variant. + explicit ScopedVariant(const VARIANT& var); + + ~ScopedVariant(); + + inline VARTYPE type() const { + return var_.vt; + } + + // Give ScopedVariant ownership over an already allocated VARIANT. + void Reset(const VARIANT& var = kEmptyVariant); + + // Releases ownership of the VARIANT to the caller. + VARIANT Release(); + + // Swap two ScopedVariant's. + void Swap(ScopedVariant& var); + + // Returns a copy of the variant. + VARIANT Copy() const; + + // The return value is 0 if the variants are equal, 1 if this object is + // greater than |var|, -1 if it is smaller. + int Compare(const VARIANT& var, bool ignore_case = false) const; + + // Retrieves the pointer address. + // Used to receive a VARIANT as an out argument (and take ownership). + // The function DCHECKs on the current value being empty/null. + // Usage: GetVariant(var.receive()); + VARIANT* Receive(); + + void Set(const wchar_t* str); + + // Setters for simple types. + void Set(int8 i8); + void Set(uint8 ui8); + void Set(int16 i16); + void Set(uint16 ui16); + void Set(int32 i32); + void Set(uint32 ui32); + void Set(int64 i64); + void Set(uint64 ui64); + void Set(float r32); + void Set(double r64); + void Set(bool b); + + // Creates a copy of |var| and assigns as this instance's value. + // Note that this is different from the Reset() method that's used to + // free the current value and assume ownership. + void Set(const VARIANT& var); + + // COM object setters + void Set(IDispatch* disp); + void Set(IUnknown* unk); + + // SAFEARRAY support + void Set(SAFEARRAY* array); + + // Special setter for DATE since DATE is a double and we already have + // a setter for double. + void SetDate(DATE date); + + // Allows const access to the contained variant without DCHECKs etc. + // This support is necessary for the V_XYZ (e.g. V_BSTR) set of macros to + // work properly but still doesn't allow modifications since we want control + // over that. + const VARIANT* operator&() const { + return &var_; + } + + // Like other scoped classes (e.g scoped_refptr, ScopedComPtr, ScopedBstr) + // we support the assignment operator for the type we wrap. + ScopedVariant& operator=(const VARIANT& var); + + // A hack to pass a pointer to the variant where the accepting + // function treats the variant as an input-only, read-only value + // but the function prototype requires a non const variant pointer. + // There's no DCHECK or anything here. Callers must know what they're doing. + VARIANT* AsInput() const { + // The nature of this function is const, so we declare + // it as such and cast away the constness here. + return const_cast<VARIANT*>(&var_); + } + + // Allows the ScopedVariant instance to be passed to functions either by value + // or by const reference. + operator const VARIANT&() const { + return var_; + } + + // Used as a debug check to see if we're leaking anything. + static bool IsLeakableVarType(VARTYPE vt); + + protected: + VARIANT var_; + + private: + // Comparison operators for ScopedVariant are not supported at this point. + // Use the Compare method instead. + bool operator==(const ScopedVariant& var) const; + bool operator!=(const ScopedVariant& var) const; + DISALLOW_COPY_AND_ASSIGN(ScopedVariant); +}; + +} // namespace win +} // namesoace base + +#endif // BASE_WIN_SCOPED_VARIANT_H_
diff --git a/src/base/win/scoped_variant_unittest.cc b/src/base/win/scoped_variant_unittest.cc new file mode 100644 index 0000000..1f017cf --- /dev/null +++ b/src/base/win/scoped_variant_unittest.cc
@@ -0,0 +1,261 @@ +// 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/win/scoped_variant.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +namespace { + +static const wchar_t kTestString1[] = L"Used to create BSTRs"; +static const wchar_t kTestString2[] = L"Also used to create BSTRs"; + +void GiveMeAVariant(VARIANT* ret) { + EXPECT_TRUE(ret != NULL); + ret->vt = VT_BSTR; + V_BSTR(ret) = ::SysAllocString(kTestString1); +} + +// A dummy IDispatch implementation (if you can call it that). +// The class does nothing intelligent really. Only increments a counter +// when AddRef is called and decrements it when Release is called. +class FakeComObject : public IDispatch { + public: + FakeComObject() : ref_(0) { + } + + STDMETHOD_(DWORD, AddRef)() { + ref_++; + return ref_; + } + + STDMETHOD_(DWORD, Release)() { + ref_--; + return ref_; + } + + STDMETHOD(QueryInterface)(REFIID, void**) { + return E_NOTIMPL; + } + + STDMETHOD(GetTypeInfoCount)(UINT*) { + return E_NOTIMPL; + } + + STDMETHOD(GetTypeInfo)(UINT, LCID, ITypeInfo**) { + return E_NOTIMPL; + } + + STDMETHOD(GetIDsOfNames)(REFIID, LPOLESTR*, UINT, LCID, DISPID*) { + return E_NOTIMPL; + } + + STDMETHOD(Invoke)(DISPID, REFIID, LCID, WORD, DISPPARAMS*, VARIANT*, + EXCEPINFO*, UINT*) { + return E_NOTIMPL; + } + + // A way to check the internal reference count of the class. + int ref_count() const { + return ref_; + } + + protected: + int ref_; +}; + +} // namespace + +TEST(ScopedVariantTest, ScopedVariant) { + ScopedVariant var; + EXPECT_TRUE(var.type() == VT_EMPTY); + // V_BSTR(&var) = NULL; <- NOTE: Assignment like that is not supported + + ScopedVariant var_bstr(L"VT_BSTR"); + EXPECT_EQ(VT_BSTR, V_VT(&var_bstr)); + EXPECT_TRUE(V_BSTR(&var_bstr) != NULL); // can't use EXPECT_NE for BSTR + var_bstr.Reset(); + EXPECT_NE(VT_BSTR, V_VT(&var_bstr)); + var_bstr.Set(kTestString2); + EXPECT_EQ(VT_BSTR, V_VT(&var_bstr)); + + VARIANT tmp = var_bstr.Release(); + EXPECT_EQ(VT_EMPTY, V_VT(&var_bstr)); + EXPECT_EQ(VT_BSTR, V_VT(&tmp)); + EXPECT_EQ(0, lstrcmp(V_BSTR(&tmp), kTestString2)); + + var.Reset(tmp); + EXPECT_EQ(VT_BSTR, V_VT(&var)); + EXPECT_EQ(0, lstrcmpW(V_BSTR(&var), kTestString2)); + + var_bstr.Swap(var); + EXPECT_EQ(VT_EMPTY, V_VT(&var)); + EXPECT_EQ(VT_BSTR, V_VT(&var_bstr)); + EXPECT_EQ(0, lstrcmpW(V_BSTR(&var_bstr), kTestString2)); + var_bstr.Reset(); + + // Test the Compare and Copy routines. + GiveMeAVariant(var_bstr.Receive()); + ScopedVariant var_bstr2(V_BSTR(&var_bstr)); + EXPECT_EQ(0, var_bstr.Compare(var_bstr2)); + var_bstr2.Reset(); + EXPECT_NE(0, var_bstr.Compare(var_bstr2)); + var_bstr2.Reset(var_bstr.Copy()); + EXPECT_EQ(0, var_bstr.Compare(var_bstr2)); + var_bstr2.Reset(); + var_bstr2.Set(V_BSTR(&var_bstr)); + EXPECT_EQ(0, var_bstr.Compare(var_bstr2)); + var_bstr2.Reset(); + var_bstr.Reset(); + + // Test for the SetDate setter. + SYSTEMTIME sys_time; + ::GetSystemTime(&sys_time); + DATE date; + ::SystemTimeToVariantTime(&sys_time, &date); + var.Reset(); + var.SetDate(date); + EXPECT_EQ(VT_DATE, var.type()); + EXPECT_EQ(date, V_DATE(&var)); + + // Simple setter tests. These do not require resetting the variant + // after each test since the variant type is not "leakable" (i.e. doesn't + // need to be freed explicitly). + + // We need static cast here since char defaults to int (!?). + var.Set(static_cast<int8>('v')); + EXPECT_EQ(VT_I1, var.type()); + EXPECT_EQ('v', V_I1(&var)); + + var.Set(static_cast<short>(123)); + EXPECT_EQ(VT_I2, var.type()); + EXPECT_EQ(123, V_I2(&var)); + + var.Set(static_cast<int32>(123)); + EXPECT_EQ(VT_I4, var.type()); + EXPECT_EQ(123, V_I4(&var)); + + var.Set(static_cast<int64>(123)); + EXPECT_EQ(VT_I8, var.type()); + EXPECT_EQ(123, V_I8(&var)); + + var.Set(static_cast<uint8>(123)); + EXPECT_EQ(VT_UI1, var.type()); + EXPECT_EQ(123, V_UI1(&var)); + + var.Set(static_cast<unsigned short>(123)); + EXPECT_EQ(VT_UI2, var.type()); + EXPECT_EQ(123, V_UI2(&var)); + + var.Set(static_cast<uint32>(123)); + EXPECT_EQ(VT_UI4, var.type()); + EXPECT_EQ(123, V_UI4(&var)); + + var.Set(static_cast<uint64>(123)); + EXPECT_EQ(VT_UI8, var.type()); + EXPECT_EQ(123, V_UI8(&var)); + + var.Set(123.123f); + EXPECT_EQ(VT_R4, var.type()); + EXPECT_EQ(123.123f, V_R4(&var)); + + var.Set(static_cast<double>(123.123)); + EXPECT_EQ(VT_R8, var.type()); + EXPECT_EQ(123.123, V_R8(&var)); + + var.Set(true); + EXPECT_EQ(VT_BOOL, var.type()); + EXPECT_EQ(VARIANT_TRUE, V_BOOL(&var)); + var.Set(false); + EXPECT_EQ(VT_BOOL, var.type()); + EXPECT_EQ(VARIANT_FALSE, V_BOOL(&var)); + + // Com interface tests + + var.Set(static_cast<IDispatch*>(NULL)); + EXPECT_EQ(VT_DISPATCH, var.type()); + EXPECT_EQ(NULL, V_DISPATCH(&var)); + var.Reset(); + + var.Set(static_cast<IUnknown*>(NULL)); + EXPECT_EQ(VT_UNKNOWN, var.type()); + EXPECT_EQ(NULL, V_UNKNOWN(&var)); + var.Reset(); + + FakeComObject faker; + EXPECT_EQ(0, faker.ref_count()); + var.Set(static_cast<IDispatch*>(&faker)); + EXPECT_EQ(VT_DISPATCH, var.type()); + EXPECT_EQ(&faker, V_DISPATCH(&var)); + EXPECT_EQ(1, faker.ref_count()); + var.Reset(); + EXPECT_EQ(0, faker.ref_count()); + + var.Set(static_cast<IUnknown*>(&faker)); + EXPECT_EQ(VT_UNKNOWN, var.type()); + EXPECT_EQ(&faker, V_UNKNOWN(&var)); + EXPECT_EQ(1, faker.ref_count()); + var.Reset(); + EXPECT_EQ(0, faker.ref_count()); + + { + ScopedVariant disp_var(&faker); + EXPECT_EQ(VT_DISPATCH, disp_var.type()); + EXPECT_EQ(&faker, V_DISPATCH(&disp_var)); + EXPECT_EQ(1, faker.ref_count()); + } + EXPECT_EQ(0, faker.ref_count()); + + { + ScopedVariant ref1(&faker); + EXPECT_EQ(1, faker.ref_count()); + ScopedVariant ref2(static_cast<const VARIANT&>(ref1)); + EXPECT_EQ(2, faker.ref_count()); + ScopedVariant ref3; + ref3 = static_cast<const VARIANT&>(ref2); + EXPECT_EQ(3, faker.ref_count()); + } + EXPECT_EQ(0, faker.ref_count()); + + { + ScopedVariant unk_var(static_cast<IUnknown*>(&faker)); + EXPECT_EQ(VT_UNKNOWN, unk_var.type()); + EXPECT_EQ(&faker, V_UNKNOWN(&unk_var)); + EXPECT_EQ(1, faker.ref_count()); + } + EXPECT_EQ(0, faker.ref_count()); + + VARIANT raw; + raw.vt = VT_UNKNOWN; + raw.punkVal = &faker; + EXPECT_EQ(0, faker.ref_count()); + var.Set(raw); + EXPECT_EQ(1, faker.ref_count()); + var.Reset(); + EXPECT_EQ(0, faker.ref_count()); + + { + ScopedVariant number(123); + EXPECT_EQ(VT_I4, number.type()); + EXPECT_EQ(123, V_I4(&number)); + } + + // SAFEARRAY tests + var.Set(static_cast<SAFEARRAY*>(NULL)); + EXPECT_EQ(VT_EMPTY, var.type()); + + SAFEARRAY* sa = ::SafeArrayCreateVector(VT_UI1, 0, 100); + ASSERT_TRUE(sa != NULL); + + var.Set(sa); + EXPECT_TRUE(ScopedVariant::IsLeakableVarType(var.type())); + EXPECT_EQ(VT_ARRAY | VT_UI1, var.type()); + EXPECT_EQ(sa, V_ARRAY(&var)); + // The array is destroyed in the destructor of var. +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/shortcut.cc b/src/base/win/shortcut.cc new file mode 100644 index 0000000..8afd55d --- /dev/null +++ b/src/base/win/shortcut.cc
@@ -0,0 +1,233 @@ +// 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/shortcut.h" + +#include <shellapi.h> +#include <shlobj.h> +#include <propkey.h> + +#include "base/file_util.h" +#include "base/threading/thread_restrictions.h" +#include "base/win/scoped_comptr.h" +#include "base/win/win_util.h" +#include "base/win/windows_version.h" + +namespace base { +namespace win { + +namespace { + +// Initializes |i_shell_link| and |i_persist_file| (releasing them first if they +// are already initialized). +// If |shortcut| is not NULL, loads |shortcut| into |i_persist_file|. +// If any of the above steps fail, both |i_shell_link| and |i_persist_file| will +// be released. +void InitializeShortcutInterfaces( + const wchar_t* shortcut, + ScopedComPtr<IShellLink>* i_shell_link, + ScopedComPtr<IPersistFile>* i_persist_file) { + i_shell_link->Release(); + i_persist_file->Release(); + if (FAILED(i_shell_link->CreateInstance(CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER)) || + FAILED(i_persist_file->QueryFrom(*i_shell_link)) || + (shortcut && FAILED((*i_persist_file)->Load(shortcut, STGM_READWRITE)))) { + i_shell_link->Release(); + i_persist_file->Release(); + } +} + +} // namespace + +bool CreateOrUpdateShortcutLink(const FilePath& shortcut_path, + const ShortcutProperties& properties, + ShortcutOperation operation) { + base::ThreadRestrictions::AssertIOAllowed(); + + // A target is required unless |operation| is SHORTCUT_UPDATE_EXISTING. + if (operation != SHORTCUT_UPDATE_EXISTING && + !(properties.options & ShortcutProperties::PROPERTIES_TARGET)) { + NOTREACHED(); + return false; + } + + bool shortcut_existed = file_util::PathExists(shortcut_path); + + ScopedComPtr<IShellLink> i_shell_link; + ScopedComPtr<IPersistFile> i_persist_file; + switch (operation) { + case SHORTCUT_CREATE_ALWAYS: + InitializeShortcutInterfaces(NULL, &i_shell_link, &i_persist_file); + break; + case SHORTCUT_UPDATE_EXISTING: + InitializeShortcutInterfaces(shortcut_path.value().c_str(), &i_shell_link, + &i_persist_file); + break; + case SHORTCUT_REPLACE_EXISTING: + InitializeShortcutInterfaces(shortcut_path.value().c_str(), &i_shell_link, + &i_persist_file); + // Confirm |shortcut_path| exists and is a shortcut by verifying + // |i_persist_file| was successfully initialized in the call above. If so, + // re-initialize the interfaces to begin writing a new shortcut (to + // overwrite the current one if successful). + if (i_persist_file.get()) + InitializeShortcutInterfaces(NULL, &i_shell_link, &i_persist_file); + break; + default: + NOTREACHED(); + } + + // Return false immediately upon failure to initialize shortcut interfaces. + if (!i_persist_file.get()) + return false; + + if ((properties.options & ShortcutProperties::PROPERTIES_TARGET) && + FAILED(i_shell_link->SetPath(properties.target.value().c_str()))) { + return false; + } + + if ((properties.options & ShortcutProperties::PROPERTIES_WORKING_DIR) && + FAILED(i_shell_link->SetWorkingDirectory( + properties.working_dir.value().c_str()))) { + return false; + } + + if ((properties.options & ShortcutProperties::PROPERTIES_ARGUMENTS) && + FAILED(i_shell_link->SetArguments(properties.arguments.c_str()))) { + return false; + } + + if ((properties.options & ShortcutProperties::PROPERTIES_DESCRIPTION) && + FAILED(i_shell_link->SetDescription(properties.description.c_str()))) { + return false; + } + + if ((properties.options & ShortcutProperties::PROPERTIES_ICON) && + FAILED(i_shell_link->SetIconLocation(properties.icon.value().c_str(), + properties.icon_index))) { + return false; + } + + bool has_app_id = + (properties.options & ShortcutProperties::PROPERTIES_APP_ID) != 0; + bool has_dual_mode = + (properties.options & ShortcutProperties::PROPERTIES_DUAL_MODE) != 0; + if ((has_app_id || has_dual_mode) && + GetVersion() >= VERSION_WIN7) { + ScopedComPtr<IPropertyStore> property_store; + if (FAILED(property_store.QueryFrom(i_shell_link)) || !property_store.get()) + return false; + + if (has_app_id && + !SetAppIdForPropertyStore(property_store, properties.app_id.c_str())) { + return false; + } + if (has_dual_mode && + !SetBooleanValueForPropertyStore(property_store, + PKEY_AppUserModel_IsDualMode, + properties.dual_mode)) { + return false; + } + } + + HRESULT result = i_persist_file->Save(shortcut_path.value().c_str(), TRUE); + + // Release the interfaces in case the SHChangeNotify call below depends on + // the operations above being fully completed. + i_persist_file.Release(); + i_shell_link.Release(); + + // If we successfully created/updated the icon, notify the shell that we have + // done so. + const bool succeeded = SUCCEEDED(result); + if (succeeded) { + if (shortcut_existed) { + // TODO(gab): SHCNE_UPDATEITEM might be sufficient here; further testing + // required. + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); + } else { + SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, shortcut_path.value().c_str(), + NULL); + } + } + + return succeeded; +} + +bool ResolveShortcut(const FilePath& shortcut_path, + FilePath* target_path, + string16* args) { + base::ThreadRestrictions::AssertIOAllowed(); + + HRESULT result; + ScopedComPtr<IShellLink> i_shell_link; + + // Get pointer to the IShellLink interface. + result = i_shell_link.CreateInstance(CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER); + if (FAILED(result)) + return false; + + ScopedComPtr<IPersistFile> persist; + // Query IShellLink for the IPersistFile interface. + result = persist.QueryFrom(i_shell_link); + if (FAILED(result)) + return false; + + // Load the shell link. + result = persist->Load(shortcut_path.value().c_str(), STGM_READ); + if (FAILED(result)) + return false; + + WCHAR temp[MAX_PATH]; + if (target_path) { + // Try to find the target of a shortcut. + result = i_shell_link->Resolve(0, SLR_NO_UI); + if (FAILED(result)) + return false; + + result = i_shell_link->GetPath(temp, MAX_PATH, NULL, SLGP_UNCPRIORITY); + if (FAILED(result)) + return false; + + *target_path = FilePath(temp); + } + + if (args) { + result = i_shell_link->GetArguments(temp, MAX_PATH); + if (FAILED(result)) + return false; + + *args = string16(temp); + } + return true; +} + +bool TaskbarPinShortcutLink(const wchar_t* shortcut) { + base::ThreadRestrictions::AssertIOAllowed(); + + // "Pin to taskbar" is only supported after Win7. + if (GetVersion() < VERSION_WIN7) + return false; + + int result = reinterpret_cast<int>(ShellExecute(NULL, L"taskbarpin", shortcut, + NULL, NULL, 0)); + return result > 32; +} + +bool TaskbarUnpinShortcutLink(const wchar_t* shortcut) { + base::ThreadRestrictions::AssertIOAllowed(); + + // "Unpin from taskbar" is only supported after Win7. + if (base::win::GetVersion() < base::win::VERSION_WIN7) + return false; + + int result = reinterpret_cast<int>(ShellExecute(NULL, L"taskbarunpin", + shortcut, NULL, NULL, 0)); + return result > 32; +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/shortcut.h b/src/base/win/shortcut.h new file mode 100644 index 0000000..c1e7d5c --- /dev/null +++ b/src/base/win/shortcut.h
@@ -0,0 +1,141 @@ +// 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_WIN_SHORTCUT_H_ +#define BASE_WIN_SHORTCUT_H_ + +#include <windows.h> + +#include "base/logging.h" +#include "base/file_path.h" +#include "base/string16.h" + +namespace base { +namespace win { + +enum ShortcutOperation { + // Create a new shortcut (overwriting if necessary). + SHORTCUT_CREATE_ALWAYS = 0, + // Overwrite an existing shortcut (fails if the shortcut doesn't exist). + SHORTCUT_REPLACE_EXISTING, + // Update specified properties only on an existing shortcut. + SHORTCUT_UPDATE_EXISTING, +}; + +// Properties for shortcuts. Properties set will be applied to the shortcut on +// creation/update, others will be ignored. +// Callers are encouraged to use the setters provided which take care of +// setting |options| as desired. +struct ShortcutProperties { + enum IndividualProperties { + PROPERTIES_TARGET = 1 << 0, + PROPERTIES_WORKING_DIR = 1 << 1, + PROPERTIES_ARGUMENTS = 1 << 2, + PROPERTIES_DESCRIPTION = 1 << 3, + PROPERTIES_ICON = 1 << 4, + PROPERTIES_APP_ID = 1 << 5, + PROPERTIES_DUAL_MODE = 1 << 6, + }; + + ShortcutProperties() : icon_index(-1), dual_mode(false), options(0U) {} + + void set_target(const FilePath& target_in) { + target = target_in; + options |= PROPERTIES_TARGET; + } + + void set_working_dir(const FilePath& working_dir_in) { + working_dir = working_dir_in; + options |= PROPERTIES_WORKING_DIR; + } + + void set_arguments(const string16& arguments_in) { + // Size restriction as per MSDN at http://goo.gl/TJ7q5. + DCHECK(arguments_in.size() < MAX_PATH); + arguments = arguments_in; + options |= PROPERTIES_ARGUMENTS; + } + + void set_description(const string16& description_in) { + // Size restriction as per MSDN at http://goo.gl/OdNQq. + DCHECK(description_in.size() < MAX_PATH); + description = description_in; + options |= PROPERTIES_DESCRIPTION; + } + + void set_icon(const FilePath& icon_in, int icon_index_in) { + icon = icon_in; + icon_index = icon_index_in; + options |= PROPERTIES_ICON; + } + + void set_app_id(const string16& app_id_in) { + app_id = app_id_in; + options |= PROPERTIES_APP_ID; + } + + void set_dual_mode(bool dual_mode_in) { + dual_mode = dual_mode_in; + options |= PROPERTIES_DUAL_MODE; + } + + // The target to launch from this shortcut. This is mandatory when creating + // a shortcut. + FilePath target; + // The name of the working directory when launching the shortcut. + FilePath working_dir; + // The arguments to be applied to |target| when launching from this shortcut. + // The length of this string must be less than MAX_PATH. + string16 arguments; + // The localized description of the shortcut. + // The length of this string must be less than MAX_PATH. + string16 description; + // The path to the icon (can be a dll or exe, in which case |icon_index| is + // the resource id). + FilePath icon; + int icon_index; + // The app model id for the shortcut (Win7+). + string16 app_id; + // Whether this is a dual mode shortcut (Win8+). + bool dual_mode; + // Bitfield made of IndividualProperties. Properties set in |options| will be + // set on the shortcut, others will be ignored. + uint32 options; +}; + +// This method creates (or updates) a shortcut link at |shortcut_path| using the +// information given through |properties|. +// Ensure you have initialized COM before calling into this function. +// |operation|: a choice from the ShortcutOperation enum. +// If |operation| is SHORTCUT_REPLACE_EXISTING or SHORTCUT_UPDATE_EXISTING and +// |shortcut_path| does not exist, this method is a no-op and returns false. +BASE_EXPORT bool CreateOrUpdateShortcutLink( + const FilePath& shortcut_path, + const ShortcutProperties& properties, + ShortcutOperation operation); + +// Resolve Windows shortcut (.LNK file) +// This methods tries to resolve a shortcut .LNK file. The path of the shortcut +// to resolve is in |shortcut_path|. If |target_path| is not NULL, the target +// will be resolved and placed in |target_path|. If |args| is not NULL, the +// arguments will be retrieved and placed in |args|. The function returns true +// if all requested fields are found successfully. +// Callers can safely use the same variable for both |shortcut_path| and +// |target_path|. +BASE_EXPORT bool ResolveShortcut(const FilePath& shortcut_path, + FilePath* target_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. +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. +BASE_EXPORT bool TaskbarUnpinShortcutLink(const wchar_t* shortcut); + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SHORTCUT_H_
diff --git a/src/base/win/shortcut_unittest.cc b/src/base/win/shortcut_unittest.cc new file mode 100644 index 0000000..26b22be --- /dev/null +++ b/src/base/win/shortcut_unittest.cc
@@ -0,0 +1,250 @@ +// 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/shortcut.h" + +#include <string> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/test/test_file_util.h" +#include "base/test/test_shortcut_win.h" +#include "base/win/scoped_com_initializer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +static const char kFileContents[] = "This is a target."; +static const char kFileContents2[] = "This is another target."; + +class ShortcutTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(temp_dir_2_.CreateUniqueTempDir()); + + link_file_ = temp_dir_.path().Append(L"My Link.lnk"); + + // Shortcut 1's properties + { + const FilePath target_file(temp_dir_.path().Append(L"Target 1.txt")); + file_util::WriteFile(target_file, kFileContents, + arraysize(kFileContents)); + + link_properties_.set_target(target_file); + link_properties_.set_working_dir(temp_dir_.path()); + link_properties_.set_arguments(L"--magic --awesome"); + link_properties_.set_description(L"Chrome is awesome."); + link_properties_.set_icon(link_properties_.target, 4); + link_properties_.set_app_id(L"Chrome"); + link_properties_.set_dual_mode(false); + } + + // Shortcut 2's properties (all different from properties of shortcut 1). + { + const FilePath target_file_2(temp_dir_.path().Append(L"Target 2.txt")); + file_util::WriteFile(target_file_2, kFileContents2, + arraysize(kFileContents2)); + + FilePath icon_path_2; + file_util::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()); + link_properties_2_.set_arguments(L"--super --crazy"); + link_properties_2_.set_description(L"The best in the west."); + link_properties_2_.set_icon(icon_path_2, 0); + link_properties_2_.set_app_id(L"Chrome.UserLevelCrazySuffix"); + link_properties_2_.set_dual_mode(true); + } + } + + base::win::ScopedCOMInitializer com_initializer_; + base::ScopedTempDir temp_dir_; + base::ScopedTempDir temp_dir_2_; + + // The link file to be created/updated in the shortcut tests below. + FilePath link_file_; + + // Properties for the created shortcut. + base::win::ShortcutProperties link_properties_; + + // Properties for the updated shortcut. + base::win::ShortcutProperties link_properties_2_; +}; + +} // namespace + +TEST_F(ShortcutTest, CreateAndResolveShortcut) { + base::win::ShortcutProperties only_target_properties; + only_target_properties.set_target(link_properties_.target); + + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, only_target_properties, base::win::SHORTCUT_CREATE_ALWAYS)); + + FilePath resolved_name; + EXPECT_TRUE(base::win::ResolveShortcut(link_file_, &resolved_name, NULL)); + + char read_contents[arraysize(kFileContents)]; + file_util::ReadFile(resolved_name, read_contents, arraysize(read_contents)); + EXPECT_STREQ(kFileContents, read_contents); +} + +TEST_F(ShortcutTest, ResolveShortcutWithArgs) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_CREATE_ALWAYS)); + + FilePath resolved_name; + string16 args; + EXPECT_TRUE(base::win::ResolveShortcut(link_file_, &resolved_name, &args)); + + char read_contents[arraysize(kFileContents)]; + file_util::ReadFile(resolved_name, read_contents, arraysize(read_contents)); + EXPECT_STREQ(kFileContents, read_contents); + EXPECT_EQ(link_properties_.arguments, args); +} + +TEST_F(ShortcutTest, CreateShortcutWithOnlySomeProperties) { + base::win::ShortcutProperties target_and_args_properties; + target_and_args_properties.set_target(link_properties_.target); + target_and_args_properties.set_arguments(link_properties_.arguments); + + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, target_and_args_properties, + base::win::SHORTCUT_CREATE_ALWAYS)); + + base::win::ValidateShortcut(link_file_, target_and_args_properties); +} + +TEST_F(ShortcutTest, CreateShortcutVerifyProperties) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_CREATE_ALWAYS)); + + base::win::ValidateShortcut(link_file_, link_properties_); +} + +TEST_F(ShortcutTest, UpdateShortcutVerifyProperties) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_CREATE_ALWAYS)); + + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_2_, base::win::SHORTCUT_UPDATE_EXISTING)); + + base::win::ValidateShortcut(link_file_, link_properties_2_); +} + +TEST_F(ShortcutTest, UpdateShortcutUpdateOnlyTargetAndResolve) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_CREATE_ALWAYS)); + + base::win::ShortcutProperties update_only_target_properties; + update_only_target_properties.set_target(link_properties_2_.target); + + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, update_only_target_properties, + base::win::SHORTCUT_UPDATE_EXISTING)); + + base::win::ShortcutProperties expected_properties = link_properties_; + expected_properties.set_target(link_properties_2_.target); + base::win::ValidateShortcut(link_file_, expected_properties); + + FilePath resolved_name; + EXPECT_TRUE(base::win::ResolveShortcut(link_file_, &resolved_name, NULL)); + + char read_contents[arraysize(kFileContents2)]; + file_util::ReadFile(resolved_name, read_contents, arraysize(read_contents)); + EXPECT_STREQ(kFileContents2, read_contents); +} + +TEST_F(ShortcutTest, UpdateShortcutMakeDualMode) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_CREATE_ALWAYS)); + + base::win::ShortcutProperties make_dual_mode_properties; + make_dual_mode_properties.set_dual_mode(true); + + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, make_dual_mode_properties, + base::win::SHORTCUT_UPDATE_EXISTING)); + + base::win::ShortcutProperties expected_properties = link_properties_; + expected_properties.set_dual_mode(true); + base::win::ValidateShortcut(link_file_, expected_properties); +} + +TEST_F(ShortcutTest, UpdateShortcutRemoveDualMode) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_2_, base::win::SHORTCUT_CREATE_ALWAYS)); + + base::win::ShortcutProperties remove_dual_mode_properties; + remove_dual_mode_properties.set_dual_mode(false); + + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, remove_dual_mode_properties, + base::win::SHORTCUT_UPDATE_EXISTING)); + + base::win::ShortcutProperties expected_properties = link_properties_2_; + expected_properties.set_dual_mode(false); + base::win::ValidateShortcut(link_file_, expected_properties); +} + +TEST_F(ShortcutTest, UpdateShortcutClearArguments) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_CREATE_ALWAYS)); + + base::win::ShortcutProperties clear_arguments_properties; + clear_arguments_properties.set_arguments(string16()); + + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, clear_arguments_properties, + base::win::SHORTCUT_UPDATE_EXISTING)); + + base::win::ShortcutProperties expected_properties = link_properties_; + expected_properties.set_arguments(string16()); + base::win::ValidateShortcut(link_file_, expected_properties); +} + +TEST_F(ShortcutTest, FailUpdateShortcutThatDoesNotExist) { + ASSERT_FALSE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_UPDATE_EXISTING)); + ASSERT_FALSE(file_util::PathExists(link_file_)); +} + +TEST_F(ShortcutTest, TruncateShortcutAllProperties) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_CREATE_ALWAYS)); + + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_2_, base::win::SHORTCUT_REPLACE_EXISTING)); + + base::win::ValidateShortcut(link_file_, link_properties_2_); +} + +TEST_F(ShortcutTest, TruncateShortcutSomeProperties) { + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_CREATE_ALWAYS)); + + base::win::ShortcutProperties new_properties; + new_properties.set_target(link_properties_2_.target); + new_properties.set_description(link_properties_2_.description); + ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink( + link_file_, new_properties, base::win::SHORTCUT_REPLACE_EXISTING)); + + // Expect only properties in |new_properties| to be set, all other properties + // should have been overwritten. + base::win::ShortcutProperties expected_properties = new_properties; + expected_properties.set_working_dir(FilePath()); + expected_properties.set_arguments(string16()); + expected_properties.set_icon(FilePath(), 0); + expected_properties.set_app_id(string16()); + expected_properties.set_dual_mode(false); + base::win::ValidateShortcut(link_file_, expected_properties); +} + +TEST_F(ShortcutTest, FailTruncateShortcutThatDoesNotExist) { + ASSERT_FALSE(base::win::CreateOrUpdateShortcutLink( + link_file_, link_properties_, base::win::SHORTCUT_REPLACE_EXISTING)); + ASSERT_FALSE(file_util::PathExists(link_file_)); +}
diff --git a/src/base/win/startup_information.cc b/src/base/win/startup_information.cc new file mode 100644 index 0000000..aff52eb --- /dev/null +++ b/src/base/win/startup_information.cc
@@ -0,0 +1,109 @@ +// 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/startup_information.h" + +#include "base/logging.h" +#include "base/win/windows_version.h" + +namespace { + +typedef BOOL (WINAPI *InitializeProcThreadAttributeListFunction)( + LPPROC_THREAD_ATTRIBUTE_LIST attribute_list, + DWORD attribute_count, + DWORD flags, + PSIZE_T size); +static InitializeProcThreadAttributeListFunction + initialize_proc_thread_attribute_list; + +typedef BOOL (WINAPI *UpdateProcThreadAttributeFunction)( + LPPROC_THREAD_ATTRIBUTE_LIST attribute_list, + DWORD flags, + DWORD_PTR attribute, + PVOID value, + SIZE_T size, + PVOID previous_value, + PSIZE_T return_size); +static UpdateProcThreadAttributeFunction update_proc_thread_attribute_list; + +typedef VOID (WINAPI *DeleteProcThreadAttributeListFunction)( + LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList); +static DeleteProcThreadAttributeListFunction delete_proc_thread_attribute_list; + +} // namespace + +namespace base { +namespace win { + +StartupInformation::StartupInformation() { + memset(&startup_info_, 0, sizeof(startup_info_)); + + // Pre Windows Vista doesn't support STARTUPINFOEX. + if (base::win::GetVersion() < base::win::VERSION_VISTA) { + startup_info_.StartupInfo.cb = sizeof(STARTUPINFO); + return; + } + + startup_info_.StartupInfo.cb = sizeof(startup_info_); + + // Load the attribute API functions. + if (!initialize_proc_thread_attribute_list || + !update_proc_thread_attribute_list || + !delete_proc_thread_attribute_list) { + HMODULE module = ::GetModuleHandleW(L"kernel32.dll"); + initialize_proc_thread_attribute_list = + reinterpret_cast<InitializeProcThreadAttributeListFunction>( + ::GetProcAddress(module, "InitializeProcThreadAttributeList")); + update_proc_thread_attribute_list = + reinterpret_cast<UpdateProcThreadAttributeFunction>( + ::GetProcAddress(module, "UpdateProcThreadAttribute")); + delete_proc_thread_attribute_list = + reinterpret_cast<DeleteProcThreadAttributeListFunction>( + ::GetProcAddress(module, "DeleteProcThreadAttributeList")); + } +} + +StartupInformation::~StartupInformation() { + if (startup_info_.lpAttributeList) { + delete_proc_thread_attribute_list(startup_info_.lpAttributeList); + delete [] reinterpret_cast<BYTE*>(startup_info_.lpAttributeList); + } +} + +bool StartupInformation::InitializeProcThreadAttributeList( + DWORD attribute_count) { + if (startup_info_.StartupInfo.cb != sizeof(startup_info_) || + startup_info_.lpAttributeList) + return false; + + SIZE_T size = 0; + initialize_proc_thread_attribute_list(NULL, attribute_count, 0, &size); + if (size == 0) + return false; + + startup_info_.lpAttributeList = + reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(new BYTE[size]); + if (!initialize_proc_thread_attribute_list(startup_info_.lpAttributeList, + attribute_count, 0, &size)) { + delete [] reinterpret_cast<BYTE*>(startup_info_.lpAttributeList); + startup_info_.lpAttributeList = NULL; + return false; + } + + return true; +} + +bool StartupInformation::UpdateProcThreadAttribute( + DWORD_PTR attribute, + void* value, + size_t size) { + if (!startup_info_.lpAttributeList) + return false; + return !!update_proc_thread_attribute_list(startup_info_.lpAttributeList, 0, + attribute, value, size, NULL, NULL); +} + +} // namespace win +} // namespace base +
diff --git a/src/base/win/startup_information.h b/src/base/win/startup_information.h new file mode 100644 index 0000000..7cef81f --- /dev/null +++ b/src/base/win/startup_information.h
@@ -0,0 +1,49 @@ +// 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_WIN_STARTUP_INFORMATION_H_ +#define BASE_WIN_STARTUP_INFORMATION_H_ + +#include <windows.h> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace win { + +// Manages the lifetime of additional attributes in STARTUPINFOEX. +class BASE_EXPORT StartupInformation { + public: + StartupInformation(); + + ~StartupInformation(); + + // Initialize the attribute list for the specified number of entries. + bool InitializeProcThreadAttributeList(DWORD attribute_count); + + // Sets one entry in the initialized attribute list. + bool UpdateProcThreadAttribute(DWORD_PTR attribute, + void* value, + size_t size); + + LPSTARTUPINFOW startup_info() { return &startup_info_.StartupInfo; } + const LPSTARTUPINFOW startup_info() const { + return const_cast<const LPSTARTUPINFOW>(&startup_info_.StartupInfo); + } + + bool has_extended_startup_info() const { + return !!startup_info_.lpAttributeList; + } + + private: + STARTUPINFOEXW startup_info_; + DISALLOW_COPY_AND_ASSIGN(StartupInformation); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_SCOPED_STARTUP_INFO_EX_H_ +
diff --git a/src/base/win/startup_information_unittest.cc b/src/base/win/startup_information_unittest.cc new file mode 100644 index 0000000..1903564 --- /dev/null +++ b/src/base/win/startup_information_unittest.cc
@@ -0,0 +1,76 @@ +// 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 <windows.h> + +#include <string> + +#include "base/command_line.h" +#include "base/test/multiprocess_test.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" +#include "testing/multiprocess_func_list.h" + +const wchar_t kSectionName[] = L"EventTestSection"; +const size_t kSectionSize = 4096; + +MULTIPROCESS_TEST_MAIN(FireInheritedEvents) { + HANDLE section = ::OpenFileMappingW(PAGE_READWRITE, false, kSectionName); + HANDLE* events = reinterpret_cast<HANDLE*>(::MapViewOfFile(section, + PAGE_READWRITE, 0, 0, kSectionSize)); + // This event should not be valid because it wasn't explicitly inherited. + if (::SetEvent(events[1])) + return -1; + // This event should be valid because it was explicitly inherited. + if (!::SetEvent(events[0])) + return -1; + + return 0; +} + +class StartupInformationTest : public base::MultiProcessTest {}; + +// Verify that only the explicitly specified event is inherited. +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, + PAGE_READWRITE, 0, kSectionSize, + kSectionName); + ASSERT_TRUE(section); + + HANDLE* events = reinterpret_cast<HANDLE*>(::MapViewOfFile(section, + FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, kSectionSize)); + + // Make two inheritable events. + SECURITY_ATTRIBUTES security_attributes = { sizeof(security_attributes), + NULL, true }; + events[0] = ::CreateEvent(&security_attributes, false, false, NULL); + ASSERT_TRUE(events[0]); + events[1] = ::CreateEvent(&security_attributes, false, false, NULL); + ASSERT_TRUE(events[1]); + + ASSERT_TRUE(startup_info.InitializeProcThreadAttributeList(1)); + ASSERT_TRUE(startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &events[0], + sizeof(events[0]))); + + std::wstring cmd_line = + this->MakeCmdLine("FireInheritedEvents", false).GetCommandLineString(); + + 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(); + // Only the first event should be signalled + EXPECT_EQ(WAIT_OBJECT_0, ::WaitForMultipleObjects(2, events, false, + 4000)); +} +
diff --git a/src/base/win/text_services_message_filter.cc b/src/base/win/text_services_message_filter.cc new file mode 100644 index 0000000..7ce233d --- /dev/null +++ b/src/base/win/text_services_message_filter.cc
@@ -0,0 +1,82 @@ +// 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/text_services_message_filter.h" + +namespace base { +namespace win { + +TextServicesMessageFilter::TextServicesMessageFilter() + : client_id_(TF_CLIENTID_NULL), + is_initialized_(false) { +} + +TextServicesMessageFilter::~TextServicesMessageFilter() { + if (is_initialized_) + thread_mgr_->Deactivate(); +} + +bool TextServicesMessageFilter::Init() { + if (FAILED(thread_mgr_.CreateInstance(CLSID_TF_ThreadMgr))) + return false; + + if (FAILED(message_pump_.QueryFrom(thread_mgr_))) + return false; + + if (FAILED(keystroke_mgr_.QueryFrom(thread_mgr_))) + return false; + + if (FAILED(thread_mgr_->Activate(&client_id_))) + return false; + + is_initialized_ = true; + return is_initialized_; +} + +// Wraps for ITfMessagePump::PeekMessage with win32 PeekMessage signature. +// Obtains messages from application message queue. +BOOL TextServicesMessageFilter::DoPeekMessage(MSG* msg, + HWND window_handle, + UINT msg_filter_min, + UINT msg_filter_max, + UINT remove_msg) { + BOOL result = FALSE; + if (FAILED(message_pump_->PeekMessage(msg, window_handle, + msg_filter_min, msg_filter_max, + remove_msg, &result))) { + result = FALSE; + } + + return result; +} + +// Sends message to Text Service Manager. +// The message will be used to input composition text. +// Returns true if |message| was consumed by text service manager. +bool TextServicesMessageFilter::ProcessMessage(const MSG& msg) { + if (msg.message == WM_KEYDOWN) { + BOOL eaten = FALSE; + HRESULT hr = keystroke_mgr_->TestKeyDown(msg.wParam, msg.lParam, &eaten); + if (FAILED(hr) && !eaten) + return false; + eaten = FALSE; + hr = keystroke_mgr_->KeyDown(msg.wParam, msg.lParam, &eaten); + return (SUCCEEDED(hr) && !!eaten); + } + + if (msg.message == WM_KEYUP) { + BOOL eaten = FALSE; + HRESULT hr = keystroke_mgr_->TestKeyUp(msg.wParam, msg.lParam, &eaten); + if (FAILED(hr) && !eaten) + return false; + eaten = FALSE; + hr = keystroke_mgr_->KeyUp(msg.wParam, msg.lParam, &eaten); + return (SUCCEEDED(hr) && !!eaten); + } + + return false; +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/text_services_message_filter.h b/src/base/win/text_services_message_filter.h new file mode 100644 index 0000000..facd613 --- /dev/null +++ b/src/base/win/text_services_message_filter.h
@@ -0,0 +1,48 @@ +// 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_WIN_TEXT_SERVICES_MESSAGE_FILTER_H_ +#define BASE_WIN_TEXT_SERVICES_MESSAGE_FILTER_H_ + +#include <msctf.h> +#include <Windows.h> + +#include "base/memory/scoped_ptr.h" +#include "base/message_pump_win.h" +#include "base/win/metro.h" +#include "base/win/scoped_comptr.h" + +namespace base { +namespace win { + +// TextServicesMessageFilter extends MessageFilter with methods that are using +// Text Services Framework COM component. +class BASE_EXPORT TextServicesMessageFilter + : public base::MessagePumpForUI::MessageFilter { + public: + TextServicesMessageFilter(); + virtual ~TextServicesMessageFilter(); + virtual BOOL DoPeekMessage(MSG* msg, + HWND window_handle, + UINT msg_filter_min, + UINT msg_filter_max, + UINT remove_msg) OVERRIDE; + virtual bool ProcessMessage(const MSG& msg) OVERRIDE; + + bool Init(); + + private: + TfClientId client_id_; + bool is_initialized_; + base::win::ScopedComPtr<ITfThreadMgr> thread_mgr_; + base::win::ScopedComPtr<ITfMessagePump> message_pump_; + base::win::ScopedComPtr<ITfKeystrokeMgr> keystroke_mgr_; + + DISALLOW_COPY_AND_ASSIGN(TextServicesMessageFilter); +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_TEXT_SERVICES_MESSAGE_FILTER_H_
diff --git a/src/base/win/win_util.cc b/src/base/win/win_util.cc new file mode 100644 index 0000000..b885d6e --- /dev/null +++ b/src/base/win/win_util.cc
@@ -0,0 +1,249 @@ +// 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/win_util.h" + +#include <aclapi.h> +#include <shobjidl.h> // Must be before propkey. +#include <initguid.h> +#include <propkey.h> +#include <propvarutil.h> +#include <sddl.h> +#include <shlobj.h> +#include <signal.h> +#include <stdlib.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/registry.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/threading/thread_restrictions.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" + +namespace { + +// Sets the value of |property_key| to |property_value| in |property_store|. +// Clears the PropVariant contained in |property_value|. +bool SetPropVariantValueForPropertyStore( + IPropertyStore* property_store, + const PROPERTYKEY& property_key, + PROPVARIANT* property_value) { + DCHECK(property_store); + + HRESULT result = property_store->SetValue(property_key, *property_value); + if (result == S_OK) + result = property_store->Commit(); + + PropVariantClear(property_value); + return SUCCEEDED(result); +} + +void __cdecl ForceCrashOnSigAbort(int) { + *((int*)0) = 0x1337; +} + +} // namespace + +namespace base { +namespace win { + +static bool g_crash_on_process_detach = false; + +#define NONCLIENTMETRICS_SIZE_PRE_VISTA \ + SIZEOF_STRUCT_WITH_SPECIFIED_LAST_MEMBER(NONCLIENTMETRICS, lfMessageFont) + +void GetNonClientMetrics(NONCLIENTMETRICS* metrics) { + DCHECK(metrics); + + static const UINT SIZEOF_NONCLIENTMETRICS = + (base::win::GetVersion() >= base::win::VERSION_VISTA) ? + sizeof(NONCLIENTMETRICS) : NONCLIENTMETRICS_SIZE_PRE_VISTA; + metrics->cbSize = SIZEOF_NONCLIENTMETRICS; + const bool success = !!SystemParametersInfo(SPI_GETNONCLIENTMETRICS, + SIZEOF_NONCLIENTMETRICS, metrics, + 0); + DCHECK(success); +} + +bool GetUserSidString(std::wstring* user_sid) { + // Get the current token. + HANDLE token = NULL; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) + return false; + base::win::ScopedHandle token_scoped(token); + + DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; + scoped_array<BYTE> user_bytes(new BYTE[size]); + TOKEN_USER* user = reinterpret_cast<TOKEN_USER*>(user_bytes.get()); + + if (!::GetTokenInformation(token, TokenUser, user, size, &size)) + return false; + + if (!user->User.Sid) + return false; + + // Convert the data to a string. + wchar_t* sid_string; + if (!::ConvertSidToStringSid(user->User.Sid, &sid_string)) + return false; + + *user_sid = sid_string; + + ::LocalFree(sid_string); + + return true; +} + +bool IsShiftPressed() { + return (::GetKeyState(VK_SHIFT) & 0x8000) == 0x8000; +} + +bool IsCtrlPressed() { + return (::GetKeyState(VK_CONTROL) & 0x8000) == 0x8000; +} + +bool IsAltPressed() { + return (::GetKeyState(VK_MENU) & 0x8000) == 0x8000; +} + +bool UserAccountControlIsEnabled() { + // This can be slow if Windows ends up going to disk. Should watch this key + // for changes and only read it once, preferably on the file thread. + // http://code.google.com/p/chromium/issues/detail?id=61644 + base::ThreadRestrictions::ScopedAllowIO allow_io; + + base::win::RegKey key(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", + KEY_READ); + DWORD uac_enabled; + if (key.ReadValueDW(L"EnableLUA", &uac_enabled) != ERROR_SUCCESS) + return true; + // Users can set the EnableLUA value to something arbitrary, like 2, which + // Vista will treat as UAC enabled, so we make sure it is not set to 0. + return (uac_enabled != 0); +} + +bool SetBooleanValueForPropertyStore(IPropertyStore* property_store, + const PROPERTYKEY& property_key, + bool property_bool_value) { + PROPVARIANT property_value; + if (FAILED(InitPropVariantFromBoolean(property_bool_value, &property_value))) + return false; + + return SetPropVariantValueForPropertyStore(property_store, + property_key, + &property_value); +} + +bool SetStringValueForPropertyStore(IPropertyStore* property_store, + const PROPERTYKEY& property_key, + const wchar_t* property_string_value) { + PROPVARIANT property_value; + if (FAILED(InitPropVariantFromString(property_string_value, &property_value))) + return false; + + return SetPropVariantValueForPropertyStore(property_store, + property_key, + &property_value); +} + +bool SetAppIdForPropertyStore(IPropertyStore* property_store, + const wchar_t* app_id) { + // App id should be less than 64 chars and contain no space. And recommended + // format is CompanyName.ProductName[.SubProduct.ProductNumber]. + // See http://msdn.microsoft.com/en-us/library/dd378459%28VS.85%29.aspx + DCHECK(lstrlen(app_id) < 64 && wcschr(app_id, L' ') == NULL); + + return SetStringValueForPropertyStore(property_store, + PKEY_AppUserModel_ID, + app_id); +} + +static const char16 kAutoRunKeyPath[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; + +bool AddCommandToAutoRun(HKEY root_key, const string16& name, + const string16& command) { + base::win::RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_SET_VALUE); + return (autorun_key.WriteValue(name.c_str(), command.c_str()) == + ERROR_SUCCESS); +} + +bool RemoveCommandFromAutoRun(HKEY root_key, const string16& name) { + base::win::RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_SET_VALUE); + return (autorun_key.DeleteValue(name.c_str()) == ERROR_SUCCESS); +} + +bool ReadCommandFromAutoRun(HKEY root_key, + const string16& name, + string16* command) { + base::win::RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_QUERY_VALUE); + return (autorun_key.ReadValue(name.c_str(), command) == ERROR_SUCCESS); +} + +void SetShouldCrashOnProcessDetach(bool crash) { + g_crash_on_process_detach = crash; +} + +bool ShouldCrashOnProcessDetach() { + return g_crash_on_process_detach; +} + +void SetAbortBehaviorForCrashReporting() { + // Prevent CRT's abort code from prompting a dialog or trying to "report" it. + // Disabling the _CALL_REPORTFAULT behavior is important since otherwise it + // has the sideffect of clearing our exception filter, which means we + // don't get any crash. + _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + + // Set a SIGABRT handler for good measure. We will crash even if the default + // is left in place, however this allows us to crash earlier. And it also + // lets us crash in response to code which might directly call raise(SIGABRT) + signal(SIGABRT, ForceCrashOnSigAbort); +} + +bool IsMachineATablet() { + if (base::win::GetVersion() < base::win::VERSION_WIN7) + return false; + const int kMultiTouch = NID_INTEGRATED_TOUCH | NID_MULTI_INPUT | NID_READY; + const int kMaxTabletScreenWidth = 1366; + const int kMaxTabletScreenHeight = 768; + int sm = GetSystemMetrics(SM_DIGITIZER); + if ((sm & kMultiTouch) == kMultiTouch) { + int cx = GetSystemMetrics(SM_CXSCREEN); + int cy = GetSystemMetrics(SM_CYSCREEN); + // Handle landscape and portrait modes. + return cx > cy ? + (cx <= kMaxTabletScreenWidth && cy <= kMaxTabletScreenHeight) : + (cy <= kMaxTabletScreenWidth && cx <= kMaxTabletScreenHeight); + } + return false; +} + +} // namespace win +} // namespace base + +#ifdef _MSC_VER +// +// If the ASSERT below fails, please install Visual Studio 2005 Service Pack 1. +// +extern char VisualStudio2005ServicePack1Detection[10]; +COMPILE_ASSERT(sizeof(&VisualStudio2005ServicePack1Detection) == sizeof(void*), + VS2005SP1Detect); +// +// Chrome requires at least Service Pack 1 for Visual Studio 2005. +// +#endif // _MSC_VER + +#ifndef COPY_FILE_COPY_SYMLINK +#error You must install the Windows 2008 or Vista Software Development Kit and \ +set it as your default include path to build this library. You can grab it by \ +searching for "download windows sdk 2008" in your favorite web search engine. \ +Also make sure you register the SDK with Visual Studio, by selecting \ +"Integrate Windows SDK with Visual Studio 2005" from the Windows SDK \ +menu (see Start - All Programs - Microsoft Windows SDK - \ +Visual Studio Registration). +#endif
diff --git a/src/base/win/win_util.h b/src/base/win/win_util.h new file mode 100644 index 0000000..d5bcd7a --- /dev/null +++ b/src/base/win/win_util.h
@@ -0,0 +1,127 @@ +// 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. + +// ============================================================================= +// PLEASE READ +// +// In general, you should not be adding stuff to this file. +// +// - If your thing is only used in one place, just put it in a reasonable +// location in or near that one place. It's nice you want people to be able +// to re-use your function, but realistically, if it hasn't been necessary +// before after so many years of development, it's probably not going to be +// used in other places in the future unless you know of them now. +// +// - If your thing is used by multiple callers and is UI-related, it should +// probably be in app/win/ instead. Try to put it in the most specific file +// possible (avoiding the *_util files when practical). +// +// ============================================================================= + +#ifndef BASE_WIN_WIN_UTIL_H_ +#define BASE_WIN_WIN_UTIL_H_ + +#include <windows.h> + +#include <string> + +#include "base/base_export.h" +#include "base/string16.h" + +struct IPropertyStore; +struct _tagpropertykey; +typedef _tagpropertykey PROPERTYKEY; + +namespace base { +namespace win { + +// A Windows message reflected from other windows. This message is sent +// with the following arguments: +// hWnd - Target window +// uMsg - kReflectedMessage +// wParam - Should be 0 +// lParam - Pointer to MSG struct containing the original message. +const int kReflectedMessage = WM_APP + 3; + +BASE_EXPORT void GetNonClientMetrics(NONCLIENTMETRICS* metrics); + +// Returns the string representing the current user sid. +BASE_EXPORT bool GetUserSidString(std::wstring* user_sid); + +// Returns true if the shift key is currently pressed. +BASE_EXPORT bool IsShiftPressed(); + +// Returns true if the ctrl key is currently pressed. +BASE_EXPORT bool IsCtrlPressed(); + +// Returns true if the alt key is currently pressed. +BASE_EXPORT bool IsAltPressed(); + +// Returns false if user account control (UAC) has been disabled with the +// EnableLUA registry flag. Returns true if user account control is enabled. +// NOTE: The EnableLUA registry flag, which is ignored on Windows XP +// machines, might still exist and be set to 0 (UAC disabled), in which case +// this function will return false. You should therefore check this flag only +// if the OS is Vista or later. +BASE_EXPORT bool UserAccountControlIsEnabled(); + +// Sets the boolean value for a given key in given IPropertyStore. +BASE_EXPORT bool SetBooleanValueForPropertyStore( + IPropertyStore* property_store, + const PROPERTYKEY& property_key, + bool property_bool_value); + +// Sets the string value for a given key in given IPropertyStore. +BASE_EXPORT bool SetStringValueForPropertyStore( + IPropertyStore* property_store, + const PROPERTYKEY& property_key, + const wchar_t* property_string_value); + +// Sets the application id in given IPropertyStore. The function is intended +// for tagging application/chromium shortcut, browser window and jump list for +// Win7. +BASE_EXPORT bool SetAppIdForPropertyStore(IPropertyStore* property_store, + const wchar_t* app_id); + +// Adds the specified |command| using the specified |name| to the AutoRun key. +// |root_key| could be HKCU or HKLM or the root of any user hive. +BASE_EXPORT bool AddCommandToAutoRun(HKEY root_key, const string16& name, + const string16& command); +// Removes the command specified by |name| from the AutoRun key. |root_key| +// could be HKCU or HKLM or the root of any user hive. +BASE_EXPORT bool RemoveCommandFromAutoRun(HKEY root_key, const string16& name); + +// Reads the command specified by |name| from the AutoRun key. |root_key| +// could be HKCU or HKLM or the root of any user hive. Used for unit-tests. +BASE_EXPORT bool ReadCommandFromAutoRun(HKEY root_key, + const string16& name, + string16* command); + +// Sets whether to crash the process during exit. This is inspected by DLLMain +// and used to intercept unexpected terminations of the process (via calls to +// exit(), abort(), _exit(), ExitProcess()) and convert them into crashes. +// Note that not all mechanisms for terminating the process are covered by +// this. In particular, TerminateProcess() is not caught. +BASE_EXPORT void SetShouldCrashOnProcessDetach(bool crash); +BASE_EXPORT bool ShouldCrashOnProcessDetach(); + +// Adjusts the abort behavior so that crash reports can be generated when the +// process is aborted. +BASE_EXPORT void SetAbortBehaviorForCrashReporting(); + +// A tablet by this definition is something that has integrated multi-touch +// ready to use and also has screen resolution not greater than 1366x768. +BASE_EXPORT bool IsMachineATablet(); + +// Get the size of a struct up to and including the specified member. +// This is necessary to set compatible struct sizes for different versions +// of certain Windows APIs (e.g. SystemParametersInfo). +#define SIZEOF_STRUCT_WITH_SPECIFIED_LAST_MEMBER(struct_name, member) \ + offsetof(struct_name, member) + \ + (sizeof static_cast<struct_name*>(NULL)->member) + +} // namespace win +} // namespace base + +#endif // BASE_WIN_WIN_UTIL_H_
diff --git a/src/base/win/win_util_unittest.cc b/src/base/win/win_util_unittest.cc new file mode 100644 index 0000000..b79ed56 --- /dev/null +++ b/src/base/win/win_util_unittest.cc
@@ -0,0 +1,58 @@ +// Copyright (c) 2010 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 <windows.h> + +#include "base/basictypes.h" +#include "base/string_util.h" +#include "base/win/win_util.h" +#include "base/win/windows_version.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +namespace { + +// Saves the current thread's locale ID when initialized, and restores it when +// the instance is going out of scope. +class ThreadLocaleSaver { + public: + ThreadLocaleSaver() : original_locale_id_(GetThreadLocale()) {} + ~ThreadLocaleSaver() { SetThreadLocale(original_locale_id_); } + + private: + LCID original_locale_id_; + + DISALLOW_COPY_AND_ASSIGN(ThreadLocaleSaver); +}; + +} // namespace + +// The test is somewhat silly, because the Vista bots some have UAC enabled +// and some have it disabled. At least we check that it does not crash. +TEST(BaseWinUtilTest, TestIsUACEnabled) { + if (GetVersion() >= base::win::VERSION_VISTA) { + UserAccountControlIsEnabled(); + } else { + EXPECT_TRUE(UserAccountControlIsEnabled()); + } +} + +TEST(BaseWinUtilTest, TestGetUserSidString) { + std::wstring user_sid; + EXPECT_TRUE(GetUserSidString(&user_sid)); + EXPECT_TRUE(!user_sid.empty()); +} + +TEST(BaseWinUtilTest, TestGetNonClientMetrics) { + NONCLIENTMETRICS metrics = {0}; + GetNonClientMetrics(&metrics); + EXPECT_TRUE(metrics.cbSize > 0); + EXPECT_TRUE(metrics.iScrollWidth > 0); + EXPECT_TRUE(metrics.iScrollHeight > 0); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/windows_version.cc b/src/base/win/windows_version.cc new file mode 100644 index 0000000..c130e0e --- /dev/null +++ b/src/base/win/windows_version.cc
@@ -0,0 +1,111 @@ +// 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/windows_version.h" + +#include <windows.h> + +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "base/win/registry.h" + +namespace base { +namespace win { + +// static +OSInfo* OSInfo::GetInstance() { + // Note: we don't use the Singleton class because it depends on AtExitManager, + // and it's convenient for other modules to use this classs without it. This + // pattern is copied from gurl.cc. + static OSInfo* info; + if (!info) { + OSInfo* new_info = new OSInfo(); + if (InterlockedCompareExchangePointer( + reinterpret_cast<PVOID*>(&info), new_info, NULL)) { + delete new_info; + } + } + return info; +} + +OSInfo::OSInfo() + : version_(VERSION_PRE_XP), + architecture_(OTHER_ARCHITECTURE), + wow64_status_(GetWOW64StatusForProcess(GetCurrentProcess())) { + OSVERSIONINFOEX version_info = { sizeof 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; + if ((version_number_.major == 5) && (version_number_.minor > 0)) { + // Treat XP Pro x64, Home Server, and Server 2003 R2 as Server 2003. + version_ = (version_number_.minor == 1) ? VERSION_XP : VERSION_SERVER_2003; + } else if (version_number_.major == 6) { + switch (version_number_.minor) { + case 0: + // Treat Windows Server 2008 the same as Windows Vista. + version_ = VERSION_VISTA; + break; + case 1: + // Treat Windows Server 2008 R2 the same as Windows 7. + version_ = VERSION_WIN7; + break; + default: + DCHECK_EQ(version_number_.minor, 2); + // Treat Windows Server 2012 the same as Windows 8. + version_ = VERSION_WIN8; + break; + } + } else if (version_number_.major > 6) { + NOTREACHED(); + version_ = VERSION_WIN_LAST; + } + service_pack_.major = version_info.wServicePackMajor; + service_pack_.minor = version_info.wServicePackMinor; + + SYSTEM_INFO system_info = { 0 }; + GetNativeSystemInfo(&system_info); + switch (system_info.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_INTEL: architecture_ = X86_ARCHITECTURE; break; + case PROCESSOR_ARCHITECTURE_AMD64: architecture_ = X64_ARCHITECTURE; break; + case PROCESSOR_ARCHITECTURE_IA64: architecture_ = IA64_ARCHITECTURE; break; + } + processors_ = system_info.dwNumberOfProcessors; + allocation_granularity_ = system_info.dwAllocationGranularity; +} + +OSInfo::~OSInfo() { +} + +std::string OSInfo::processor_model_name() { + if (processor_model_name_.empty()) { + const wchar_t kProcessorNameString[] = + L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"; + base::win::RegKey key(HKEY_LOCAL_MACHINE, kProcessorNameString, KEY_READ); + string16 value; + key.ReadValue(L"ProcessorNameString", &value); + processor_model_name_ = UTF16ToUTF8(value); + } + return processor_model_name_; +} + +// static +OSInfo::WOW64Status OSInfo::GetWOW64StatusForProcess(HANDLE process_handle) { + typedef BOOL (WINAPI* IsWow64ProcessFunc)(HANDLE, PBOOL); + IsWow64ProcessFunc is_wow64_process = reinterpret_cast<IsWow64ProcessFunc>( + GetProcAddress(GetModuleHandle(L"kernel32.dll"), "IsWow64Process")); + if (!is_wow64_process) + return WOW64_DISABLED; + BOOL is_wow64 = FALSE; + if (!(*is_wow64_process)(process_handle, &is_wow64)) + return WOW64_UNKNOWN; + return is_wow64 ? WOW64_ENABLED : WOW64_DISABLED; +} + +Version GetVersion() { + return OSInfo::GetInstance()->version(); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/windows_version.h b/src/base/win/windows_version.h new file mode 100644 index 0000000..d466dad --- /dev/null +++ b/src/base/win/windows_version.h
@@ -0,0 +1,110 @@ +// 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_WIN_WINDOWS_VERSION_H_ +#define BASE_WIN_WINDOWS_VERSION_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" + +typedef void* HANDLE; + +namespace base { +namespace win { + +// The running version of Windows. This is declared outside OSInfo for +// syntactic sugar reasons; see the declaration of GetVersion() below. +// NOTE: Keep these in order so callers can do things like +// "if (base::win::GetVersion() >= base::win::VERSION_VISTA) ...". +enum Version { + VERSION_PRE_XP = 0, // Not supported. + VERSION_XP, + VERSION_SERVER_2003, // Also includes XP Pro x64 and Server 2003 R2. + VERSION_VISTA, // Also includes Windows Server 2008. + VERSION_WIN7, // Also includes Windows Server 2008 R2. + VERSION_WIN8, // Also includes Windows Server 2012. + VERSION_WIN_LAST, // Indicates error condition. +}; + +// 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. +class BASE_EXPORT OSInfo { + public: + struct VersionNumber { + int major; + int minor; + int build; + }; + + struct ServicePack { + int major; + int minor; + }; + + // The processor architecture this copy of Windows natively uses. For + // example, given an x64-capable processor, we have three possibilities: + // 32-bit Chrome running on 32-bit Windows: X86_ARCHITECTURE + // 32-bit Chrome running on 64-bit Windows via WOW64: X64_ARCHITECTURE + // 64-bit Chrome running on 64-bit Windows: X64_ARCHITECTURE + enum WindowsArchitecture { + X86_ARCHITECTURE, + X64_ARCHITECTURE, + IA64_ARCHITECTURE, + OTHER_ARCHITECTURE, + }; + + // Whether a process is running under WOW64 (the wrapper that allows 32-bit + // processes to run on 64-bit versions of Windows). This will return + // WOW64_DISABLED for both "32-bit Chrome on 32-bit Windows" and "64-bit + // Chrome on 64-bit Windows". WOW64_UNKNOWN means "an error occurred", e.g. + // the process does not have sufficient access rights to determine this. + enum WOW64Status { + WOW64_DISABLED, + WOW64_ENABLED, + WOW64_UNKNOWN, + }; + + static OSInfo* GetInstance(); + + Version version() const { return version_; } + // The next two functions return arrays of values, [major, minor(, build)]. + VersionNumber version_number() const { return version_number_; } + ServicePack service_pack() const { return service_pack_; } + WindowsArchitecture architecture() const { return architecture_; } + int processors() const { return processors_; } + size_t allocation_granularity() const { return allocation_granularity_; } + WOW64Status wow64_status() const { return wow64_status_; } + std::string processor_model_name(); + + // Like wow64_status(), but for the supplied handle instead of the current + // process. This doesn't touch member state, so you can bypass the singleton. + static WOW64Status GetWOW64StatusForProcess(HANDLE process_handle); + + private: + OSInfo(); + ~OSInfo(); + + Version version_; + VersionNumber version_number_; + ServicePack service_pack_; + WindowsArchitecture architecture_; + int processors_; + size_t allocation_granularity_; + WOW64Status wow64_status_; + std::string processor_model_name_; + + DISALLOW_COPY_AND_ASSIGN(OSInfo); +}; + +// Because this is by far the most commonly-requested value from the above +// singleton, we add a global-scope accessor here as syntactic sugar. +BASE_EXPORT Version GetVersion(); + +} // namespace win +} // namespace base + +#endif // BASE_WIN_WINDOWS_VERSION_H_
diff --git a/src/base/win/wrapped_window_proc.cc b/src/base/win/wrapped_window_proc.cc new file mode 100644 index 0000000..04e59c5 --- /dev/null +++ b/src/base/win/wrapped_window_proc.cc
@@ -0,0 +1,63 @@ +// 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/wrapped_window_proc.h" + +#include "base/atomicops.h" +#include "base/logging.h" +#include "base/process_util.h" + +namespace { + +base::win::WinProcExceptionFilter s_exception_filter = NULL; + +} // namespace. + +namespace base { +namespace win { + +WinProcExceptionFilter SetWinProcExceptionFilter( + WinProcExceptionFilter filter) { + subtle::AtomicWord rv = subtle::NoBarrier_AtomicExchange( + reinterpret_cast<subtle::AtomicWord*>(&s_exception_filter), + reinterpret_cast<subtle::AtomicWord>(filter)); + return reinterpret_cast<WinProcExceptionFilter>(rv); +} + +int CallExceptionFilter(EXCEPTION_POINTERS* info) { + return s_exception_filter ? s_exception_filter(info) : + EXCEPTION_CONTINUE_SEARCH; +} + +BASE_EXPORT void InitializeWindowClass( + const char16* class_name, + WNDPROC window_proc, + UINT style, + int class_extra, + int window_extra, + HCURSOR cursor, + HBRUSH background, + const char16* menu_name, + HICON large_icon, + HICON small_icon, + WNDCLASSEX* class_out) { + class_out->cbSize = sizeof(WNDCLASSEX); + class_out->style = style; + class_out->lpfnWndProc = window_proc; + class_out->cbClsExtra = class_extra; + class_out->cbWndExtra = window_extra; + class_out->hInstance = base::GetModuleFromAddress(window_proc); + class_out->hIcon = large_icon; + class_out->hCursor = cursor; + class_out->hbrBackground = background; + class_out->lpszMenuName = menu_name; + class_out->lpszClassName = class_name; + class_out->hIconSm = small_icon; + + // Check if |window_proc| is valid. + DCHECK(class_out->hInstance != NULL); +} + +} // namespace win +} // namespace base
diff --git a/src/base/win/wrapped_window_proc.h b/src/base/win/wrapped_window_proc.h new file mode 100644 index 0000000..b5793f2 --- /dev/null +++ b/src/base/win/wrapped_window_proc.h
@@ -0,0 +1,85 @@ +// 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. + +// Provides a way to handle exceptions that happen while a WindowProc is +// running. The behavior of exceptions generated inside a WindowProc is OS +// dependent, but it is possible that the OS just ignores the exception and +// continues execution, which leads to unpredictable behavior for Chrome. + +#ifndef BASE_WIN_WRAPPED_WINDOW_PROC_H_ +#define BASE_WIN_WRAPPED_WINDOW_PROC_H_ + +#include <windows.h> + +#include "base/base_export.h" +#include "base/string16.h" + +namespace base { +namespace win { + +// An exception filter for a WindowProc. The return value determines how the +// exception should be handled, following standard SEH rules. However, the +// expected behavior for this function is to not return, instead of returning +// EXCEPTION_EXECUTE_HANDLER or similar, given that in general we are not +// prepared to handle exceptions. +typedef int (__cdecl *WinProcExceptionFilter)(EXCEPTION_POINTERS* info); + +// Sets the filter to deal with exceptions inside a WindowProc. Returns the old +// exception filter, if any. +// This function should be called before any window is created. +BASE_EXPORT WinProcExceptionFilter SetWinProcExceptionFilter( + WinProcExceptionFilter filter); + +// Calls the registered exception filter. +BASE_EXPORT int CallExceptionFilter(EXCEPTION_POINTERS* info); + +// Initializes the WNDCLASSEX structure |*class_out| to be passed to +// RegisterClassEx() making sure that it is associated with the module +// containing the window procedure. +BASE_EXPORT void InitializeWindowClass( + const char16* class_name, + WNDPROC window_proc, + UINT style, + int class_extra, + int window_extra, + HCURSOR cursor, + HBRUSH background, + const char16* menu_name, + HICON large_icon, + HICON small_icon, + WNDCLASSEX* class_out); + +// Wrapper that supplies a standard exception frame for the provided WindowProc. +// The normal usage is something like this: +// +// LRESULT CALLBACK MyWinProc(HWND hwnd, UINT message, +// WPARAM wparam, LPARAM lparam) { +// // Do Something. +// } +// +// ... +// +// WNDCLASSEX wc = {0}; +// wc.lpfnWndProc = WrappedWindowProc<MyWinProc>; +// wc.lpszClassName = class_name; +// ... +// RegisterClassEx(&wc); +// +// CreateWindowW(class_name, window_name, ... +// +template <WNDPROC proc> +LRESULT CALLBACK WrappedWindowProc(HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam) { + LRESULT rv = 0; + __try { + rv = proc(hwnd, message, wparam, lparam); + } __except(CallExceptionFilter(GetExceptionInformation())) { + } + return rv; +} + +} // namespace win +} // namespace base + +#endif // BASE_WIN_WRAPPED_WINDOW_PROC_H_
diff --git a/src/base/win/wrapped_window_proc_unittest.cc b/src/base/win/wrapped_window_proc_unittest.cc new file mode 100644 index 0000000..ccf3c85 --- /dev/null +++ b/src/base/win/wrapped_window_proc_unittest.cc
@@ -0,0 +1,79 @@ +// 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/win/wrapped_window_proc.h" +#include "base/message_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +DWORD kExceptionCode = 12345; +WPARAM kCrashMsg = 98765; + +// A trivial WindowProc that generates an exception. +LRESULT CALLBACK TestWindowProc(HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam) { + if (message == kCrashMsg) + RaiseException(kExceptionCode, 0, 0, NULL); + return DefWindowProc(hwnd, message, wparam, lparam); +} + +// This class implements an exception filter that can be queried about a past +// exception. +class TestWrappedExceptionFiter { + public: + TestWrappedExceptionFiter() : called_(false) { + EXPECT_FALSE(s_filter_); + s_filter_ = this; + } + + ~TestWrappedExceptionFiter() { + EXPECT_EQ(s_filter_, this); + s_filter_ = NULL; + } + + bool called() { + return called_; + } + + // The actual exception filter just records the exception. + static int Filter(EXCEPTION_POINTERS* info) { + EXPECT_FALSE(s_filter_->called_); + if (info->ExceptionRecord->ExceptionCode == kExceptionCode) + s_filter_->called_ = true; + return EXCEPTION_EXECUTE_HANDLER; + } + + private: + bool called_; + static TestWrappedExceptionFiter* s_filter_; +}; +TestWrappedExceptionFiter* TestWrappedExceptionFiter::s_filter_ = NULL; + +} // namespace. + +TEST(WrappedWindowProc, CatchesExceptions) { + HINSTANCE hinst = GetModuleHandle(NULL); + std::wstring class_name(L"TestClass"); + + WNDCLASS wc = {0}; + wc.lpfnWndProc = base::win::WrappedWindowProc<TestWindowProc>; + wc.hInstance = hinst; + wc.lpszClassName = class_name.c_str(); + RegisterClass(&wc); + + HWND window = CreateWindow(class_name.c_str(), 0, 0, 0, 0, 0, 0, HWND_MESSAGE, + 0, hinst, 0); + ASSERT_TRUE(window); + + // Before generating the exception we make sure that the filter will see it. + TestWrappedExceptionFiter wrapper; + base::win::WinProcExceptionFilter old_filter = + base::win::SetWinProcExceptionFilter(TestWrappedExceptionFiter::Filter); + + SendMessage(window, kCrashMsg, 0, 0); + EXPECT_TRUE(wrapper.called()); + + base::win::SetWinProcExceptionFilter(old_filter); +}